{"id":13806018,"url":"https://github.com/orbitjs/ember-orbit","last_synced_at":"2025-04-04T08:03:46.012Z","repository":{"id":14103152,"uuid":"16807601","full_name":"orbitjs/ember-orbit","owner":"orbitjs","description":"Ember.js data layer built with Orbit.js","archived":false,"fork":false,"pushed_at":"2023-03-01T12:50:37.000Z","size":5181,"stargazers_count":319,"open_issues_count":25,"forks_count":42,"subscribers_count":16,"default_branch":"main","last_synced_at":"2025-03-29T04:37:43.749Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/orbitjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","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":"2014-02-13T15:42:40.000Z","updated_at":"2025-02-28T03:02:25.000Z","dependencies_parsed_at":"2024-05-04T17:53:35.272Z","dependency_job_id":null,"html_url":"https://github.com/orbitjs/ember-orbit","commit_stats":{"total_commits":747,"total_committers":27,"mean_commits":"27.666666666666668","dds":0.2449799196787149,"last_synced_commit":"c98f1fa051cd7e64cd78d0465c6a8be1d0173c08"},"previous_names":[],"tags_count":81,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orbitjs%2Fember-orbit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orbitjs%2Fember-orbit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orbitjs%2Fember-orbit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/orbitjs%2Fember-orbit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/orbitjs","download_url":"https://codeload.github.com/orbitjs/ember-orbit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247137426,"owners_count":20889873,"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-08-04T01:01:07.198Z","updated_at":"2025-04-04T08:03:45.989Z","avatar_url":"https://github.com/orbitjs.png","language":"TypeScript","readme":"# ember-orbit\n\n[![Join the chat at https://gitter.im/orbitjs/orbit.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/orbitjs/orbit.js?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n`ember-orbit` (or \"EO\") is a library that integrates\n[orbit.js](https://github.com/orbitjs/orbit) with\n[ember.js](https://github.com/emberjs/ember.js) to provide flexibility and\ncontrol in your application's data layer.\n\n## Highlights\n\n- Access to the full power of Orbit and its ecosystem, including compatible\n  sources, buckets, and coordination strategies.\n\n- A data schema that's declared through simple model definitions.\n\n- Stores that wrap Orbit sources and provide access to their underlying data as\n  easy to use records and record arrays. These stores can be forked, edited in\n  isolation, and merged back to the original as a coalesced changeset.\n\n- Live-updating filtered query results and model relationships.\n\n- The full power of Orbit's composable query expressions.\n\n- The ability to connect any number of sources together with Orbit coordination\n  strategies.\n\n- Orbit's git-like deterministic change tracking and history manipulation\n  capabilities.\n\n## Relationship to Orbit\n\nEO provides a thin \"Emberified\" layer over the top of some core\nprimitives from Orbit, including `Store`, `Cache`, and `Model` classes. Most\ncommon developer interactions with Orbit will be through these classes.\n\nEO does not attempt to wrap _every_ base class from Orbit. For instance, you'll\nneed to use Orbit's `Coordinator` and coordination strategies to define\nrelationships between Orbit sources. In this way, you can install any Orbit\n`Source` or `Bucket` library and wire them together in your EO application.\n\n\u003e **Important**: It's strongly recommended that you read the Orbit guides at\n\u003e [orbitjs.com](https://orbitjs.com) before using EO, since an understanding of\n\u003e Orbit is vital to making the most of EO.\n\n## Compatibility\n\n- Ember.js v3.24 or above\n- Ember CLI v3.24 or above\n- Node.js v12 or above\n\n## Status\n\nEO obeys [semver](http://semver.org) and thus should not be considered to have a\nstable API until 1.0. Until then, any breaking changes in APIs or Orbit\ndependencies should be accompanied by a minor version bump of EO.\n\n## Demo\n\n[todomvc-ember-orbit](https://github.com/orbitjs/todomvc-ember-orbit) is a\nsimple TodoMVC example that uses EO to illustrate a number of possible\nconfigurations and application patterns.\n\n## Installation\n\nInstall EO in your project with:\n\n```\nember install ember-orbit\n```\n\nThe generators for orbit sources and buckets will attempt to install any\nadditional orbit-related dependencies.\n\n## Usage\n\nEO creates the following directories by default:\n\n- `app/data-buckets` - Factories for creating Orbit `Bucket`s, which are used\n  to save / load orbit application state.\n\n- `app/data-models` - For EO `Model` classes, which represent data records.\n\n- `app/data-sources` - Factories for creating Orbit `Source`s, which represent\n  different sources of data.\n\n- `app/data-strategies` - Factories for creating Orbit coordination strategies.\n\nNote that \"factories\" are simply objects with a `create` method that serves to\ninstantiate an object. The factory interface conforms with the expectations of\nEmber's DI system.\n\nEO installs the following services by default:\n\n- `store` - An `ember-orbit` `Store` to manage querying and updating models.\n\n- `dataCoordinator` - An `@orbit/coordinator` `Coordinator` that manages sources\n  and coordination strategies between them.\n\n- `dataSchema` - An `@orbit/data` `Schema` that represents a schema for models\n  that is shared by the `store` and other sources.\n\n- `dataKeyMap` - An `@orbit/data` `KeyMap` that manages a mapping between keys\n  and local IDs for scenarios in which a server does not accept client-generated\n  IDs.\n\nAll the directories and services configured by EO can be customized for your\napp, as described in the \"Customizing EO\" section below.\n\n### Defining models\n\nModels are used to access the underlying data in an EO `Store`.\nThey provide a proxy to get and set attributes and relationships. In addition,\nmodels are used to define the schema that's shared by the sources in your\nOrbit application.\n\nThe easiest way to create a `Model` class is with the `data-model` generator:\n\n```\nember g data-model planet\n```\n\nThis will create the following module in `app/data-models/planet.js`:\n\n```js\nimport { Model } from 'ember-orbit';\n\nexport default class Planet extends Model {}\n```\n\nYou can then extend your model to include keys, attributes, and relationships:\n\n```js\nimport { Model, attr, hasOne, hasMany, key } from 'ember-orbit';\n\nexport default class Planet extends Model {\n  @key() remoteId;\n  @attr('string') name;\n  @hasMany('moon', { inverse: 'planet' }) moons;\n  @hasOne('star') sun;\n}\n```\n\nYou can create polymorphic relationships by passing in an array of types:\n\n```js\nimport { Model, attr, hasOne, hasMany } from 'ember-orbit';\n\nexport default class PlanetarySystem extends Model {\n  @attr('string') name;\n  @hasMany(['moon', 'planet']) bodies;\n  @hasOne(['star', 'binaryStar']) star;\n}\n```\n\n### Stores and Caches\n\nEO's `Store` class is a thin wrapper around Orbit's\n[`MemorySource`](https://orbitjs.com/docs/api/memory/classes/MemorySource),\nwhile EO's `Cache` class wraps Orbit's\n[`MemoryCache`](https://orbitjs.com/docs/api/memory/classes/MemoryCache).\nThe difference between memory sources and caches is [explained extensively in\nOrbit's docs](https://orbitjs.com/docs/memory-sources).\n\nThe essential difference between EO's `Store` and `Cache` and the underlying\nOrbit classes is that EO is model-aware. Unlike plain Orbit, in which results\nare returned as static POJOS, every query and update result in EO is translated\ninto `Model` instances, or simply \"records\". When changes occur to the\nunderlying Orbit sources and caches, they will be reflected immediately in EO's\nrecords.\n\nEvery EO record is connected to a cache, which in turn belongs to a store. When\nstores or caches provide results in the form of records, they are always\ninstantiated by, and belong to, a `Cache`. For a given identity (`type` / `id`\npair), there is only ever one record instance per cache. These are maintained in\nwhat is fittingly called an [\"identity\nmap\"](https://orbitjs.com/docs/api/identity-map).\n\nRecords, including all their attributes and relationships, will stay in sync\nwith the underlying data in their associated cache.\n\n### Querying Data\n\nThere are three primary methods available to query records:\n\n- `store.query()` - returns a promise that resolves to a static recordset.\n\n- `store.cache.query()` - returns a static set of in-memory results immediately.\n\n- `store.cache.liveQuery()` - returns a live recordset that will be refreshed\n  whenever the data changes in the cache.\n\nAll of these query methods take the same arguments as any other queryable Orbit\nsource - see [the Orbit guides](https://orbitjs.com/docs/querying-data) for\ndetails.\n\nThe following `liveQuery` immediately returns a live resultset that will stay\nupdated with the \"terrestrial\" planets in the store:\n\n```javascript\nlet planets = store.cache.liveQuery((qb) =\u003e\n  qb\n    .findRecords('planet')\n    .filter({ attribute: 'classification', value: 'terrestrial' })\n);\n```\n\nThe EO `Store` also supports `findRecord` and `findRecords` methods. These\nmethods are async and call `query` internally:\n\n```javascript\n// find all records of a type\nlet planets = await store.findRecords('planet');\n\n// find a specific record by type and id\nlet planet = await store.findRecord('planet', 'abc123');\n```\n\nThese methods are also available on the EO `Cache`, but are synchronous:\n\n```javascript\n// find all records of a type\nlet planets = store.cache.findRecords('planet');\n\n// find a specific record by type and id\nlet planet = store.cache.findRecord('planet', 'abc123');\n```\n\n### Updating Data\n\nThere are two primary approaches to update data in EO:\n\n- Directly via async methods on the main `Store`. Direct updates flow\n  immediately into Orbit's [request\n  flow](https://orbitjs.com/docs/data-flows), where they can trigger side\n  effects, such as remote server requests.\n\n- In an isolated \"forked\" `Store`, usually via sync methods on its associated\n  `Cache` and/or `Model` instances. These changes remain in this fork until they\n  are merged back to a base store.\n\n#### Direct Updates to the Store\n\nThe `Store` exposes several async methods to update data:\n\n- `addRecord` - adds a single record.\n- `updateRecord` - updates the fields of a single record.\n- `updateRecordFields` - for updating the fields of a single record, with a\n  first argument that provides the identity of the record.\n- `removeRecord` - removes a single record.\n- `update` - the most flexible and powerful method, which can perform one or\n  more operations in a single request.\n\nHere are some examples of each:\n\n```javascript\n// add a new record (returned as a Model instance)\nlet planet = await store.addRecord({ type: 'planet', id: '1', name: 'Earth' });\nconsole.log(planet.name); // Earth\n\n// update one or more fields of the record\nawait store.updateRecord({ type: 'planet', id: '1', name: 'Mother Earth' });\nconsole.log(planet.name); // Mother Earth\n\n// remove the record\nawait store.removeRecord({ type: 'planet', id: '1' });\n// or alternatively: await store.removeRecord(planet);\n\n// add more planets in a single `Transform`\nlet [mars, venus] = await store.update((t) =\u003e [\n  t.addRecord({ type: 'planet', name: 'Mars' }),\n  t.addRecord({ type: 'planet', name: 'Venus' })\n]);\n```\n\n#### Updates via Forking / Merging\n\nEO stores can be forked and merged, just as described in the [Orbit\nguides](https://orbitjs.com/docs/memory-sources#forking-memory-sources).\n\nOnce you have forked a store, you can proceed to make synchronous changes to the\nfork's associated `Cache` and/or `Model` instances. These changes will be\ntracked and can then be merged back to the base store.\n\nHere's an example:\n\n```javascript\n  // (async) start by adding two planets and a moon to the store\n  await store.update(t =\u003e [\n    t.addRecord(earth),\n    t.addRecord(venus),\n    t.addRecord(theMoon)\n  ]);\n\n  // (async) query the planets in the store\n  let planets = await store.query(q =\u003e q.findRecords('planet').sort('name')));\n  console.log('original planets', planets);\n\n  // (sync) fork the store\n  forkedStore = store.fork();\n  let forkedCache = forkedStore.cache;\n\n  // (sync) add a planet and moons to the fork's cache\n  forkedCache.update(t =\u003e [\n    t.addRecord(jupiter),\n    t.addRecord(io),\n    t.addRecord(europa)\n  ]);\n\n  // (sync) query the planets in the forked cache\n  planets = forkedCache.query(q =\u003e q.findRecords('planet').sort('name')));\n  console.log('planets in fork', planets);\n\n  // (async) merge the forked store back into the original store\n  await store.merge(forkedStore);\n\n  // (async) query the planets in the original store\n  planets = await store.query(q =\u003e q.findRecords('planet').sort('name')));\n  console.log('merged planets', planets);\n```\n\nSome notes about forking / merging:\n\n- Once a store has been forked, the original and forked stores’ data can diverge\n  independently.\n\n- Merging a fork will coalesce any changes made to the forked cache into a\n  single new transform, and then update the original store.\n\n- A store fork can simply be abandoned without cost. Just remember to free any\n  references to the JS objects themselves.\n\n\u003e **Important** - One additional concern to be aware of is that EO will generate\n\u003e new records for each store. Care should be taken to not mix records between\n\u003e stores, since the underlying data in each store can diverge. If you need to\n\u003e access a record in a store's fork, just query the forked store or cache for\n\u003e that record.\n\n#### Sync Updates via the Cache\n\nThe `Cache` exposes sync versions of the `Store`'s async update methods:\n\n- `addRecord` - for adding a single record.\n- `updateRecord` - for updating the fields of a single record.\n- `removeRecord` - for removing a single record.\n- `update` - the most flexible and powerful method, which can perform one or\n  more operations in a single request.\n\nBy default, only forked caches are able to be updated directly. This provides\nprotection against data loss, since changes to caches do not participate in\nOrbit's [data flows](https://orbitjs.com/docs/data-flows). An exception is\nmade for forks because the changes are tracked and applied back to stores via\n`merge`.\n\nIf you want to override these protections and update a non-forked cache, you can\nset `cache.allowUpdates = true`, but know that those updates won't leave the\ncache.\n\n#### Sync Updates via Model instances\n\nEach `Model` exposes all of its fields, including attributes and relationships,\nas properties that stay updated.\n\nAttributes and has-one relationships are also directly editable. For instance:\n\n```javascript\nlet jupiter = forkedCache.findRecord('planet', 'jupiter');\nlet sun = forkedCache.findRecord('star', 'theSun');\n\nconsole.log(jupiter.name); // 'Jupiter'\n\n// update attribute\njupiter.name = 'Jupiter!';\nconsole.log(jupiter.name); // 'Jupiter!'\n\n// update has-one relationship\njupiter.sun = theSun;\n```\n\nIn order to not conflict with user-defined fields, all standard methods on\n`Model` are prefixed with a `$`. The following synchronous methods are\navailable:\n\n- `$replaceAttribute`\n- `$replaceRelatedRecord`\n- `$replaceRelatedRecords`\n- `$addToRelatedRecords`\n- `$removeFromRelatedRecords`\n- `$update`\n- `$remove`\n\n```javascript\nlet jupiter = forkedCache.findRecord('planet', 'jupiter');\nlet io = forkedCache.findRecord('moon', 'io');\nlet europa = forkedCache.findRecord('moon', 'europa');\nlet sun = forkedCache.findRecord('star', 'theSun');\n\njupiter.$replaceAttribute('name', 'JUPITER!');\njupiter.$addToRelatedRecords('moons', io);\njupiter.$removeFromRelatedRecords('moons', europa);\njupiter.$replaceRelatedRecord('sun', sun);\n\nconsole.log(jupiter.name); // 'JUPITER!'\nconsole.log(jupiter.moons.includes(io)); // true\nconsole.log(jupiter.moons.includes(europa)); // false\nconsole.log(jupiter.sun.id); // 'theSun'\n```\n\nBehind the scenes, these changes each result in a call to `forkedCache.update`.\nOf course, this method could also be called directly instead of issuing updates\nthrough the model:\n\n```javascript\nforkedCache.update((t) =\u003e [\n  t.replaceAttribute(jupiter, 'name', 'JUPITER!');\n  t.addToRelatedRecords(jupiter, 'moons', io);\n  t.removeFromRelatedRecords(jupiter, 'moons', europa);\n  t.replaceRelatedRecord(jupiter, 'sun', sun);\n]);\n```\n\n### Adding a \"backup\" source\n\nThe store's contents exist only in memory and will be cleared every time your\napp restarts. Let's try adding another source and use the `dataCoordinator`\nservice to keep it in sync with the store.\n\nWe can use the `data-source` generator to create a `backup` source:\n\n```\nember g data-source backup --from=@orbit/indexeddb\n```\n\nThis will generate a source factory in `app/data-sources/backup.js`:\n\n```javascript\nimport SourceClass from '@orbit/indexeddb';\nimport { applyStandardSourceInjections } from 'ember-orbit';\n\nexport default {\n  create(injections = {}) {\n    applyStandardSourceInjections(injections);\n    injections.name = 'backup';\n    return new SourceClass(injections);\n  }\n};\n```\n\nNote that `injections` should include both a `Schema` and a `KeyMap`, which\nare injected by default for every EO application. We're also adding\na `name` to uniquely identify the source within the coordinator. You could\noptionally specify a `namespace` to be used to name the IndexedDB database.\n\nEvery source that's defined in `app/data-sources` will be discovered\nautomatically by EO and added to the `dataCoordinator` service.\n\nNext let's define some strategies to synchronize data between sources.\n\n### Defining coordination strategies\n\nThere are four different types of coordination strategies that can be generated\nby default using the standard `data-strategy` generator:\n\n- `request`\n- `sync`\n- `event-logging`\n- `log-truncation`\n\nLet's define a sync strategy to backup changes made to the store into our new\n`backup` source.\n\n```\nember g data-strategy store-backup-sync --type=sync\n```\n\nThis should create a `SyncStrategy` factory in\n`app/data-strategies/store-backup-sync.js` as follows:\n\n```js\nimport { SyncStrategy } from '@orbit/coordinator';\n\nexport default {\n  create() {\n    return new SyncStrategy({\n      name: 'store-backup-sync',\n\n      /**\n       * The name of the source which will have its `transform` event observed.\n       */\n      source: 'store',\n\n      /**\n       * The name of the source which will be acted upon.\n       *\n       * When the source receives the `transform` event, the `sync` method\n       * will be invoked on the target.\n       */\n      target: 'backup',\n\n      /**\n       * A handler for any errors thrown as a result of invoking `sync` on the\n       * target.\n       */\n      // catch(e) {},\n\n      /**\n       * A filter function that returns `true` if `sync` should be performed.\n       *\n       * `filter` will be invoked in the context of this strategy (and thus will\n       * have access to both `this.source` and `this.target`).\n       */\n      // filter(...args) {};\n\n      /**\n       * Should resolution of the target's `sync` block the completion of the\n       * source's `transform`?\n       *\n       * Can be specified as a boolean or a function which which will be\n       * invoked in the context of this strategy (and thus will have access to\n       * both `this.source` and `this.target`).\n       */\n      blocking: true\n    });\n  }\n};\n```\n\nYou should also consider adding an event logging strategy to log events emitted\nfrom your sources to the browser console:\n\n```\nember g data-strategy event-logging\n```\n\nSources have another kind of log as well: a transform log, which tracks\ntransforms that are applied. A log truncation strategy will keep the size of\ntransform logs in check. It observes the sources associated with the strategy\nand truncates their transform logs when a common transform has been applied\nto them all. Let's add a log truncation strategy as well:\n\n```\nember g data-strategy log-truncation\n```\n\n### Activating the coordinator\n\nNext we'll need to activate our coordinator as part of our app's boot process.\nThe coordinator requires an explicit activation step because the process is\nasync _and_ we may want to allow developers to do work beforehand.\n\nIn our case, we want to restore our store from the backup source before we\nenable the coordinator. Let's do this in our application route's `beforeModel`\nhook (in `app/routes/application.js`):\n\n```js\nimport Route from '@ember/routing/route';\nimport { inject as service } from '@ember/service';\n\nexport default class ApplicationRoute extends Route {\n  @service dataCoordinator;\n  @service store;\n\n  async beforeModel() {\n    // Populate the store from backup prior to activating the coordinator\n    const backup = this.dataCoordinator.getSource('backup');\n    const records = await backup.query((q) =\u003e q.findRecords());\n    await this.store.sync((t) =\u003e records.map((r) =\u003e t.addRecord(r)));\n\n    await this.dataCoordinator.activate();\n  }\n}\n```\n\nThis code first pulls all the records from backup and then syncs them\nwith the main store _before_ activating the coordinator. In this way, the\ncoordination strategy that backs up the store won't be enabled until after\nthe restore is complete.\n\n### Defining a data bucket\n\nData buckets are used by sources and key maps to load and persist state. You\nwill probably want to use a bucket if you plan to support any offline or\noptimistic UX.\n\nTo create a new bucket, run the generator:\n\n```\nember g data-bucket main\n```\n\nBy default this will create a new bucket factory based on `@orbit/indexeddb-bucket`.\nIt will also create an initializer that injects this bucket into all your\nsources and key maps.\n\n### Customizing EO\n\nThe types, collections, and services used by EO can all be customized for\nyour application via settings under the `orbit` key in `config/environment`:\n\n```js\nmodule.exports = function (environment) {\n  let ENV = {\n    // ... other settings here\n\n    // Default Orbit settings (any of which can be overridden)\n    orbit: {\n      schemaVersion: undefined,\n      types: {\n        bucket: 'data-bucket',\n        model: 'data-model',\n        source: 'data-source',\n        strategy: 'data-strategy'\n      },\n      collections: {\n        buckets: 'data-buckets',\n        models: 'data-models',\n        sources: 'data-sources',\n        strategies: 'data-strategies'\n      },\n      services: {\n        store: 'store',\n        bucket: 'data-bucket',\n        coordinator: 'data-coordinator',\n        schema: 'data-schema',\n        keyMap: 'data-key-map',\n        normalizer: 'data-normalizer',\n        validator: 'data-validator'\n      },\n      skipStoreService: false,\n      skipBucketService: false,\n      skipCoordinatorService: false,\n      skipSchemaService: false,\n      skipKeyMapService: false,\n      skipNormalizerService: false,\n      skipValidatorService: false\n    }\n  };\n\n  return ENV;\n};\n```\n\nNote that `schemaVersion` should be set if you're using any Orbit sources, such\nas `IndexedDBSource`, that track schema version. By default, Orbit's schema\nversion will start at `1`. This value should be bumped to a a higher number with\neach significant change that requires a schema migration. Migrations themselves\nmust be handled in each individual source.\n\n### Conditionally include strategies and sources\n\nSources and strategies may be conditionally included in your app's coordinator\nby customizing the default export of the source / strategy factory. A valid\nfactory is an object with the interface `{ create: () =\u003e {} }`. If a valid\nfactory is not the default export for your module, it will be ignored.\n\nFor example, the following strategy will be conditionally included for all\nnon-production builds:\n\n```js\n// app/data-strategies/event-logging.js\n\nimport { EventLoggingStrategy } from '@orbit/coordinator';\nimport config from 'example/config/environment';\n\nconst factory = {\n  create() {\n    return new EventLoggingStrategy();\n  }\n};\n\n// Conditionally include this strategy\nexport default config.environment !== 'production' ? factory : null;\n```\n\n#### Customizing validators\n\nLike Orbit itself, EO enables validators by default in all sources. EO provides\nthe same set of validators to all sources by building a single `data-validator`\nservice that is injected into all sources.\n\nValidators are useful to ensure that your data matches its type expectations and\nthat operations and query expressions are well formed. Of course, they also add\nsome extra code and processing, which you may want to eliminate (or perhaps only\nfor production environments). You can disable validators across all sources by\nsetting Orbit's `skipValidatorService` environment flag to `false` in\n`config/environment`, as described above.\n\nIf you want to use validators but extend them to include custom validators, you\ncan override the standard validator service by generating your own\n`data-validator` service that passes custom arguments to\n[`buildRecordValidatorFor`](https://orbitjs.com/docs/api/records/modules#buildrecordvalidatorfor).\n\nFor instance, in order to provide a custom validator for an `address` type:\n\n```js\n// app/services/data-validator.js\n\nimport { buildRecordValidatorFor } from '@orbit/records';\n\nconst validators = {\n  address: (input) =\u003e {\n    if (typeof input?.country !== 'string') {\n      return [\n        {\n          validator: 'address',\n          validation: 'country',\n          description: 'is not a string',\n          ref: input,\n        },\n      ];\n    }\n  },\n};\n\nexport default {\n  create() {\n    return buildRecordValidatorFor({ validators });\n  },\n};\n```\n\nThis custom validator service will be injected into all your orbit sources via\n`applyStandardSourceInjections`, as described above.\n\n## Contributing to EO\n\n### Installation\n\n- `git clone https://github.com/orbitjs/ember-orbit.git`\n- `cd ember-orbit`\n- `yarn install`\n\n### Running Tests\n\n- `yarn test`\n\n## Acknowledgments\n\nEO owes a great deal to [Ember Data](https://github.com/emberjs/data),\nwhich has influenced the design of many of EO's interfaces. Many thanks\nto the Ember Data Core Team, including Yehuda Katz, Tom Dale, and Igor Terzic,\nfor their work.\n\nIt is hoped that, by tracking Ember Data's features and interfaces where\npossible, EO will also be able to contribute back to Ember Data.\n\n## License\n\nCopyright 2014-2021 Cerebris Corporation. MIT License (see LICENSE for details).\n","funding_links":[],"categories":["Packages"],"sub_categories":["Data Management"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forbitjs%2Fember-orbit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Forbitjs%2Fember-orbit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Forbitjs%2Fember-orbit/lists"}