{"id":23543876,"url":"https://github.com/beatthat/entities","last_synced_at":"2026-04-18T00:02:10.427Z","repository":{"id":144017082,"uuid":"140025795","full_name":"beatthat/entities","owner":"beatthat","description":"framework for accessing entities, an entity being an item of data that has an id and can be resolved by that id","archived":false,"fork":false,"pushed_at":"2020-10-29T05:34:53.000Z","size":120,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-15T08:42:16.118Z","etag":null,"topics":["entities","mvc","package","package-manager","redux","separation-of-concerns","unity","unity3d"],"latest_commit_sha":null,"homepage":null,"language":"C#","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/beatthat.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,"publiccode":null,"codemeta":null}},"created_at":"2018-07-06T20:12:04.000Z","updated_at":"2020-10-29T05:34:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"710b708e-cdef-4e3b-84f6-865c47079f11","html_url":"https://github.com/beatthat/entities","commit_stats":null,"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"purl":"pkg:github/beatthat/entities","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beatthat%2Fentities","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beatthat%2Fentities/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beatthat%2Fentities/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beatthat%2Fentities/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/beatthat","download_url":"https://codeload.github.com/beatthat/entities/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beatthat%2Fentities/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31950891,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-17T17:29:20.459Z","status":"ssl_error","status_checked_at":"2026-04-17T17:28:47.801Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["entities","mvc","package","package-manager","redux","separation-of-concerns","unity","unity3d"],"created_at":"2024-12-26T07:11:53.284Z","updated_at":"2026-04-18T00:02:10.259Z","avatar_url":"https://github.com/beatthat.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ca name=\"readme\"\u003e\u003c/a\u003eNeed to manage a collection of data items, like say, downloadable songs for a music app? Entities make it easy.\n\nYou can use entities to manage any collection whose items share a common data type and where each item has a unique id. A common use for entities is to manage a client store of items retrieved via a REST API.\n\n## Install\n\nFrom your unity project folder:\n\n    npm init --force\n    npm install beathat/entities --save\n\nThe package and all its dependencies will be installed under Assets/Plugins/packages.\n\nIn case it helps, a quick video of the above: https://youtu.be/Uss_yOiLNw8\n\n## USAGE\n\n#### Setting Up a new Entity type\n\nIn the example below, we'll set up a DogData entity, where each item has a url for an image of a dog.\n\nYou need 4 basic components to set up a new Entity type.\n\nFirst, you need a ```DataType```.\n\n```csharp\n// Your data type can be a struct or a class.\n//\n// I like to use structs because it clarifies\n// and enforces that entity items shouldn't\n// be edited directly, i.e. if the DogData entity below\n// were a class and you retrieved one from the store\n// and edited its properties, you will have in effect\n// changed the entity in the store.\npublic struct DogData\n{\n  public string id;\n  public string imageUrl;\n}\n```\n\n...next you need an ```EntityStore``` that will hold your entities.\n```csharp\n// The [RegisterEntittStore] attribute makes the store injectable with dependency injection.\n[RegisterEntityStore]\npublic class DogStore : EntityStore\u003cDogData\u003e {}\n```\n\n...a ```Command``` that resolves and stores entities in response to notifications\n\n```csharp\n[RegisterCommand]\npublic class ResolveDogCmd : ResolveEntityCmd\u003cDogData\u003e {}\n```\n\n...and finally an ```EntityResolver``` whose job is to resolve an item of entity data given an id (or alias). Whereas the ```DogStore``` and ```ResolveDogCmd``` classes above are templates, your ```EntityResolver``` is the main class where you need to provide some implementation.\n\n```csharp\nusing System.Threading.Tasks;\nusing BeatThat.Requests;\nusing BeatThat.Service;\n\n[RegisterService(typeof(EntityResolver\u003cDogData\u003e))]\npublic class DogDataResolver : DefaultEntityResolver\u003cDogData\u003e\n{\n    public override async Task\u003cResolveResultDTO\u003cDogData\u003e\u003e ResolveAsync(string key)\n    {\n      // NOTE: this public dog api (api.thedogapi.co.uk) seems to be intermittently unavailable\n\n        var path = string.Format(\"https://api.thedogapi.co.uk/v2/dog.php?id={0}\", loadKey);\n\n        try {\n\n            // using BeatThat.Requests.WebRequest here, just an easy way\n            // to make a one-line HTTP request and get a typed result\n            var data = await new WebRequest\u003cDogData\u003e(path).ExecuteAsync();\n\n            return new ResolveResultDTO\u003cDogData\u003e\n            {\n                key = key,\n                id = data.id,\n                status = ResolveStatusCode.OK,\n                timestamp = DateTimeOffset.Now,\n                data = data\n            };\n        }\n        catch(Exception e) {\n            return new ResolveResultDTO\u003cDogData\u003e\n            {\n                key = key,\n                id = key,\n                status = ResolveStatusCode.ERROR,\n                message = e.Message,\n                timestamp = DateTimeOffset.Now\n            };\n        }\n    }\n}\n```\n\n...if you're not using .NET 4.6 (if async/await is unsupported in your project), then use this example instead:\n\n```csharp\nusing BeatThat.Requests;\nusing BeatThat.Service;\n\n[RegisterService]\npublic class DogResolver : DefaultEntityResolver\u003cDogData\u003e\n{\n  public Request\u003cResolveResultDTO\u003cDogData\u003e\u003e Resolve(string loadKey, Action\u003cRequest\u003cResolveResultDTO\u003cGoalData\u003e\u003e\u003e callback)\n  {\n    var promise = new Promise((resolve, reject) =\u003e {\n      // NOTE: this public dog api (api.thedogapi.co.uk) seems to be intermittently unavailable\n\n      //https://api.thedogapi.co.uk/v2/dog.php?id=5ta5p7JdHEL\n      var path = string.Format(\"https://api.thedogapi.co.uk/v2/dog.php?id={0}\", loadKey);\n\n      new WebRequest\u003cDogData\u003e(path).Execute(result =\u003e {\n        if(result.hasError) {\n          reject(result);\n          return;\n        }\n        resolve(result.item);\n      });\n    });\n\n    promise.Execute(callback);\n    return promise;\n  }\n}\n```\n\n\n#### Using Entities\n\nBelow are a set of short examples of accessing Entity data. All the examples are using dependency injection to get access to an EntityStore service and that requires that this piece of init happened somewhere as close as possible to app launch (before the other examples would run):\n\n```csharp\nusing BeatThat.Service;\npublic class MyAppStartup : MonoBehaviour\n{\n  void Start()\n  {\n    Services.Init();\n  }\n}\n```\n\nHere's how you get data for an Entity:\n\n```csharp\nusing BeatThat.Service;\nusing BeatThat.DependencyInjection;\nusing BeatThat.Entities;\npublic class Foo : DependencyInjectedBehaviour\n{\n  [Inject] HasEntities\u003cDogData\u003e dogs;\n\n  public void DoSomethingWithDog(string dogId)\n  {\n    DogData data;\n    if(this.dogs.GetData(dogId, out data)) {\n      // use data, but only if it is already resolved\n    }\n  }\n}\n```\n\n...here's how you get an Entity that might not already be resolved\n\n```csharp\nusing BeatThat.Service;\nusing BeatThat.DependencyInjection;\nusing BeatThat.Entities;\npublic class Foo : DependencyInjectedBehaviour\n{\n  [Inject] HasEntities\u003cDogData\u003e dogs;\n\n  public async void DoSomethingWithDog(string dogId)\n  {\n    DogData data;\n    if(!this.dogs.GetData(dogId, out data)) {\n      try {\n        data = await Entity\u003cDogData\u003e.ResolveOrThrowAsync(dogId, this.dogs);\n      }\n      catch(Exception e) {\n        // the entity was either not found or some other error in resolve\n      }\n    }\n\n    // do something with dog data\n  }\n}\n```\n\n...here's how you get the same entity if you want to handle failures without exceptions:\n\n```csharp\nusing BeatThat.Service;\nusing BeatThat.DependencyInjection;\nusing BeatThat.Entities;\npublic class Foo : DependencyInjectedBehaviour\n{\n  [Inject] HasEntities\u003cDogData\u003e dogs;\n\n  public async void DoSomethingWithDog(string dogId)\n  {\n    DogData data;\n    if(!this.dogs.GetData(dogId, out data)) {\n      var result = await Entity\u003cDogData\u003e.ResolveAsync(dogId, this.dogs);\n      if(!result.status == ResolveStatusCode.OK) {\n        // handle the failure\n        return;\n      }\n\n      data = result.data; // proceed\n    }\n  }\n}\n```\n\n...here's how you get an Entity that might not already be resolved if async/await is not supported (.NET \u003c 4.6)\n\n```csharp\nusing BeatThat.Service;\nusing BeatThat.DependencyInjection;\nusing BeatThat.Entities;\npublic class Foo : DependencyInjectedBehaviour\n{\n  [Inject] HasEntities\u003cDogData\u003e dogs;\n\n  public void DoSomethingWithDog(string dogId)\n  {\n    DogData data;\n    if(this.dogs.GetData(dogId, out data)) {\n      // usually have another function to do something w data\n      // to avoid duplicate code\n\n      return;\n    }\n\n    Entity\u003cDogData\u003e.Resolve(dogId, this.dogs, result =\u003e {\n      if(result.hasError) {\n        Debug.LogError(result.error);\n        return;\n      }\n\n      var data = result.item;\n      // call your DoSomethingWithDogData function\n    });\n  }\n}\n```\n\nHere's how you check whether an Entity's data is available for use.\n\n```csharp\n//HasEntities\u003cDogData\u003e dogs;\nvar canUseData = this.dogs.IsResolved(dogId);\n```\n\n...or if you want to know if an entity is in progress loading/resolving\n\n```csharp\n//HasEntities\u003cDogData\u003e dogs;\nvar isResolving = this.dogs.IsResolveInProgress(dogId);\n```\n\n...or if you want to inspect an Entity's full resolve status, including possible errors\n\n```csharp\n//HasEntities\u003cDogData\u003e dogs;\nResolveStatus status;\nif(this.dogs.GetStatus(id, out status)) {\n  // If GetStatus returned FALSE above,\n  // the entity is not resolved and there\n  // has been no attempt to resolve it\n\n  var canUse = status.hasResolved;\n  var isResolvingNow = status.isResolveInProgress;\n  if(status.hasError) {\n    var error = status.error;\n  }\n\n  var shouldRefresh = status.IsExpiredAt(DateTimeOffset.Now);\n}\n```\n\n...or if you want to inspect both the status and the data (which may or may not be available)\n\n```csharp\n//HasEntities\u003cDogData\u003e dogs;\nEntity\u003cDogData\u003e dog;\nif(this.dogs.GetEntity(id, out dog)) {\n  DogData data = dog.data;\n  ResolveStatus status = dog.status;\n}\n```\n\n...here's how you request that an Entity resolve without waiting for it\n\n```csharp\nEntity\u003cDogData\u003e.RequestResolve(\"some-dog-id\");\n```\n\n...and here's how you listen for updates to entity resolve status\n\n```csharp\n// using BeatThat.Notifications\nNotificationBus.Add(Entity\u003cDogData\u003e.Updated, (id) =\u003e {\n  // do something with the id that was updated\n})\n```\n\n#### Using Entities with BeatThat helper classes\n\nThere are base classes in BeatThat that simplify working with notifications and dependency injection. You don't need to use these classes but they handle a lot of boilerplate for you.\n\nBelow are some examples, with and with out the base classes.\n\n...a global singleton service that listens for Entity updates.\n\n```csharp\nusing BeatThat.Service;\nusing BeatThat.Bindings;\nusing BeatThat.DependencyInjection;\nusing BeatThat.Entities;\n[RegisterService]\npublic class MyDogService : BindingService\n{\n  [Inject] HasEntities\u003cDogData\u003e dogs;\n\n  override protected void BindAll()\n  {\n    Bind(Entity\u003cDogData\u003e.UPDATE, this.OnUpdated);\n  }\n\n  private void OnUpdated(string id)\n  {\n    Entity\u003cDogData\u003e dog;\n    if(!this.dogs.GetEntity(id)) {\n      return;\n    }\n    // do something with this dog\n  }\n}\n```\n\n...you can have the same behavior as above with no special base classes doing something like this:\n\n```csharp\nusing BeatThat.Service;\nusing BeatThat.Notifications;\nusing BeatThat.Entities;\n\n// assume you made this a singleton by\n// your preferred method\npublic class MyDogService : MonoBehaviour\n{\n  private HasEntities\u003cDogData\u003e dogs;\n\n  void OnEnable()\n  {\n    this.dogs = Services.Require\u003cHasEntities\u003cDogData\u003e\u003e();\n    NotificationBus.Add(Entity\u003cDogData\u003e.UPDATE, this.OnUpdated);\n  }\n\n  void OnDisable()\n  {\n    NotificationBus.Remove(Entity\u003cDogData\u003e.UPDATE, this.OnUpdated);\n  }\n\n  private void OnUpdated(string id)\n  {\n    Entity\u003cDogData\u003e dog;\n    if(!this.dogs.GetEntity(id)) {\n      return;\n    }\n    // do something with this dog\n  }\n}\n```\n\n#### References\n\nThe architecture of entities borrows a lot from [redux](https://redux.js.org/)--there is an EntityStore containing items which have been stored and updated (reduced) in response to notifications. Understanding redux and its motivations will help you understand this implementation of entities or any of the many similar frameworks in the wild. I highly recommend going through Dan Abramov's [redux course on egghead](https://egghead.io/courses/getting-started-with-redux) if you have the time.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeatthat%2Fentities","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbeatthat%2Fentities","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeatthat%2Fentities/lists"}