{"id":16625812,"url":"https://github.com/aleclarson/condo","last_synced_at":"2026-04-21T15:03:46.728Z","repository":{"id":66177624,"uuid":"123457488","full_name":"aleclarson/condo","owner":"aleclarson","description":"Simple, event-based state wrappers with optional validation","archived":false,"fork":false,"pushed_at":"2018-12-05T16:48:10.000Z","size":13,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-18T02:44:03.412Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/aleclarson.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-03-01T15:57:14.000Z","updated_at":"2018-05-12T01:22:02.000Z","dependencies_parsed_at":"2023-02-20T17:15:45.521Z","dependency_job_id":null,"html_url":"https://github.com/aleclarson/condo","commit_stats":{"total_commits":2,"total_committers":1,"mean_commits":2.0,"dds":0.0,"last_synced_commit":"7d9c91bae46b85e8e9f9b335e984ab32b8ac9776"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aleclarson%2Fcondo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aleclarson%2Fcondo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aleclarson%2Fcondo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aleclarson%2Fcondo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aleclarson","download_url":"https://codeload.github.com/aleclarson/condo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243039628,"owners_count":20226131,"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":"2024-10-12T04:07:20.317Z","updated_at":"2026-04-21T15:03:46.678Z","avatar_url":"https://github.com/aleclarson.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# condo v0.0.1\n\nSimple, event-based state wrappers with incremental schema validation.\n\n```js\nlet app = condo.box()\n\napp.domain({\n  type: 'users',\n  spawn: app.entity({\n    type: 'user',\n    schema: {\n      id: 'number',\n      name: 'string',\n    }\n  })\n})\n\nlet users = app.domain.users()\n\nusers.on('add', console.log)\n\nlet user = users.add({\n  id: 1,\n  name: 'Ron Burgundy',\n})\n\nusers.get(1) === user // =\u003e true\n\nusers.remove(1)\n```\n\nThere are 3 object types in condo:\n- `Box` the root-level state container\n- `Entity` a powerful state wrapper\n- `Domain` an entity container\n\nThe `Entity` and `Domain` types are meant to be subclassed using\n  the `box.entity` and `box.domain` methods. Once created, you can\n  construct your subclass using its \"type\" name like so:\n\n```js\nlet user = box.entity.user(opts)\nlet users = box.domain.users(opts)\n```\n\nThe great thing about domains is their ability to construct\n  entities implicitly using the `spawn` option:\n\n```js\nbox.domain({\n  type: 'foo',\n  spawn(arg) {\n    if (typeof arg == 'string') {\n      return box.entity.thing(arg)\n    }\n    throw Error('Expected a string!')\n  }\n})\n\nlet foo = box.domain.foo()\nlet thing = foo.add('hello world')\n```\n\nBy default, all domain values must have an `id` property, but\n  you can use the `identify` option for deriving identifiers\n  from each value:\n\n```js\nbox.domain({\n  type: 'foo',\n  spawn: box.entity.thing,\n  identify(arg) {\n    // `arg` is the same value passed to `spawn`.\n    // If you return falsy, `entity.get('id')` is used.\n    // The returned identifier is cached internally.\n  }\n})\n```\n\nUse the `constructor` option with `box.entity` or `box.domain`\n  if you want to initialize each instance.\n\n```js\nbox.domain({\n  type: 'foo',\n  spawn: box.entity({\n    type: 'thing',\n    constructor(arg) {\n      // Called on each entity instance.\n    }\n  }),\n  constructor(opts) {\n    // Called on each domain instance.\n  }\n})\n```\n\n---\n\n### State wrappers\n\nThe `Box` and `Entity` types wrap an internal state object\n  to provide powerful controls.\n\nThe `get`, `set`, `merge`, and `delete` methods allow for\n  easy path-based data access/mutation. This means dot-notation\n  is supported.\n\nThe `get` method will never throw, preferring to return\n  `undefined` if a path does not exist.\n\nThe `set`, `merge`, and `delete` methods will throw if a parent\n  path is defined but not an object. If a parent path is null\n  or undefined, the `set` method will avoid an error by creating\n  a new object, and the `merge` method will avoid the extra work\n  of cloning by simply using the given parent object.\n\nThe `set`, `merge`, and `delete` methods are chainable.\n\n#### Custom methods\n\nThe `extend` static method lets you easily add custom methods to\nyour domain and entity types.\n\n```js\nbox.domain({\n  type: 'foo',\n  spawn() {return {}}\n}).extend({\n  hello() {\n    this.emit('hello')\n  }\n})\n\n// Print the event to console.\nbox.on('foo:hello', console.log)\n\n// Create an instance.\nlet foo = box.domain.foo()\n\nfoo.hello()\n```\n\n---\n\n### The event system\n\nThe `on`, `off`, and `emit` methods exist on all `Box`, `Domain`,\n  and `Entity` objects for driving event-based reactions.\n\nWithin the event system, there is a sort of pseudo-bubbling where\n  a domain can listen to the events of its children and the box can\n  listen to the events of any descendant.\n\nWhen an entity emits an event, its own listeners are called first.\n  If a parent domain exists, its listeners are called. And finally,\n  the box's listeners are called.\n\nWhen the box or domain receives an entity event, the event name\n  always begins with the entity type and a colon.\n\n```js\nusers.on('user:load', (event) =\u003e {\n  // Called when a user entity emits a 'load' event.\n})\n```\n\nSimilarly, when the box receives a domain event, the event name\n  always begins with the domain type and a colon.\n\n```js\nbox.on('users:add', (event) =\u003e {\n  // Called when a users domain emits an 'add' event.\n})\n```\n\nEvent objects always have the following properties:\n- `name: string`\n- `source: entity|domain|box`\n\nWhen calling `emit`, you can add any properties you want\n  to the event object.\n\n```js\nuser.emit({\n  name: 'error',\n  error: new Error(),\n})\n```\n\nThe built-in \"add\" and \"remove\" domain events always include\n  the entity, which is accessible using the entity type name.\n\n```js\nusers.on('add', (event) =\u003e {\n  event.user // =\u003e [object Entity]\n})\n```\n\nThe event system embraces colon-separated event names. This style\n  allows for useful namespacing and event globbing.\n\nValid event patterns include:\n- `*` all events\n- `foo` one event\n- `foo:*` events that begin with `foo:`\n- `*:foo` events that end with `:foo`\n\nYou can listen to multiple event patterns with space separation.\n\n```js\nbox.on('foo bar:*', (event) =\u003e {\n  console.log(event.name)\n})\n```\n\nThere are 3 ways to remove a listener:\n\n```js\n// Stop listening for 'bar:*'\nbox.off('bar:*')\n\n// Stop a specific listener of 'bar:*'\nbox.off('bar:*', fn)\n\n// Stop a specific listener\nbox.off(fn)\n```\n\nThe event pattern is searched for as-is, so `bar:*` would not\n  remove a listener of `bar:foo`.\n\nYou can remove multiple event patterns with space separation.\n\nFor listeners of multiple event patterns, removing one pattern\n  does not affect the other patterns.\n\n#### Shared listeners\n\nWhen creating a `Domain` or `Entity` type, you can attach listeners\n  that will be reused across all instances.\n\n```js\n// Log 'add' events for all 'users' domain instances.\nbox.domain.users.on('add', console.log)\n```\n\nShared listeners are not removable.\n\nShared listeners are called after instance-specific listeners.\n\n---\n\n### The `schema` option\n\nBoth the `Box` and `Entity` classes have a `schema` option\n  for strict type validation of state.\n\nThe word \"nullish\" is used to describe both null and undefined.\n\nThe following values are valid types:\n- `any` any value except nullish\n- `array` uses `Array.isArray`\n- `boolean`\n- `date`\n- `error`\n- `false`\n- `function`\n- `int` a round number\n- `number`\n- `null`\n- `object` an object literal or `Object.create(null)`\n- `promise` an object with a `then` method\n- `regexp`\n- `string`\n- `symbol`\n- `true`\n- `uint` a positive, round number\n\nYou can append `?` to the end of any type to allow nullish values.\n\nYou can specify multiple types using the `|` separator.\n\nYou can use uppercase strings (eg: `Uint8Array`) to validate\n  against a global class. Currently, the type must be accessible\n  from the `window` object.\n\nAll number types protect against NaN.\n\nThe `date`, `error`, and `object` types use `Object.prototype.toString`\n  to validate value types. So your custom error types should always\n  evaluate to `[object Error]` when called with `toString`.\n\nCustom data types will be supported in the future.\n\nThe validator function created from the `schema` object is a collection\n  of switch cases glued together with `new Function` for optimal performance.\n  It's reused between instances of the same `Entity` type as the `_validate`\n  method. The same name is used for `Box` objects with schemas.\n\n```js\n// Set `throws` to false to prevent exceptions, else leave it blank.\nbox._validate(value, key, throws)\n```\n\nThe `set` and `merge` methods check the schema when updating values.\n  An error is thrown when a value's type is invalid. Currently, only\n  root-level keys can be validated, so you must create another Entity\n  type if you want to validate an object within another entity.\n\nThe `delete` method will throw if the given key exists in the schema\n  without a trailing `?`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faleclarson%2Fcondo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faleclarson%2Fcondo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faleclarson%2Fcondo/lists"}