[
  {
    "path": ".babelrc.js",
    "content": "const { NODE_ENV, BABEL_ENV } = process.env;\n\nconst cjs = BABEL_ENV === 'cjs' || NODE_ENV === 'test';\n\nmodule.exports = {\n  presets: [['@babel/preset-env', { loose: true }]],\n  plugins: [\n    // cjs && 'transform-es2015-modules-commonjs',\n    ['@babel/plugin-proposal-object-rest-spread', { loose: true }],\n    ['@babel/plugin-proposal-class-properties', { loose: true }]\n  ].filter(Boolean)\n};\n"
  },
  {
    "path": ".eslintignore",
    "content": "dist/*\nnode_modules/*\nexamples/*/node_modules/*\ncoverage/*\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  // babel parser to support ES6/7 features\n  parser: 'babel-eslint',\n  parserOptions: {\n    ecmaVersion: 7,\n    ecmaFeatures: {\n      experimentalObjectRestSpread: true,\n      jsx: true,\n    },\n    sourceType: 'module',\n  },\n  extends: ['plugin:jest/recommended', 'prettier'],\n  plugins: ['json', 'prettier'],\n  env: {\n    es6: true,\n    node: true,\n  },\n  globals: {\n    document: false,\n    navigator: false,\n    window: false,\n  },\n  rules: {\n    'accessor-pairs': 'error',\n    camelcase: 'off',\n    'constructor-super': 'error',\n    curly: ['error', 'all'],\n    'default-case': ['error', { commentPattern: '^no default$' }],\n    eqeqeq: ['error', 'allow-null'],\n    'handle-callback-err': ['error', '^(err|error)$'],\n    'new-cap': ['error', { newIsCap: true, capIsNew: false }],\n    'no-alert': 'warn',\n    'no-array-constructor': 'error',\n    'no-caller': 'error',\n    'no-case-declarations': 'error',\n    'no-class-assign': 'error',\n    'no-compare-neg-zero': 'error',\n    'no-cond-assign': 'error',\n    'no-console': ['error', { allow: ['warn', 'error'] }],\n    'no-const-assign': 'error',\n    'no-control-regex': 'error',\n    'no-debugger': 'error',\n    'no-delete-var': 'error',\n    'no-dupe-args': 'error',\n    'no-dupe-class-members': 'error',\n    'no-dupe-keys': 'error',\n    'no-duplicate-case': 'error',\n    'no-empty-character-class': 'error',\n    'no-empty-pattern': 'error',\n    'no-eval': 'error',\n    'no-ex-assign': 'error',\n    'no-extend-native': 'error',\n    'no-extra-bind': 'error',\n    'no-extra-boolean-cast': 'error',\n    'no-fallthrough': 'error',\n    'no-func-assign': 'error',\n    'no-implied-eval': 'error',\n    'no-inner-declarations': ['error', 'functions'],\n    'no-invalid-regexp': 'error',\n    'no-iterator': 'error',\n    'no-label-var': 'error',\n    'no-labels': ['error', { allowLoop: false, allowSwitch: false }],\n    'no-lone-blocks': 'error',\n    'no-loop-func': 'error',\n    'no-multi-str': 'error',\n    'no-native-reassign': 'error',\n    'no-negated-in-lhs': 'error',\n    'no-new': 'error',\n    'no-new-func': 'error',\n    'no-new-object': 'error',\n    'no-new-require': 'error',\n    'no-new-symbol': 'error',\n    'no-new-wrappers': 'error',\n    'no-obj-calls': 'error',\n    'no-octal': 'error',\n    'no-octal-escape': 'error',\n    'no-path-concat': 'error',\n    'no-proto': 'error',\n    'no-redeclare': 'error',\n    'no-regex-spaces': 'error',\n    'no-return-assign': ['error', 'except-parens'],\n    'no-script-url': 'error',\n    'no-self-assign': 'error',\n    'no-self-compare': 'error',\n    'no-sequences': 'error',\n    'no-shadow-restricted-names': 'error',\n    'no-sparse-arrays': 'error',\n    'no-this-before-super': 'error',\n    'no-throw-literal': 'error',\n    'no-undef': 'error',\n    'no-undef-init': 'error',\n    'no-unexpected-multiline': 'error',\n    'no-unmodified-loop-condition': 'error',\n    'no-unneeded-ternary': ['error', { defaultAssignment: false }],\n    'no-unreachable': 'error',\n    'no-unsafe-finally': 'error',\n    'no-unused-vars': ['error', { vars: 'all', args: 'none' }],\n    'no-useless-call': 'error',\n    'no-useless-computed-key': 'error',\n    'no-useless-concat': 'error',\n    'no-useless-constructor': 'error',\n    'no-useless-escape': 'error',\n    'no-var': 'error',\n    'no-with': 'error',\n    'prefer-const': 'error',\n    'prefer-rest-params': 'error',\n    'prefer-template': 'error',\n    radix: 'error',\n    'require-yield': 'error',\n    'sort-imports': ['error', { memberSyntaxSortOrder: ['none', 'all', 'single', 'multiple'], ignoreCase: true }],\n    'spaced-comment': [\n      'error',\n      'always',\n      { markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] },\n    ],\n    'use-isnan': 'error',\n    'valid-typeof': 'error',\n    yoda: ['error', 'never'],\n\n    'prettier/prettier': 'error',\n\n    'jest/consistent-test-it': ['error', { fn: 'test' }],\n    'jest/no-disabled-tests': 'error',\n    'jest/no-test-prefixes': 'error',\n    'jest/prefer-to-be-null': 'error',\n    'jest/prefer-to-be-undefined': 'error',\n    'jest/prefer-to-have-length': 'error',\n    'jest/valid-describe': 'error',\n    'jest/valid-expect': 'error',\n    'jest/valid-expect-in-promise': 'error',\n  },\n};\n"
  },
  {
    "path": ".flowconfig",
    "content": ""
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: \n  - paularmstrong\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.md",
    "content": "---\nname: 🐛 Bug Report\nabout: If something isn't working as expected 🤔.\n---\n\n# Problem\n\nA short explanation of your problem or use-case is helpful!\n\n<!-- \nInstead of the below, please also consider\nforking the following Codesandbox: https://codesandbox.io/s/normalizr-test-case-0yfcf\nPaste your link here when done.\n-->\n\n**Input**\n\nHere's how I'm using normalizr:\n\n```js\n// Add as much relevant code and input as possible.\nconst myData = {\n  // This section is really helpful! A minimum test case goes a long way!\n};\nconst mySchema = new schema.Entity('myschema');\nnormalize(myData, mySchema);\n```\n\n**Output**\n\nHere's what I expect to see when I run the above:\n\n```js\n{\n    result: [1, 2],\n    entities: { ... }\n}\n```\n\nHere's what I _actually_ see when I run the above:\n\n```js\n{\n    result: [1, 2],\n    entities: { ... }\n}\n```\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: 🚀 Feature Request\nabout: I have a suggestion (and I might like to implement it myself 😀)!\n---\n\n<!--\nWhen trying to solve more solutions with Normalizr, please keep in mind some of the following goals of the package:\n* Be lightweight: small package size (single-digit KiBs, gzipped)\n* Be easy: too many options in an API can become confusing\n* Be clear: the intended purpose of every method should be as obvious as possible\n* Is it easy to do this in \"userland\"? Would it be better off done there?\n-->\n\n# Problem\n\nExplain the problem you'd like to have Normalizr handle.\n\n# Solution\n\nExplain a possible way to solve the problem. Keep in mind that there may be alternate ways to do the same thing. Try to think of those and weight the tradeoffs.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/support_question.md",
    "content": "---\nname: 🤗 Support or Question\nabout: If you have a question 💬, please check for help on StackOverflow!\n---\n\nIssues on GitHub are intended to be related to problems with Normalizr itself. You are not likely to receive support with how to use it here 😁.\n\n---\n\nIf you have a support request or question please submit them to one of this resources:\n\n* StackOverflow: https://stackoverflow.com/questions/tagged/normalizr using the tag `normalizr`\n* Also have a look at the docs for more information on how to get do many things: https://github.com/paularmstrong/normalizr/tree/master/docs\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\nThank you so much for contributing to open source and the Normalizr project!\n-->\n# Problem\n\nExplain the problem that this pull request aims to resolve.\n\n# Solution\n\nExplain your approach. Sometimes it helps to justify your approach against some others that you didn't choose to explain why yours is better.\n\n# TODO\n\n- [ ] Add & update tests\n- [ ] Ensure CI is passing (lint, tests, flow)\n- [ ] Update relevant documentation\n"
  },
  {
    "path": ".github/lock.yml",
    "content": "# Configuration for lock-threads - https://github.com/dessant/lock-threads\n\n# Number of days of inactivity before a closed issue or pull request is locked\ndaysUntilLock: 182\n\n# Issues and pull requests with these labels will not be locked. Set to `[]` to disable\nexemptLabels: []\n\n# Label to add before locking, such as `outdated`. Set to `false` to disable\nlockLabel: Outdated\n\n# Comment to post before locking. Set to `false` to disable\nlockComment: >\n  This thread has been automatically locked since there has not been\n  any recent activity after it was closed. Please open a new issue for\n  related bugs.\n"
  },
  {
    "path": ".github/workflows/close-pull-request.yml",
    "content": "name: Close Pull Request\n\non:\n  pull_request_target:\n    types: [opened]\n\njobs:\n  run:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: superbrothers/close-pull-request@v3\n        with:\n          comment: 'Normalizr is no longer maintained and does not accept pull requests. Please maintain your own fork of this repository.'\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n\nnode_modules/*\ndist/*\n*.log\ncoverage/*\n.coveralls.yml\n.eslintcache\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - '10'\n  - '12'\n  - '14'\nscript:\n  - npm run lint:ci\n  - npm run test:ci\n  - npm run build\n  - npm run typecheck\nafter_success:\n  - npm run test:coverage\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# v3.6.1\n\n- **Fixed** Add types for fallback strategy\n- **Chore** Upgraded development dependencies\n\n# v3.6.0\n\n- **Added** `fallbackStrategy` for denormalization (#422)\n- **Fixed** entities can be `undefined` in TS defs if none found (#435)\n\n# v3.5.0\n\n- **Added** ability to dynamically set nested schema type (#415)\n- **Changed** Enable loose transformation for object spread operator to improve performance (#431)\n- **Fixed** don't use schema to attribute mapping on singular array schemas (#387)\n- **Fixed** When normalize() receives null input, don't say it is an object (#411)\n- **Fixed** Improve performance of circular reference detection (#420)\n\n# v3.4.0\n\n- **Changed** Now built with Babel 7\n- **Added** Support for circular references (gh-335)\n- **Added** Symbols are valid keys for Entity keys (gh-369)\n- **Added/Changed** Typescript definitions include generics for `normalize` (gh-363)\n- **Fixed** denormalization skipping of falsy valued ids used in `Object` schemas (gh-345)\n- **Chore** Update dev dependencies\n- **Chore** Added Greenkeeper\n\n# v3.3.0\n\n- **Added** ES Module builds\n- **Fixed** type error with typescript on array+object shorthand (gh-322)\n\n# v3.2.0\n\n- **Added** Support denormalizing from Immutable entities (gh-228)\n- **Added** Brought back `get idAttribute()` to `schema.Entity` (gh-226)\n- **Fixed** Gracefully handle missing data in `denormalize` (gh-232)\n- **Fixed** Prevent infinite recursion in `denormalize` (gh-220)\n\n# v3.1.0\n\n- **Added** `denormalize`. (gh-214)\n- **Changed** No longer requires all input in a polymorphic schema (`Array`, `Union`, `Values`) have a matching schema definition. (gh-208)\n- **Changed** Builds do both rollup and plain babel file conversions. `\"main\"` property in package.json points to babel-converted files.\n\n# v3.0.0\n\nThe entire normalizr package has been rewritten from v2.x for this version. Please refer to the [documentation](/docs) for all changes.\n\n## Added\n\n- `schema.Entity`\n  - `processStrategy` for modifying `Entity` objects before they're moved to the `entities` stack.\n  - `mergeStrategy` for merging with multiple entities with the same ID.\n- Added `schema.Object`, with a shorthand of `{}`\n- Added `schema.Array`, with a shorthand of `[ schema ]`\n\n## Changed\n\n- `Schema` has been moved to a `schema` namespace, available at `schema.Entity`\n- `arrayOf` has been replaced by `schema.Array` or `[]`\n- `unionOf` has been replaced by `schema.Union`\n- `valuesOf` has been replaced by `schema.Values`\n\n## Removed\n\n- `normalize` no longer accepts an optional `options` argument. All options are assigned at the schema level.\n- Entity schema no longer accepts `defaults` as an option. Use a custom `processStrategy` option to apply defaults as needed.\n- `assignEntity` has been replaced by `processStrategy`\n- `meta` option. See `processStrategy`\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\n## Issues\n\n1. Follow the [Issue Template](/.github/ISSUE_TEMPLATE.md) provided when opening a new Issue.\n2. Provide a minimal, reproducible test-case.\n3. Do not ask for help or usage questions in Issues. Use [StackOverflow](http://stackoverflow.com/questions/tagged/normalizr) for those.\n\n## Pull Requests\n\nFirst, thank you so much for contributing to open source and the Normalizr project!\n\nFollow the instructions on the Pull Request Template (shown when you open a new PR) and make sure you've done the following:\n\n- [ ] Add & update tests\n- [ ] Ensure CI is passing (lint, tests)\n- [ ] Update relevant documentation\n\n## Setup\n\nNormalizr uses [yarn](https://yarnpkg.com) for development dependency management. Ensure you have it installed before continuing.\n\n```sh\nyarn\n```\n\n## Running Tests\n\n```sh\nnpm run test\n```\n\n## Lint\n\nStandard code style is nice. ESLint is used to ensure we continue to write similar code. The following command will also fix simple issues, like spacing and alphabetized imports:\n\n```sh\nnpm run lint\n```\n\n## Building\n\nNormalizr aims to keep its byte-size as low as possible. Ensure your changes don't incur more space than seems necessary for your feature or change:\n\n```sh\nnpm run build\n```\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Dan Abramov, Paul Armstrong\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# normalizr [![build status](https://img.shields.io/travis/paularmstrong/normalizr/master.svg?style=flat-square)](https://travis-ci.org/paularmstrong/normalizr) [![Coverage Status](https://img.shields.io/coveralls/paularmstrong/normalizr/master.svg?style=flat-square)](https://coveralls.io/github/paularmstrong/normalizr?branch=master) [![npm version](https://img.shields.io/npm/v/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/normalizr) [![npm downloads](https://img.shields.io/npm/dm/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/normalizr)\n\n# 📣 Normalizr is no longer maintained\n\nDue to lack of ability to find an invested maintainer and inability to find time to do routine maintenance and community building, this package is no longer maintained. Please see the discussion [🤝 Maintainer help wanted](https://github.com/paularmstrong/normalizr/discussions/493) for more information.\n\n## FAQs\n\n### Should I still use Normalizr?\n\nIf you need it, yes. Normalizr is and has been at a stable release for a very long time, used by thousands of others without issue.\n\n### What should I do if I want other features or found a bug?\n\nFork [Normalizr on Github](https://github.com/paularmstrong/normalizr) and maintain a version yourself.\n\n### Can I contribute back to Normalizr?\n\nThere are no current plans to resurrect this origin of Normalizr. If a forked version becomes sufficiently maintained and popular, please reach out about merging the fork and changing maintainers.\n"
  },
  {
    "path": "docs/README.md",
    "content": "# normalizr [![build status](https://img.shields.io/travis/paularmstrong/normalizr/master.svg?style=flat-square)](https://travis-ci.org/paularmstrong/normalizr) [![Coverage Status](https://img.shields.io/coveralls/paularmstrong/normalizr/master.svg?style=flat-square)](https://coveralls.io/github/paularmstrong/normalizr?branch=master) [![npm version](https://img.shields.io/npm/v/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/normalizr) [![npm downloads](https://img.shields.io/npm/dm/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/normalizr)\n\n## Install\n\nInstall from the NPM repository using yarn or npm:\n\n```shell\nyarn add normalizr\n```\n\n```shell\nnpm install normalizr\n```\n\n## Motivation\n\nMany APIs, public or not, return JSON data that has deeply nested objects. Using data in this kind of structure is often very difficult for JavaScript applications, especially those using [Flux](http://facebook.github.io/flux/) or [Redux](http://redux.js.org/).\n\n## Solution\n\nNormalizr is a small, but powerful utility for taking JSON with a schema definition and returning nested entities with their IDs, gathered in dictionaries.\n\n## Documentation\n\n- [Introduction](/docs/introduction.md)\n  - [Build Files](/docs/introduction.md#build-files)\n- [Quick Start](/docs/quickstart.md)\n- [API](/docs/api.md)\n  - [normalize](/docs/api.md#normalizedata-schema)\n  - [denormalize](/docs/api.md#denormalizeinput-schema-entities)\n  - [schema](/docs/api.md#schema)\n- [Using with JSONAPI](/docs/jsonapi.md)\n\n## Examples\n\n- [Normalizing GitHub Issues](/examples/github)\n- [Relational Data](/examples/relationships)\n- [Interactive Redux](/examples/redux)\n\n## Quick Start\n\nConsider a typical blog post. The API response for a single post might look something like this:\n\n```json\n{\n  \"id\": \"123\",\n  \"author\": {\n    \"id\": \"1\",\n    \"name\": \"Paul\"\n  },\n  \"title\": \"My awesome blog post\",\n  \"comments\": [\n    {\n      \"id\": \"324\",\n      \"commenter\": {\n        \"id\": \"2\",\n        \"name\": \"Nicole\"\n      }\n    }\n  ]\n}\n```\n\nWe have two nested entity types within our `article`: `users` and `comments`. Using various `schema`, we can normalize all three entity types down:\n\n```js\nimport { normalize, schema } from 'normalizr';\n\n// Define a users schema\nconst user = new schema.Entity('users');\n\n// Define your comments schema\nconst comment = new schema.Entity('comments', {\n  commenter: user,\n});\n\n// Define your article\nconst article = new schema.Entity('articles', {\n  author: user,\n  comments: [comment],\n});\n\nconst normalizedData = normalize(originalData, article);\n```\n\nNow, `normalizedData` will be:\n\n```js\n{\n  result: \"123\",\n  entities: {\n    \"articles\": {\n      \"123\": {\n        id: \"123\",\n        author: \"1\",\n        title: \"My awesome blog post\",\n        comments: [ \"324\" ]\n      }\n    },\n    \"users\": {\n      \"1\": { \"id\": \"1\", \"name\": \"Paul\" },\n      \"2\": { \"id\": \"2\", \"name\": \"Nicole\" }\n    },\n    \"comments\": {\n      \"324\": { id: \"324\", \"commenter\": \"2\" }\n    }\n  }\n}\n```\n\n## Dependencies\n\nNone.\n\n## Credits\n\nNormalizr was originally created by [Dan Abramov](http://github.com/gaearon) and inspired by a conversation with [Jing Chen](https://twitter.com/jingc). Since v3, it was completely rewritten and maintained by [Paul Armstrong](https://twitter.com/paularmstrong). It has also received much help, enthusiasm, and contributions from [community members](https://github.com/paularmstrong/normalizr/graphs/contributors).\n"
  },
  {
    "path": "docs/api.md",
    "content": "# API\n\n- [normalize](#normalizedata-schema)\n- [denormalize](#denormalizeinput-schema-entities)\n- [schema](#schema)\n  - [Array](#arraydefinition-schemaattribute)\n  - [Entity](#entitykey-definition---options--)\n  - [Object](#objectdefinition)\n  - [Union](#uniondefinition-schemaattribute)\n  - [Values](#valuesdefinition-schemaattribute)\n\n## `normalize(data, schema)`\n\nNormalizes input data per the schema definition provided.\n\n- `data`: **required** Input JSON (or plain JS object) data that needs normalization.\n- `schema`: **required** A schema definition\n\n### Usage\n\n```js\nimport { normalize, schema } from 'normalizr';\n\nconst myData = { users: [{ id: 1 }, { id: 2 }] };\nconst user = new schema.Entity('users');\nconst mySchema = { users: [user] };\nconst normalizedData = normalize(myData, mySchema);\n```\n\n### Output\n\n```js\n{\n  result: { users: [ 1, 2 ] },\n  entities: {\n    users: {\n      '1': { id: 1 },\n      '2': { id: 2 }\n    }\n  }\n}\n```\n\n## `denormalize(input, schema, entities)`\n\nDenormalizes an input based on schema and provided entities from a plain object or Immutable data. The reverse of `normalize`.\n\n_Special Note:_ Be careful with denormalization. Prematurely reverting your data to large, nested objects could cause performance impacts in React (and other) applications.\n\nIf your schema and data have recursive references, only the first instance of an entity will be given. Subsequent references will be returned as the `id` provided.\n\n- `input`: **required** The normalized result that should be _de-normalized_. Usually the same value that was given in the `result` key of the output of `normalize`.\n- `schema`: **required** A schema definition that was used to get the value for `input`.\n- `entities`: **required** An object, keyed by entity schema names that may appear in the denormalized output. Also accepts an object with Immutable data.\n\n### Usage\n\n```js\nimport { denormalize, schema } from 'normalizr';\n\nconst user = new schema.Entity('users');\nconst mySchema = { users: [user] };\nconst entities = { users: { '1': { id: 1 }, '2': { id: 2 } } };\nconst denormalizedData = denormalize({ users: [1, 2] }, mySchema, entities);\n```\n\n### Output\n\n```js\n{\n  users: [{ id: 1 }, { id: 2 }];\n}\n```\n\n## `schema`\n\n### `Array(definition, schemaAttribute)`\n\nCreates a schema to normalize an array of schemas. If the input value is an `Object` instead of an `Array`, the normalized result will be an `Array` of the `Object`'s values.\n\n_Note: The same behavior can be defined with shorthand syntax: `[ mySchema ]`_\n\n- `definition`: **required** A singular schema that this array contains _or_ a mapping of schema to attribute values.\n- `schemaAttribute`: _optional_ (required if `definition` is not a singular schema) The attribute on each entity found that defines what schema, per the definition mapping, to use when normalizing.  \n  Can be a string or a function. If given a function, accepts the following arguments:  \n   _ `value`: The input value of the entity.\n  _ `parent`: The parent object of the input array. \\* `key`: The key at which the input array appears on the parent object.\n\n#### Instance Methods\n\n- `define(definition)`: When used, the `definition` passed in will be merged with the original definition passed to the `Array` constructor. This method tends to be useful for creating circular references in schema.\n\n#### Usage\n\nTo describe a simple array of a singular entity type:\n\n```js\nconst data = [{ id: '123', name: 'Jim' }, { id: '456', name: 'Jane' }];\nconst userSchema = new schema.Entity('users');\n\nconst userListSchema = new schema.Array(userSchema);\n// or use shorthand syntax:\nconst userListSchema = [userSchema];\n\nconst normalizedData = normalize(data, userListSchema);\n```\n\n#### Output\n\n```js\n{\n  entities: {\n    users: {\n      '123': { id: '123', name: 'Jim' },\n      '456': { id: '456', name: 'Jane' }\n    }\n  },\n  result: [ '123', '456' ]\n}\n```\n\nIf your input data is an array of more than one type of entity, it is necessary to define a schema mapping.\n\n_Note: If your data returns an object that you did not provide a mapping for, the original object will be returned in the result and an entity will not be created._\n\nFor example:\n\n```js\nconst data = [{ id: 1, type: 'admin' }, { id: 2, type: 'user' }];\n\nconst userSchema = new schema.Entity('users');\nconst adminSchema = new schema.Entity('admins');\nconst myArray = new schema.Array(\n  {\n    admins: adminSchema,\n    users: userSchema\n  },\n  (input, parent, key) => `${input.type}s`\n);\n\nconst normalizedData = normalize(data, myArray);\n```\n\n#### Output\n\n```js\n{\n  entities: {\n    admins: { '1': { id: 1, type: 'admin' } },\n    users: { '2': { id: 2, type: 'user' } }\n  },\n  result: [\n    { id: 1, schema: 'admins' },\n    { id: 2, schema: 'users' }\n  ]\n}\n```\n\n### `Entity(key, definition = {}, options = {})`\n\n- `key`: **required** The key name under which all entities of this type will be listed in the normalized response. Must be a string name.\n- `definition`: A definition of the nested entities found within this entity. Defaults to empty object.  \n  You _do not_ need to define any keys in your entity other than those that hold nested entities. All other values will be copied to the normalized entity's output.\n- `options`:\n  - `idAttribute`: The attribute where unique IDs for each of this entity type can be found.  \n    Accepts either a string `key` or a function that returns the IDs `value`. Defaults to `'id'`. This function can and will be run multiple times – which means your generated ID _must_ be the same every time the function is run. Using a random number/string generator like `uuid` will cause unexpected errors.\n    As a function, accepts the following arguments, in order:\n    - `value`: The input value of the entity.\n    - `parent`: The parent object of the input array.\n    - `key`: The key at which the input array appears on the parent object.\n  - `mergeStrategy(entityA, entityB)`: Strategy to use when merging two entities with the same `id` value. Defaults to merge the more recently found entity onto the previous.\n  - `processStrategy(value, parent, key)`: Strategy to use when pre-processing the entity. Use this method to add extra data, defaults, and/or completely change the entity before normalization is complete. Defaults to returning a shallow copy of the input entity.  \n    _Note: It is recommended to always return a copy of your input and not modify the original._  \n    The function accepts the following arguments, in order:\n    - `value`: The input value of the entity.\n    - `parent`: The parent object of the input array.\n    - `key`: The key at which the input array appears on the parent object.\n  - `fallbackStrategy(key, schema)`: Strategy to use when denormalizing data structures with id references to missing entities.\n    - `key`: The key at which the input array appears on the parent object.\n    - `schema`: The schema of the missing entity\n\n#### Instance Methods\n\n- `define(definition)`: When used, the `definition` passed in will be merged with the original definition passed to the `Entity` constructor. This method tends to be useful for creating circular references in schema.\n\n#### Instance Attributes\n\n- `key`: Returns the key provided to the constructor.\n- `idAttribute`: Returns the idAttribute provided to the constructor in options.\n\n#### Usage\n\n```js\nconst data = { id_str: '123', url: 'https://twitter.com', user: { id_str: '456', name: 'Jimmy' } };\n\nconst user = new schema.Entity('users', {}, { idAttribute: 'id_str' });\nconst tweet = new schema.Entity(\n  'tweets',\n  { user: user },\n  {\n    idAttribute: 'id_str',\n    // Apply everything from entityB over entityA, except for \"favorites\"\n    mergeStrategy: (entityA, entityB) => ({\n      ...entityA,\n      ...entityB,\n      favorites: entityA.favorites\n    }),\n    // Remove the URL field from the entity\n    processStrategy: (entity) => omit(entity, 'url')\n  }\n);\n\nconst normalizedData = normalize(data, tweet);\n```\n\n#### Output\n\n```js\n{\n  entities: {\n    tweets: { '123': { id_str: '123', user: '456' } },\n    users: { '456': { id_str: '456', name: 'Jimmy' } }\n  },\n  result: '123'\n}\n```\n\n#### `idAttribute` Usage\n\nWhen passing the `idAttribute` a function, it should return the IDs value.\n\nFor Example:\n\n```js\nconst data = [{ id: '1', guest_id: null, name: 'Esther' }, { id: '1', guest_id: '22', name: 'Tom' }];\n\nconst patronsSchema = new schema.Entity('patrons', undefined, {\n  // idAttribute *functions* must return the ids **value** (not key)\n  idAttribute: (value) => (value.guest_id ? `${value.id}-${value.guest_id}` : value.id)\n});\n\nnormalize(data, [patronsSchema]);\n```\n\n#### Output\n\n```js\n{\n  entities: {\n    patrons: {\n      '1': { id: '1', guest_id: null, name: 'Esther' },\n      '1-22': { id: '1', guest_id: '22', name: 'Tom' },\n    }\n  },\n  result: ['1', '1-22']\n}\n```\n\n#### `fallbackStrategy` Usage\n```js\nconst users = {\n  '1': { id: '1', name: \"Emily\", requestState: 'SUCCEEDED' },\n  '2': { id: '2', name: \"Douglas\", requestState: 'SUCCEEDED' }\n};\nconst books = {\n  '1': {id: '1', name: \"Book 1\", author: 1 },\n  '2': {id: '2', name: \"Book 2\", author: 2 },\n  '3': {id: '3', name: \"Book 3\", author: 3 }\n};\n\nconst authorSchema = new schema.Entity('authors', {}, {\n  fallbackStrategy: (key, schema) => {\n    return {\n      [schema.idAttribute]: key,\n      name: 'Unknown',\n      requestState: 'NONE'\n    };\n  }\n});\nconst bookSchema = new schema.Entity('books', {\n  author: authorSchema\n});\n\ndenormalize([1, 2, 3], [bookSchema], {\n  books,\n  authors: users\n})\n\n```\n\n\n#### Output\n```js\n[\n  {\n    id: '1', \n    name: \"Book 1\", \n    author: { id: '1', name: \"Emily\", requestState: 'SUCCEEDED' }\n  },\n  {\n    id: '2', \n    name: \"Book 2\", \n    author: { id: '2', name: \"Douglas\", requestState: 'SUCCEEDED' },\n  },\n  {\n    id: '3', \n    name: \"Book 3\", \n    author: { id: '3', name: \"Unknown\", requestState: 'NONE' },\n  }\n]\n\n```\n\n### `Object(definition)`\n\nDefine a plain object mapping that has values needing to be normalized into Entities. _Note: The same behavior can be defined with shorthand syntax: `{ ... }`_\n\n- `definition`: **required** A definition of the nested entities found within this object. Defaults to empty object.  \n  You _do not_ need to define any keys in your object other than those that hold other entities. All other values will be copied to the normalized output.\n\n#### Instance Methods\n\n- `define(definition)`: When used, the `definition` passed in will be merged with the original definition passed to the `Object` constructor. This method tends to be useful for creating circular references in schema.\n\n#### Usage\n\n```js\n// Example data response\nconst data = { users: [{ id: '123', name: 'Beth' }] };\n\nconst user = new schema.Entity('users');\nconst responseSchema = new schema.Object({ users: new schema.Array(user) });\n// or shorthand\nconst responseSchema = { users: new schema.Array(user) };\n\nconst normalizedData = normalize(data, responseSchema);\n```\n\n#### Output\n\n```js\n{\n  entities: {\n    users: { '123': { id: '123', name: 'Beth' } }\n  },\n  result: { users: [ '123' ] }\n}\n```\n\n### `Union(definition, schemaAttribute)`\n\nDescribe a schema which is a union of multiple schemas. This is useful if you need the polymorphic behavior provided by `schema.Array` or `schema.Values` but for non-collection fields.\n\n- `definition`: **required** An object mapping the definition of the nested entities found within the input array\n- `schemaAttribute`: **required** The attribute on each entity found that defines what schema, per the definition mapping, to use when normalizing.  \n  Can be a string or a function. If given a function, accepts the following arguments:\n  - `value`: The input value of the entity.\n  - `parent`: The parent object of the input array.\n  - `key`: The key at which the input array appears on the parent object.\n\n#### Instance Methods\n\n- `define(definition)`: When used, the `definition` passed in will be merged with the original definition passed to the `Union` constructor. This method tends to be useful for creating circular references in schema.\n\n#### Usage\n\n_Note: If your data returns an object that you did not provide a mapping for, the original object will be returned in the result and an entity will not be created._\n\n```js\nconst data = { owner: { id: 1, type: 'user', name: 'Anne' } };\n\nconst user = new schema.Entity('users');\nconst group = new schema.Entity('groups');\nconst unionSchema = new schema.Union(\n  {\n    user: user,\n    group: group\n  },\n  'type'\n);\n\nconst normalizedData = normalize(data, { owner: unionSchema });\n```\n\n#### Output\n\n```js\n{\n  entities: {\n    users: { '1': { id: 1, type: 'user', name: 'Anne' } }\n  },\n  result: { owner: { id: 1, schema: 'user' } }\n}\n```\n\n### `Values(definition, schemaAttribute)`\n\nDescribes a map whose values follow the given schema.\n\n- `definition`: **required** A singular schema that this array contains _or_ a mapping of schema to attribute values.\n- `schemaAttribute`: _optional_ (required if `definition` is not a singular schema) The attribute on each entity found that defines what schema, per the definition mapping, to use when normalizing.  \n  Can be a string or a function. If given a function, accepts the following arguments:\n  - `value`: The input value of the entity.\n  - `parent`: The parent object of the input array.\n  - `key`: The key at which the input array appears on the parent object.\n\n#### Instance Methods\n\n- `define(definition)`: When used, the `definition` passed in will be merged with the original definition passed to the `Values` constructor. This method tends to be useful for creating circular references in schema.\n\n#### Usage\n\n```js\nconst data = { firstThing: { id: 1 }, secondThing: { id: 2 } };\n\nconst item = new schema.Entity('items');\nconst valuesSchema = new schema.Values(item);\n\nconst normalizedData = normalize(data, valuesSchema);\n```\n\n#### Output\n\n```js\n{\n  entities: {\n    items: { '1': { id: 1 }, '2': { id: 2 } }\n  },\n  result: { firstThing: 1, secondThing: 2 }\n}\n```\n\nIf your input data is an object that has values of more than one type of entity, but their schema is not easily defined by the key, you can use a mapping of schema, much like `schema.Union` and `schema.Array`.\n\n_Note: If your data returns an object that you did not provide a mapping for, the original object will be returned in the result and an entity will not be created._\n\nFor example:\n\n```js\nconst data = {\n  '1': { id: 1, type: 'admin' },\n  '2': { id: 2, type: 'user' }\n};\n\nconst userSchema = new schema.Entity('users');\nconst adminSchema = new schema.Entity('admins');\nconst valuesSchema = new schema.Values(\n  {\n    admins: adminSchema,\n    users: userSchema\n  },\n  (input, parent, key) => `${input.type}s`\n);\n\nconst normalizedData = normalize(data, valuesSchema);\n```\n\n#### Output\n\n```js\n{\n  entities: {\n    admins: { '1': { id: 1, type: 'admin' } },\n    users: { '2': { id: 2, type: 'user' } }\n  },\n  result: {\n    '1': { id: 1, schema: 'admins' },\n    '2': { id: 2, schema: 'users' }\n  }\n}\n```\n"
  },
  {
    "path": "docs/faqs.md",
    "content": "# Frequently Asked Questions\n\nIf you are having trouble with Normalizr, try [StackOverflow](http://stackoverflow.com/questions/tagged/normalizr). There is a larger community there that will help you solve issues a lot quicker than opening an Issue on the Normalizr GitHub page.\n"
  },
  {
    "path": "docs/introduction.md",
    "content": "# Introduction\n\n## Motivation\n\nMany APIs, public or not, return JSON data that has deeply nested objects. Using data in this kind of structure is often very difficult for JavaScript applications, especially those using [Flux](http://facebook.github.io/flux/) or [Redux](http://redux.js.org/).\n\n## Solution\n\nNormalizr is a small, but powerful utility for taking JSON with a schema definition and returning nested entities with their IDs, gathered in dictionaries.\n\n### Example\n\nThe following nested object:\n\n```js\n[\n  {\n    id: 1,\n    title: 'Some Article',\n    author: {\n      id: 1,\n      name: 'Dan'\n    }\n  },\n  {\n    id: 2,\n    title: 'Other Article',\n    author: {\n      id: 1,\n      name: 'Dan'\n    }\n  }\n];\n```\n\nCan be normalized to:\n\n```js\n{\n  result: [1, 2],\n  entities: {\n    articles: {\n      1: {\n        id: 1,\n        title: 'Some Article',\n        author: 1\n      },\n      2: {\n        id: 2,\n        title: 'Other Article',\n        author: 1\n      }\n    },\n    users: {\n      1: {\n        id: 1,\n        name: 'Dan'\n      }\n    }\n  }\n}\n```\n\n## Build Files\n\nNormalizr is built for various environments\n\n- `src/*`\n  - CommonJS, unpacked files. These are the recommended files for use with your own package bundler and are the default in-point as defined by this modules `package.json`.\n- `normalizr.js`, `normalizr.min.js`\n  - [CommonJS](http://davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/)\n- `normalizr.amd.js`, `normalizr.amd.min.js`\n  - [Asynchronous Module Definition](http://davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/)\n- `normalizr.umd.js`, `normalizr.umn.min.js`\n  - [Universal Module Definition](http://davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/)\n- `normalizr.browser.js`, `normalizr.browser.min.js`\n  - [IIFE](http://benalman.com/news/2010/11/immediately-invoked-function-expression/) / Immediately-Invoked Function Expression, suitable for use as a standalone script import in the browser.\n  - Note: It is not recommended to use packages like Normalizr with direct browser `<script src=\"normalizr.js\"></script>` tags. Consider a package bundler like [webpack](https://webpack.github.io/), [rollup](https://rollupjs.org/), or [browserify](http://browserify.org/) instead.\n"
  },
  {
    "path": "docs/jsonapi.md",
    "content": "# Normalizr and JSONAPI\n\nIf you're using JSONAPI, you're ahead of the curve, but also in a bit of a tough spot. JSONAPI is a great spec, but doesn't play nicely with the way that you want to manage data in Redux/Flux style state management applications.\n\nJust as well, Normalizr was not written for JSONAPI and really doesn't work well. Instead, stop what you're doing now and check out some of the other great libraries and packages available that are written specifically for normalizing JSONAPI data\\*:\n\n- [stevenpetryk/jsonapi-normalizer](https://github.com/stevenpetryk/jsonapi-normalizer)\n- [yury-dymov/json-api-normalizer](https://github.com/yury-dymov/json-api-normalizer)\n- [JSONAPI client libraries](http://jsonapi.org/implementations/#client-libraries-javascript)\n\n**Note:** These are in no particular order. Review all libraries on your own before deciding which is best for your particular use-case.\n"
  },
  {
    "path": "docs/quickstart.md",
    "content": "# Quick Start\n\nConsider a typical blog post. The API response for a single post might look something like this:\n\n```json\n{\n  \"id\": \"123\",\n  \"author\": {\n    \"id\": \"1\",\n    \"name\": \"Paul\"\n  },\n  \"title\": \"My awesome blog post\",\n  \"comments\": [\n    {\n      \"id\": \"324\",\n      \"commenter\": {\n        \"id\": \"2\",\n        \"name\": \"Nicole\"\n      }\n    }\n  ]\n}\n```\n\nWe have two nested entity types within our `article`: `users` and `comments`. Using various `schema`, we can normalize all three entity types down:\n\n```js\nimport { normalize, schema } from 'normalizr';\n\n// Define a users schema\nconst user = new schema.Entity('users');\n\n// Define your comments schema\nconst comment = new schema.Entity('comments', {\n  commenter: user\n});\n\n// Define your article\nconst article = new schema.Entity('articles', {\n  author: user,\n  comments: [comment]\n});\n\nconst normalizedData = normalize(originalData, article);\n```\n\nNow, `normalizedData` will be:\n\n```js\n{\n  result: \"123\",\n  entities: {\n    \"articles\": {\n      \"123\": {\n        id: \"123\",\n        author: \"1\",\n        title: \"My awesome blog post\",\n        comments: [ \"324\" ]\n      }\n    },\n    \"users\": {\n      \"1\": { \"id\": \"1\", \"name\": \"Paul\" },\n      \"2\": { \"id\": \"2\", \"name\": \"Nicole\" }\n    },\n    \"comments\": {\n      \"324\": { id: \"324\", \"commenter\": \"2\" }\n    }\n  }\n}\n```\n"
  },
  {
    "path": "examples/.eslintrc",
    "content": "{\n  \"rules\": {\n    \"no-console\": \"off\"\n  }\n}\n"
  },
  {
    "path": "examples/github/README.md",
    "content": "# Normalizing GitHub Issues\n\nThis is a barebones example for node to illustrate how normalizing the GitHub Issues API endpoint could work.\n\n## Running\n\n```sh\n# from the root directory:\nyarn\n# from this directory:\n../../node_modules/.bin/babel-node ./index.js\n```\n\n## Files\n\n* [index.js](/examples/github/index.js): Pulls live data from the GitHub API for this project's issues and normalizes the JSON.\n* [output.json](/examples/github/output.json): A sample of the normalized output.\n* [schema.js](/examples/github/schema.js): The schema used to normalize the GitHub issues.\n"
  },
  {
    "path": "examples/github/index.js",
    "content": "import * as schema from './schema';\nimport fs from 'fs';\nimport https from 'https';\nimport { normalize } from '../../src';\nimport path from 'path';\n\nlet data = '';\nconst request = https.request(\n  {\n    host: 'api.github.com',\n    path: '/repos/paularmstrong/normalizr/issues',\n    method: 'get',\n    headers: {\n      'user-agent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',\n    },\n  },\n  (res) => {\n    res.on('data', (d) => {\n      data += d;\n    });\n\n    res.on('end', () => {\n      const normalizedData = normalize(JSON.parse(data), schema.issueOrPullRequest);\n      const out = JSON.stringify(normalizedData, null, 2);\n      fs.writeFileSync(path.resolve(__dirname, './output.json'), out);\n    });\n\n    res.on('error', (e) => {\n      console.log(e);\n    });\n  }\n);\n\nrequest.end();\n"
  },
  {
    "path": "examples/github/output.json",
    "content": "{\n  \"entities\": {\n    \"users\": {\n      \"14322\": {\n        \"login\": \"whistlerbrk\",\n        \"id\": 14322,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/14322?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/whistlerbrk\",\n        \"html_url\": \"https://github.com/whistlerbrk\",\n        \"followers_url\": \"https://api.github.com/users/whistlerbrk/followers\",\n        \"following_url\": \"https://api.github.com/users/whistlerbrk/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/whistlerbrk/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/whistlerbrk/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/whistlerbrk/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/whistlerbrk/orgs\",\n        \"repos_url\": \"https://api.github.com/users/whistlerbrk/repos\",\n        \"events_url\": \"https://api.github.com/users/whistlerbrk/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/whistlerbrk/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"33297\": {\n        \"login\": \"paularmstrong\",\n        \"id\": 33297,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/33297?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/paularmstrong\",\n        \"html_url\": \"https://github.com/paularmstrong\",\n        \"followers_url\": \"https://api.github.com/users/paularmstrong/followers\",\n        \"following_url\": \"https://api.github.com/users/paularmstrong/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/paularmstrong/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/paularmstrong/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/paularmstrong/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/paularmstrong/orgs\",\n        \"repos_url\": \"https://api.github.com/users/paularmstrong/repos\",\n        \"events_url\": \"https://api.github.com/users/paularmstrong/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/paularmstrong/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"69485\": {\n        \"login\": \"unindented\",\n        \"id\": 69485,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/69485?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/unindented\",\n        \"html_url\": \"https://github.com/unindented\",\n        \"followers_url\": \"https://api.github.com/users/unindented/followers\",\n        \"following_url\": \"https://api.github.com/users/unindented/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/unindented/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/unindented/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/unindented/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/unindented/orgs\",\n        \"repos_url\": \"https://api.github.com/users/unindented/repos\",\n        \"events_url\": \"https://api.github.com/users/unindented/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/unindented/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"84749\": {\n        \"login\": \"pcompassion\",\n        \"id\": 84749,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/84749?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/pcompassion\",\n        \"html_url\": \"https://github.com/pcompassion\",\n        \"followers_url\": \"https://api.github.com/users/pcompassion/followers\",\n        \"following_url\": \"https://api.github.com/users/pcompassion/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/pcompassion/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/pcompassion/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/pcompassion/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/pcompassion/orgs\",\n        \"repos_url\": \"https://api.github.com/users/pcompassion/repos\",\n        \"events_url\": \"https://api.github.com/users/pcompassion/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/pcompassion/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"655857\": {\n        \"login\": \"adjohu\",\n        \"id\": 655857,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/655857?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/adjohu\",\n        \"html_url\": \"https://github.com/adjohu\",\n        \"followers_url\": \"https://api.github.com/users/adjohu/followers\",\n        \"following_url\": \"https://api.github.com/users/adjohu/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/adjohu/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/adjohu/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/adjohu/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/adjohu/orgs\",\n        \"repos_url\": \"https://api.github.com/users/adjohu/repos\",\n        \"events_url\": \"https://api.github.com/users/adjohu/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/adjohu/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"853220\": {\n        \"login\": \"babsonmatt\",\n        \"id\": 853220,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/853220?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/babsonmatt\",\n        \"html_url\": \"https://github.com/babsonmatt\",\n        \"followers_url\": \"https://api.github.com/users/babsonmatt/followers\",\n        \"following_url\": \"https://api.github.com/users/babsonmatt/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/babsonmatt/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/babsonmatt/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/babsonmatt/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/babsonmatt/orgs\",\n        \"repos_url\": \"https://api.github.com/users/babsonmatt/repos\",\n        \"events_url\": \"https://api.github.com/users/babsonmatt/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/babsonmatt/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"969003\": {\n        \"login\": \"YoruNoHikage\",\n        \"id\": 969003,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/969003?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/YoruNoHikage\",\n        \"html_url\": \"https://github.com/YoruNoHikage\",\n        \"followers_url\": \"https://api.github.com/users/YoruNoHikage/followers\",\n        \"following_url\": \"https://api.github.com/users/YoruNoHikage/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/YoruNoHikage/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/YoruNoHikage/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/YoruNoHikage/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/YoruNoHikage/orgs\",\n        \"repos_url\": \"https://api.github.com/users/YoruNoHikage/repos\",\n        \"events_url\": \"https://api.github.com/users/YoruNoHikage/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/YoruNoHikage/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"1499050\": {\n        \"login\": \"poyannabati\",\n        \"id\": 1499050,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/1499050?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/poyannabati\",\n        \"html_url\": \"https://github.com/poyannabati\",\n        \"followers_url\": \"https://api.github.com/users/poyannabati/followers\",\n        \"following_url\": \"https://api.github.com/users/poyannabati/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/poyannabati/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/poyannabati/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/poyannabati/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/poyannabati/orgs\",\n        \"repos_url\": \"https://api.github.com/users/poyannabati/repos\",\n        \"events_url\": \"https://api.github.com/users/poyannabati/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/poyannabati/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"1591483\": {\n        \"login\": \"arb\",\n        \"id\": 1591483,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/1591483?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/arb\",\n        \"html_url\": \"https://github.com/arb\",\n        \"followers_url\": \"https://api.github.com/users/arb/followers\",\n        \"following_url\": \"https://api.github.com/users/arb/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/arb/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/arb/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/arb/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/arb/orgs\",\n        \"repos_url\": \"https://api.github.com/users/arb/repos\",\n        \"events_url\": \"https://api.github.com/users/arb/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/arb/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"1690457\": {\n        \"login\": \"TryingToImprove\",\n        \"id\": 1690457,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/1690457?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/TryingToImprove\",\n        \"html_url\": \"https://github.com/TryingToImprove\",\n        \"followers_url\": \"https://api.github.com/users/TryingToImprove/followers\",\n        \"following_url\": \"https://api.github.com/users/TryingToImprove/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/TryingToImprove/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/TryingToImprove/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/TryingToImprove/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/TryingToImprove/orgs\",\n        \"repos_url\": \"https://api.github.com/users/TryingToImprove/repos\",\n        \"events_url\": \"https://api.github.com/users/TryingToImprove/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/TryingToImprove/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"5119347\": {\n        \"login\": \"lqzerogg\",\n        \"id\": 5119347,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/5119347?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/lqzerogg\",\n        \"html_url\": \"https://github.com/lqzerogg\",\n        \"followers_url\": \"https://api.github.com/users/lqzerogg/followers\",\n        \"following_url\": \"https://api.github.com/users/lqzerogg/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/lqzerogg/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/lqzerogg/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/lqzerogg/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/lqzerogg/orgs\",\n        \"repos_url\": \"https://api.github.com/users/lqzerogg/repos\",\n        \"events_url\": \"https://api.github.com/users/lqzerogg/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/lqzerogg/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"6775919\": {\n        \"login\": \"BerkeleyTrue\",\n        \"id\": 6775919,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/6775919?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/BerkeleyTrue\",\n        \"html_url\": \"https://github.com/BerkeleyTrue\",\n        \"followers_url\": \"https://api.github.com/users/BerkeleyTrue/followers\",\n        \"following_url\": \"https://api.github.com/users/BerkeleyTrue/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/BerkeleyTrue/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/BerkeleyTrue/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/BerkeleyTrue/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/BerkeleyTrue/orgs\",\n        \"repos_url\": \"https://api.github.com/users/BerkeleyTrue/repos\",\n        \"events_url\": \"https://api.github.com/users/BerkeleyTrue/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/BerkeleyTrue/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      },\n      \"14313723\": {\n        \"login\": \"vimalceg\",\n        \"id\": 14313723,\n        \"avatar_url\": \"https://avatars.githubusercontent.com/u/14313723?v=3\",\n        \"gravatar_id\": \"\",\n        \"url\": \"https://api.github.com/users/vimalceg\",\n        \"html_url\": \"https://github.com/vimalceg\",\n        \"followers_url\": \"https://api.github.com/users/vimalceg/followers\",\n        \"following_url\": \"https://api.github.com/users/vimalceg/following{/other_user}\",\n        \"gists_url\": \"https://api.github.com/users/vimalceg/gists{/gist_id}\",\n        \"starred_url\": \"https://api.github.com/users/vimalceg/starred{/owner}{/repo}\",\n        \"subscriptions_url\": \"https://api.github.com/users/vimalceg/subscriptions\",\n        \"organizations_url\": \"https://api.github.com/users/vimalceg/orgs\",\n        \"repos_url\": \"https://api.github.com/users/vimalceg/repos\",\n        \"events_url\": \"https://api.github.com/users/vimalceg/events{/privacy}\",\n        \"received_events_url\": \"https://api.github.com/users/vimalceg/received_events\",\n        \"type\": \"User\",\n        \"site_admin\": false\n      }\n    },\n    \"labels\": {\n      \"undefined\": {\n        \"0\": {\n          \"id\": 373884166,\n          \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/labels/Priority:%20Low\",\n          \"name\": \"Priority: Low\",\n          \"color\": \"009800\",\n          \"default\": false\n        },\n        \"1\": {\n          \"id\": 373884281,\n          \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/labels/Status:%20Accepted\",\n          \"name\": \"Status: Accepted\",\n          \"color\": \"009800\",\n          \"default\": false\n        },\n        \"2\": {\n          \"id\": 373884447,\n          \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/labels/Type:%20Bug\",\n          \"name\": \"Type: Bug\",\n          \"color\": \"e11d21\",\n          \"default\": false\n        },\n        \"3\": {\n          \"id\": 373884449,\n          \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/labels/Type:%20Question\",\n          \"name\": \"Type: Question\",\n          \"color\": \"cc317c\",\n          \"default\": false\n        }\n      }\n    },\n    \"milestones\": {\n      \"1883567\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/milestones/2\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/milestone/2\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/milestones/2/labels\",\n        \"id\": 1883567,\n        \"number\": 2,\n        \"title\": \"2.3.0\",\n        \"description\": \"\",\n        \"creator\": 33297,\n        \"open_issues\": 2,\n        \"closed_issues\": 1,\n        \"state\": \"closed\",\n        \"created_at\": \"2016-07-14T15:24:52Z\",\n        \"updated_at\": \"2016-12-21T21:54:06Z\",\n        \"due_on\": \"2016-08-20T07:00:00Z\",\n        \"closed_at\": \"2016-12-19T19:32:20Z\"\n      },\n      \"2205635\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/milestones/3\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/milestone/3\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/milestones/3/labels\",\n        \"id\": 2205635,\n        \"number\": 3,\n        \"title\": \"3.0.0\",\n        \"description\": \"Fully re-written, simpler, faster, more extendable.\",\n        \"creator\": 33297,\n        \"open_issues\": 3,\n        \"closed_issues\": 0,\n        \"state\": \"open\",\n        \"created_at\": \"2016-12-19T19:32:16Z\",\n        \"updated_at\": \"2016-12-21T15:42:53Z\",\n        \"due_on\": \"2017-01-03T08:00:00Z\",\n        \"closed_at\": null\n      }\n    },\n    \"issues\": {\n      \"127301608\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/54\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/54/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/54/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/54/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/54\",\n        \"id\": 127301608,\n        \"number\": 54,\n        \"title\": \"Should empty arrays be preserved on their entity?\",\n        \"user\": 853220,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 5,\n        \"created_at\": \"2016-01-18T20:07:54Z\",\n        \"updated_at\": \"2016-05-24T14:00:18Z\",\n        \"closed_at\": null,\n        \"body\": \"{ id: 1, name: 'test', tags: [] } normalizes as { id: 1, name: 'test' }\\n\\nWould it make sense to preserve the empty array or at least allow the option to do so?\\n\"\n      },\n      \"134547187\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/68\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/68/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/68/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/68/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/68\",\n        \"id\": 134547187,\n        \"number\": 68,\n        \"title\": \"Dealing with collections keyed by ID without an ID in the entity itself\",\n        \"user\": 655857,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 4,\n        \"created_at\": \"2016-02-18T11:02:18Z\",\n        \"updated_at\": \"2016-05-24T13:59:36Z\",\n        \"closed_at\": null,\n        \"body\": \"Is there any way I can deal with data in the following format?\\n\\n```\\n{\\n  1: {\\n    name: 'test user'\\n  }\\n}\\n```\\n\\nAs far as I can tell, I can only work with objects that themselves contain an id i.e.\\n\\n```\\n{\\n  name: 'test user',\\n  id: 1\\n}\\n```\\n\"\n      },\n      \"134935104\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/70\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/70/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/70/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/70/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/70\",\n        \"id\": 134935104,\n        \"number\": 70,\n        \"title\": \"Keep consistency for relations\",\n        \"user\": 969003,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 9,\n        \"created_at\": \"2016-02-19T18:10:17Z\",\n        \"updated_at\": \"2016-05-24T13:45:14Z\",\n        \"closed_at\": null,\n        \"body\": \"I think it would be great to have consistency in relationship between entities when normalized.\\nHere's an example : \\n\\nConsider you have two entities **user** and **project**. You define the relations like this (bidirectional) : \\n\\n``` js\\nuserSchema.define({\\n  projects: arrayOf(projectSchema),\\n})\\n\\nprojectSchema.define({\\n  contributors: arrayOf(userSchema),\\n})\\n```\\n\\nNow you want to create a project, you send some request to a server that replies with the project entity like this : \\n\\n``` js\\n{\\n  id: 80,\\n  contributors: [1], // your userId for example\\n  // other fields\\n}\\n```\\n\\nYou normalize the response and merge with the rest of your entities (just like the real-world example for Redux), the projects' entities are updated but not the user.\\n\\nSo my proposal is to add a capability of telling what is the property in the foreign object and instead of normalizing like this : \\n\\n``` js\\n{\\n  entities: {\\n    projects: {\\n      80: {\\n        id: 80,\\n        contributors: [1],\\n        // other fields\\n      }\\n    }\\n  }\\n}\\n```\\n\\nI'm proposing something like this : \\n\\n``` js\\n{\\n  entities: {\\n    projects: {\\n      80: {\\n        id: 80,\\n        contributors: [1],\\n        // other fields\\n      }\\n    },\\n    users: {\\n      1: {\\n         id: 1,\\n         projects: [80]\\n       }\\n    }\\n  }\\n}\\n```\\n\\nWe could do something like this : \\n\\n``` js\\nuserSchema.define({\\n  projects: arrayOf(projectSchema).mappedBy('contributors'),\\n})\\n\\nprojectSchema.define({\\n  contributors: arrayOf(userSchema).mappedBy('projects'),\\n})\\n```\\n\\nThis prevent any flaws in consistency while being simple (user side). I already tried a solution, it's not bulletproof but I can submit a cleaner PR if you're interested.\\n\"\n      },\n      \"154328039\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/105\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/105/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/105/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/105/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/105\",\n        \"id\": 154328039,\n        \"number\": 105,\n        \"title\": \"Update & Organize Documentation\",\n        \"user\": 33297,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": 33297,\n        \"assignees\": [\n          33297\n        ],\n        \"milestone\": 1883567,\n        \"comments\": 0,\n        \"created_at\": \"2016-05-11T19:52:34Z\",\n        \"updated_at\": \"2016-07-14T15:24:58Z\",\n        \"closed_at\": null,\n        \"body\": \"\"\n      },\n      \"154328158\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/106\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/106/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/106/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/106/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/106\",\n        \"id\": 154328158,\n        \"number\": 106,\n        \"title\": \"Clean up tests\",\n        \"user\": 33297,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": 33297,\n        \"assignees\": [\n          33297\n        ],\n        \"milestone\": 1883567,\n        \"comments\": 0,\n        \"created_at\": \"2016-05-11T19:53:07Z\",\n        \"updated_at\": \"2016-07-14T15:25:19Z\",\n        \"closed_at\": null,\n        \"body\": \"Currently hard to read through. Should be better organized and easier to maintain.\\n\"\n      },\n      \"161111737\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/126\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/126/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/126/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/126/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/126\",\n        \"id\": 161111737,\n        \"number\": 126,\n        \"title\": \"normalize is take some time if object has more number of keys.\",\n        \"user\": 14313723,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 3,\n        \"created_at\": \"2016-06-20T03:41:07Z\",\n        \"updated_at\": \"2016-07-05T22:55:19Z\",\n        \"closed_at\": null,\n        \"body\": \"# Problem\\n\\nnormalization is take some time if object has more number of keys.  I tried with array has 100 object and each object has 30 keys and mapping with two nested schema. It takes around ~25ms. I tried using schema based iteration it takes ~7ms. I don't know, Is any problem using schema based iteration? Please refer the link below.\\n\\nhttps://github.com/vimalceg/normalizr\\n\"\n      },\n      \"178773735\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/154\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/154/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/154/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/154/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/154\",\n        \"id\": 178773735,\n        \"number\": 154,\n        \"title\": \"Will arrayOf support a parameter that not a schema?\",\n        \"user\": 5119347,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 2,\n        \"created_at\": \"2016-09-23T03:05:59Z\",\n        \"updated_at\": \"2016-09-27T01:58:49Z\",\n        \"closed_at\": null,\n        \"body\": \"# Problem\\n\\nAfter normalizr process, I want something like this\\n\\n```\\n{result: [{a: 'xxx', b: 'xxx', c: 'ccc'}]}\\n```\\n\\nBut I don't want to defined a schema like:\\n\\n```\\nconst dSchema = new Schema('d')\\ndSchema.define({a: aSchema, b: bSchema, c: cSchema})\\n```\\n\\nIs there any way to achieve this? If there is, please tell me. If not, will it possible to support it?\\nI do feel sick of defining an extra schema.\\n\\n**Input**\\n\\n```\\nlet input = [{a: {id: 'aid', name: 'object a'}, b: {id: 'bid', name: 'object b'}]\\n```\\n\\n**schema**\\n\\n```\\nconst aSchema = new Schema('as');\\nconst bSchema = new Schema('bs');\\nnormalize(input, arrayOf({a: aSchema, b: bSchema}));\\n```\\n\\n**Output**\\n\\nHere's what I expect to see when I run the above:\\n\\n```\\n{\\n result: [{a: 'aid', b: 'bid'}],\\n entities: {\\n  as: { aid: {id: 'aid', name: 'object a'}}\\n  bs: { bid: {id: 'bid', name: 'object b'}}\\n }\\n}\\n```\\n\"\n      },\n      \"186469900\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/165\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/165/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/165/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/165/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/165\",\n        \"id\": 186469900,\n        \"number\": 165,\n        \"title\": \"Django rest framework and normalizr \",\n        \"user\": 84749,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 2,\n        \"created_at\": \"2016-11-01T06:46:33Z\",\n        \"updated_at\": \"2016-11-02T06:30:40Z\",\n        \"closed_at\": null,\n        \"body\": \"For a following schema (expressed in django models)\\r\\n\\r\\n    class A:\\r\\n      b = foreigneKey(B)\\r\\n      \\r\\n    \\r\\n    class B:\\r\\n      pass\\r\\n\\r\\nDjango rest framework serializes A instance as\\r\\n\\r\\n    { \\r\\n      'id': 1,\\r\\n      'b': 3\\r\\n    }\\r\\n\\r\\n\\r\\nnormalizr seems to want a format of\\r\\n\\r\\n    {\\r\\n      'id': 1,\\r\\n      'b': {\\r\\n        'id': 3\\r\\n      }\\r\\n    }\\r\\n\\r\\n\\r\\nI could change DRF 's default PrimaryKeyRelatedField's serialization, but I 'm wondering if there's another way?  \\r\\nMaybe there's a way to tell normalizr that 'b' is itself an id attribute?\\r\\n\\r\\n\"\n      },\n      \"188006036\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/172\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/172/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/172/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/172/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/172\",\n        \"id\": 188006036,\n        \"number\": 172,\n        \"title\": \"Set field on child entity based on parent entity on normalization\",\n        \"user\": 1499050,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 2,\n        \"created_at\": \"2016-11-08T14:29:51Z\",\n        \"updated_at\": \"2016-12-21T15:02:10Z\",\n        \"closed_at\": null,\n        \"body\": \"# Problem\\r\\n\\r\\nIs there anyway to set a field on the child entity based on the parent entity? I.e, if we have\\r\\n\\r\\n```\\r\\n[{\\r\\n  id: 1,\\r\\n  title: 'Some Article',\\r\\n  comment: {\\r\\n    id: 109,\\r\\n    content: 'Hai'\\r\\n  }\\r\\n}, {\\r\\n  id: 2,\\r\\n  title: 'Other Article',\\r\\n  comment: {\\r\\n    id: 110,\\r\\n    content: 'Gee'\\r\\n  }\\r\\n}]\\r\\n```\\r\\n\\r\\nCan we somehow get \\r\\n\\r\\n```\\r\\n{\\r\\n  result: [1, 2],\\r\\n  entities: {\\r\\n    articles: {\\r\\n      1: {\\r\\n        id: 1,\\r\\n        title: 'Some Article',\\r\\n        comments: [109]\\r\\n      },\\r\\n      2: {\\r\\n        id: 2,\\r\\n        title: 'Other Article',\\r\\n        comments: [110]\\r\\n      }\\r\\n    },\\r\\n    comments: {\\r\\n      109: {\\r\\n        id: 109,\\r\\n        content: 'Hai'\\r\\n        article: 1,\\r\\n      },\\r\\n      110: {\\r\\n        id: 110,\\r\\n        content: 'Gee'\\r\\n        article: 2,\\r\\n      }\\r\\n    }\\r\\n  }\\r\\n}\\r\\n```\\r\\n?\\r\\n\\r\\nWould this be a desirable feature otherwise? \"\n      },\n      \"191124603\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/176\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/176/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/176/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/176/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/176\",\n        \"id\": 191124603,\n        \"number\": 176,\n        \"title\": \"bizarre uncaught reference when attempting import of normalizr\",\n        \"user\": 14322,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 3,\n        \"created_at\": \"2016-11-22T21:08:09Z\",\n        \"updated_at\": \"2016-11-28T10:11:54Z\",\n        \"closed_at\": null,\n        \"body\": \"# Problem\\r\\n\\r\\nI have a perfectly working Redux app being served out via Express, I'm using webpack which I suspect may be the problem.\\r\\n\\r\\n**Input**\\r\\n\\r\\nHere's how I'm using normalizr, in schema.js\\r\\n\\r\\n```js\\r\\nimport { Schema, arrayOf, valuesOf } from 'normalizr';\\r\\n\\r\\nconst uploadSchema = new Schema('uploads');\\r\\nconst sheetSchema = new Schema('sheets');\\r\\nconst mappingSchema = new Schema('mappings');\\r\\nconst tableSchema = new Schema('tables');\\r\\nconst rowSchema = new Schema('rows');\\r\\n\\r\\nuploadSchema.define({\\r\\n  sheets: valuesOf(sheetSchema)\\r\\n})\\r\\n\\r\\nsheetSchema.define({\\r\\n  mapping: mappingSchema,\\r\\n  rows: {\\r\\n    collection: arrayOf(tableSchema)\\r\\n  }\\r\\n})\\r\\n\\r\\nexport { uploadSchema, sheetSchema, mappingSchema, tableSchema, rowSchema }\\r\\n```\\r\\n\\r\\nand in a utility file I have for abstracting out my ajax calls via `whatwg-fetch`:\\r\\n\\r\\n```js\\r\\nimport { normalize } from 'normalizr';\\r\\nimport {uploadSchema, sheetSchema, mappingSchema, tableSchema, rowSchema} from \\\"./schema\\\";\\r\\nimport 'whatwg-fetch';\\r\\n\\r\\n... some functions ...\\r\\n\\r\\nfunction handleRequestAndResponse(url, request) {\\r\\n  console.log(\\\"request:\\\");\\r\\n  console.log(request);\\r\\n\\r\\n  return fetch(url, request)\\r\\n          .then(function (resp) {\\r\\n            // check status and throw if unexpected...\\r\\n            if (resp.status >= 200 && resp.status < 400) {\\r\\n              return resp\\r\\n            } else {\\r\\n\\r\\n              console.log(\\\"uncaught error, but we will format a response from it...\\\")\\r\\n              console.log(resp)\\r\\n\\r\\n              var errorResponse = new Error(resp.statusText)\\r\\n              errorResponse.error = true\\r\\n              errorResponse.status = resp.status;\\r\\n              errorResponse.url = resp.url;\\r\\n              errorResponse.body = resp.body;\\r\\n\\r\\n              throw errorResponse\\r\\n            }\\r\\n          }).then(function (resp) {\\r\\n            // parse JSON\\r\\n            return resp.json();\\r\\n          }).then(function (resp) {\\r\\n            // log and return\\r\\n            console.log(\\\"response:\\\");\\r\\n            console.log(resp);\\r\\n\\r\\n           // boom, here, normalize is an uncaught ref, and if I insert a debugger anywhere else in this file, the same\\r\\n            debugger\\r\\n\\r\\n            return resp;\\r\\n          }).catch(function(errorResp) {\\r\\n            return errorResp;\\r\\n          });\\r\\n}\\r\\n```\\r\\n\\r\\nI suspected TypeScript involvement and tried to remedy with ts-loader, no dice.\\r\\n\\r\\nI tried directly importing normalizr without success:\\r\\n\\r\\n`import 'normalizr'`\\r\\n\\r\\nI've tried adjusting my webpack in the endless esoteric ways it requires to no avail.\\r\\n\\r\\nI've tried directly importing functions from normalizr.min.js in the dist folder, also to no avail.\\r\\n\\r\\nMy webpack:\\r\\n\\r\\n```\\r\\nvar path = require('path');\\r\\nvar webpack = require('webpack');\\r\\n\\r\\nmodule.exports = {\\r\\n  devtool: 'eval-source-map',\\r\\n  entry:\\r\\n      [\\r\\n        'whatwg-fetch',\\r\\n        'webpack-hot-middleware/client',\\r\\n        './client/inventory-app'\\r\\n      ]\\r\\n  ,\\r\\n  output: {\\r\\n    path: path.join(__dirname, 'dist'),\\r\\n    filename: 'bundle.js',\\r\\n    publicPath: '/static/'\\r\\n  },\\r\\n  plugins: [\\r\\n    new webpack.HotModuleReplacementPlugin(),\\r\\n    new webpack.NoErrorsPlugin()\\r\\n  ],\\r\\n  resolve: {\\r\\n    alias: {\\r\\n      webworkify: 'webworkify-webpack'\\r\\n    },\\r\\n    extensions: ['', '.scss', '.css', '.js', '.json', '.ts'],\\r\\n    modulesDirectories: [\\r\\n      'node_modules',\\r\\n      path.resolve(__dirname, './node_modules')\\r\\n    ]\\r\\n  },\\r\\n  module: {\\r\\n    loaders: [\\r\\n      {\\r\\n        test: /\\\\.css$/,\\r\\n        loader:'style!css!'\\r\\n      },\\r\\n      { test: /\\\\.tsx?$/, loader: \\\"ts-loader\\\" },\\r\\n      {\\r\\n        test: /\\\\.js$/,\\r\\n        loaders: ['babel'],\\r\\n        // loaders: ['babel?presets[]=es2015,presets[]=stage-0,presets[]=react,plugins[]=transform-runtime'],\\r\\n        exclude: /node_modules/,\\r\\n        include: path.join(__dirname, 'client')\\r\\n      },\\r\\n      {\\r\\n        test: /\\\\.json$/,\\r\\n        loader: 'json-loader'\\r\\n      }\\r\\n    ]\\r\\n  }\\r\\n};\\r\\n```\\r\\n\\r\\nNote that the typescript stuff was added by me most recently and didn't help.\\r\\n\\r\\nAlso note that when in my schema.js when I insert a debugger statement below the const definitions, my consts *are* defined, but if I try to \\\"var foo = new Schema('foo')\\\" I'll get a reference error again.\\r\\n\\r\\nI'm at a loss. I hope this isn't my poor understanding of webpack, but this has only happened with this integration so I'm opening an issue after not finding success looking around on the web / asking in Reactiflux.\\r\\n\\r\\n\\r\\n\\r\\n\"\n      },\n      \"196525159\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/185\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/185/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/185/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/185/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/185\",\n        \"id\": 196525159,\n        \"number\": 185,\n        \"title\": \"Request: Add a changelog\",\n        \"user\": 6775919,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 1,\n        \"created_at\": \"2016-12-19T21:29:14Z\",\n        \"updated_at\": \"2016-12-19T21:40:26Z\",\n        \"closed_at\": null,\n        \"body\": \"I'm attempting to debug in a breaking change introduced in 2.3\\r\\n\\r\\nMy first step is usually to run through the changelog, but to my surprise there is none to be found (At least to my eyes, but I have been known to miss things).\\r\\n\\r\\nAlso, want to say thanks for such an awesome project!\"\n      },\n      \"196531836\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/186\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/186/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/186/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/186/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/186\",\n        \"id\": 196531836,\n        \"number\": 186,\n        \"title\": \"results for arrayOf returns array instead of map as of version 2.3.0\",\n        \"user\": 6775919,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 0,\n        \"created_at\": \"2016-12-19T22:01:30Z\",\n        \"updated_at\": \"2016-12-19T22:10:31Z\",\n        \"closed_at\": null,\n        \"body\": \"# Problem\\r\\n\\r\\nUpgrading to 2.3.0 introduces a breaking change to the way `results` are returned.\\r\\n\\r\\nIt looks like results are now normalized when both values are the same. https://github.com/paularmstrong/normalizr/blob/master/src/index.js#L116\\r\\n\\r\\nThis is a breaking change and should result in a major bump and a warning to those who expect a keyed map in return regardless of the values of that map.\\r\\n\\r\\n**Input**\\r\\n\\r\\nHere's how I'm using normalizr:\\r\\n\\r\\n```js\\r\\nconst challenge = new Schema('challenge', { idAttribute: 'dashedName' });\\r\\nconst block = new Schema('block', { idAttribute: 'dashedName' });\\r\\nconst superBlock = new Schema('superBlock', { idAttribute: 'dashedName' });\\r\\n\\r\\nblock.define({\\r\\n  challenges: arrayOf(challenge)\\r\\n});\\r\\n\\r\\nsuperBlock.define({\\r\\n  blocks: arrayOf(block)\\r\\n});\\r\\n\\r\\nconst mapSchema = valuesOf(superBlock);\\r\\nconst map = {\\r\\n  'getting-started': {\\r\\n    dashedName: 'getting-started',\\r\\n    blocks: [\\r\\n      {\\r\\n        dashedName: 'some-block',\\r\\n        challenges: [{\\r\\n          dashedName: 'some-challenge',\\r\\n          ...\\r\\n        },\\r\\n        ...\\r\\n       ],\\r\\n        ...\\r\\n      },\\r\\n      ...\\r\\n    ],\\r\\n    ...\\r\\n  },\\r\\n   ...\\r\\n};\\r\\nnormalize(map, mapSchema);\\r\\n```\\r\\n\\r\\n**Output**\\r\\nHere is what has been the output up to 2.2.1\\r\\n```js\\r\\n {\\r\\n   result: {\\r\\n  'getting-started': 'getting-started',\\r\\n  'front-end-development-certification': 'front-end-development-certification',\\r\\n  'data-visualization-certification': 'data-visualization-certification',\\r\\n  'back-end-development-certification': 'back-end-development-certification',\\r\\n  'video-challenges': 'video-challenges',\\r\\n  'full-stack-development-certification': 'full-stack-development-certification',\\r\\n  'coding-interview-preparation': 'coding-interview-preparation'\\r\\n},\\r\\n   entities: { ... }\\r\\n}\\r\\n```\\r\\n\\r\\nHere is what I actually as of 2.3.0\\r\\n\\r\\n\\r\\n```js\\r\\n{\\r\\n  result: [ [ \\r\\n  'getting-started',\\r\\n  'front-end-development-certification',\\r\\n  'data-visualization-certification',\\r\\n  'back-end-development-certification',\\r\\n  'video-challenges',\\r\\n  'full-stack-development-certification',\\r\\n  'coding-interview-preparation'\\r\\n  ],\\r\\n  entities: { ...}\\r\\n}\\r\\n```\\r\\n\"\n      },\n      \"196954427\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/187\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/187/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/187/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/187/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/187\",\n        \"id\": 196954427,\n        \"number\": 187,\n        \"title\": \"v3 Docs\",\n        \"user\": 33297,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": 33297,\n        \"assignees\": [\n          33297\n        ],\n        \"milestone\": 2205635,\n        \"comments\": 0,\n        \"created_at\": \"2016-12-21T15:04:39Z\",\n        \"updated_at\": \"2016-12-21T21:53:28Z\",\n        \"closed_at\": null,\n        \"body\": \"# Problem\\r\\n\\r\\nDocumentation in <= 2.x has been a bit difficult to follow, with lots of really long examples in a single README.\\r\\n\\r\\n# Solution\\r\\n\\r\\nFor v3, create a `/docs` folder and separate out meaningful documentation by section/page, with shorter, easier to follow examples.\"\n      },\n      \"196954928\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/188\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/188/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/188/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/188/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/188\",\n        \"id\": 196954928,\n        \"number\": 188,\n        \"title\": \"Add In-Repo Examples\",\n        \"user\": 33297,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": 33297,\n        \"assignees\": [\n          33297\n        ],\n        \"milestone\": 2205635,\n        \"comments\": 0,\n        \"created_at\": \"2016-12-21T15:06:29Z\",\n        \"updated_at\": \"2016-12-21T21:53:28Z\",\n        \"closed_at\": null,\n        \"body\": \"# Problem\\r\\n\\r\\nExamples are currently somewhere untracked by this project. They have moved or been lost in the past.\\r\\n\\r\\n# Solution\\r\\n\\r\\nAdd an `/examples` folder with clear and concise usage patterns.\"\n      },\n      \"196963939\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/189\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/189/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/189/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/189/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/issues/189\",\n        \"id\": 196963939,\n        \"number\": 189,\n        \"title\": \"Wanted: Typescript definitions & tests for v3.0.0\",\n        \"user\": 33297,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": 69485,\n        \"assignees\": [\n          69485\n        ],\n        \"milestone\": 2205635,\n        \"comments\": 2,\n        \"created_at\": \"2016-12-21T15:42:53Z\",\n        \"updated_at\": \"2016-12-22T14:57:41Z\",\n        \"closed_at\": null,\n        \"body\": \"# Problem\\r\\n\\r\\n@paularmstrong doesn't know typescript and is too lazy to learn\\r\\n\\r\\n# Solution\\r\\n\\r\\nTry to get someone from the community donate a little time adding a typescript definition file and tests for the [v3.0.0 branch](https://github.com/paularmstrong/normalizr/tree/master) before launch\"\n      }\n    },\n    \"pullRequests\": {\n      \"165986889\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/135\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/135/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/135/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/135/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/pull/135\",\n        \"id\": 165986889,\n        \"number\": 135,\n        \"title\": \"It should be possible to use relations\",\n        \"user\": 1690457,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 1,\n        \"created_at\": \"2016-07-17T17:38:38Z\",\n        \"updated_at\": \"2016-09-25T15:15:05Z\",\n        \"closed_at\": null,\n        \"pull_request\": {\n          \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/pulls/135\",\n          \"html_url\": \"https://github.com/paularmstrong/normalizr/pull/135\",\n          \"diff_url\": \"https://github.com/paularmstrong/normalizr/pull/135.diff\",\n          \"patch_url\": \"https://github.com/paularmstrong/normalizr/pull/135.patch\"\n        },\n        \"body\": \"# Problem\\n\\nSome objects have arrays with objects, which does not have a identifier, and is\\ntightly coupled to the parent object.\\n\\nI created a issue here: #134\\n# Solution\\n\\nIt should possible to mark those objects as a relation to the parent object\\nand use the parent object id as the identifier when normalizing\\n# TODO\\n- [x] Add & Update Tests\\n### Use case\\n\\nSee updated test\\n\"\n      },\n      \"183956234\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/161\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/161/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/161/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/161/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/pull/161\",\n        \"id\": 183956234,\n        \"number\": 161,\n        \"title\": \"Pass parent object to idAttribute.\",\n        \"user\": 1591483,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 9,\n        \"created_at\": \"2016-10-19T13:06:03Z\",\n        \"updated_at\": \"2016-11-29T18:51:34Z\",\n        \"closed_at\": null,\n        \"pull_request\": {\n          \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/pulls/161\",\n          \"html_url\": \"https://github.com/paularmstrong/normalizr/pull/161\",\n          \"diff_url\": \"https://github.com/paularmstrong/normalizr/pull/161.diff\",\n          \"patch_url\": \"https://github.com/paularmstrong/normalizr/pull/161.patch\"\n        },\n        \"body\": \"# Problem\\n\\nProvide a mechanism to use a parent object data in generating entity id.\\n# Solution\\n\\nPass the `last` object through all of the traversal methods and make sure it gets passed specifically to `visitEntity` as that is where `getId` is called and pass the parent object into that function. \\n\\nCloses #160\\n\"\n      },\n      \"188066564\": {\n        \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/173\",\n        \"repository_url\": \"https://api.github.com/repos/paularmstrong/normalizr\",\n        \"labels_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/173/labels{/name}\",\n        \"comments_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/173/comments\",\n        \"events_url\": \"https://api.github.com/repos/paularmstrong/normalizr/issues/173/events\",\n        \"html_url\": \"https://github.com/paularmstrong/normalizr/pull/173\",\n        \"id\": 188066564,\n        \"number\": 173,\n        \"title\": \"Added schema option for assigning the parent id on the child entity\",\n        \"user\": 1499050,\n        \"state\": \"open\",\n        \"locked\": false,\n        \"assignee\": null,\n        \"assignees\": [],\n        \"milestone\": null,\n        \"comments\": 2,\n        \"created_at\": \"2016-11-08T18:22:42Z\",\n        \"updated_at\": \"2016-12-21T15:00:24Z\",\n        \"closed_at\": null,\n        \"pull_request\": {\n          \"url\": \"https://api.github.com/repos/paularmstrong/normalizr/pulls/173\",\n          \"html_url\": \"https://github.com/paularmstrong/normalizr/pull/173\",\n          \"diff_url\": \"https://github.com/paularmstrong/normalizr/pull/173.diff\",\n          \"patch_url\": \"https://github.com/paularmstrong/normalizr/pull/173.patch\"\n        },\n        \"body\": \"# Problem\\r\\n\\r\\nCloses #172. Partially inspired by #135. \\r\\n\\r\\nBasically, we sometimes when normalizing, we would like to set the parent id on the child as well as setting the child ids on the parent. \\r\\n\\r\\nExample;\\r\\n\\r\\n```\\r\\n// INPUT \\r\\n\\r\\n[{\\r\\n  id: 1,\\r\\n  title: 'Some Article',\\r\\n  comment: {\\r\\n    id: 109,\\r\\n    content: 'Hai'\\r\\n  }\\r\\n}, {\\r\\n  id: 2,\\r\\n  title: 'Other Article',\\r\\n  comment: {\\r\\n    id: 110,\\r\\n    content: 'Gee'\\r\\n  }\\r\\n}]\\r\\n\\r\\n// OUTPUT\\r\\n\\r\\n{\\r\\n  result: [1, 2],\\r\\n  entities: {\\r\\n    articles: {\\r\\n      1: {\\r\\n        id: 1,\\r\\n        title: 'Some Article',\\r\\n        comments: [109]\\r\\n      },\\r\\n      2: {\\r\\n        id: 2,\\r\\n        title: 'Other Article',\\r\\n        comments: [110]\\r\\n      }\\r\\n    },\\r\\n    comments: {\\r\\n      109: {\\r\\n        id: 109,\\r\\n        content: 'Hai'\\r\\n        article: 1,\\r\\n      },\\r\\n      110: {\\r\\n        id: 110,\\r\\n        content: 'Gee'\\r\\n        article: 2,\\r\\n      }\\r\\n    }\\r\\n  }\\r\\n}\\r\\n```\\r\\n\\r\\n# Solution\\r\\n\\r\\nWhen defining the schema, you can set assignParentId to true and this will result in the child entity getting the parent object's id set. \\r\\n\\r\\n# TODO\\r\\n- [ ] Didn't cover the UnionSchema case. Let me know if that is a requirement to proceed. \\r\\n- [x] Add & Update Tests\\r\\n\\r\\n\"\n      }\n    }\n  },\n  \"result\": [\n    {\n      \"id\": 196963939,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 196954928,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 196954427,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 196531836,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 196525159,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 191124603,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 188066564,\n      \"schema\": \"pullRequests\"\n    },\n    {\n      \"id\": 188006036,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 186469900,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 183956234,\n      \"schema\": \"pullRequests\"\n    },\n    {\n      \"id\": 178773735,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 165986889,\n      \"schema\": \"pullRequests\"\n    },\n    {\n      \"id\": 161111737,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 154328158,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 154328039,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 134935104,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 134547187,\n      \"schema\": \"issues\"\n    },\n    {\n      \"id\": 127301608,\n      \"schema\": \"issues\"\n    }\n  ]\n}\n"
  },
  {
    "path": "examples/github/schema.js",
    "content": "import { schema } from '../../src';\n\nexport const user = new schema.Entity('users');\n\nexport const label = new schema.Entity('labels');\n\nexport const milestone = new schema.Entity('milestones', {\n  creator: user,\n});\n\nexport const issue = new schema.Entity('issues', {\n  assignee: user,\n  assignees: [user],\n  labels: label,\n  milestone,\n  user,\n});\n\nexport const pullRequest = new schema.Entity('pullRequests', {\n  assignee: user,\n  assignees: [user],\n  labels: label,\n  milestone,\n  user,\n});\n\nexport const issueOrPullRequest = new schema.Array(\n  {\n    issues: issue,\n    pullRequests: pullRequest,\n  },\n  (entity) => (entity.pull_request ? 'pullRequests' : 'issues')\n);\n"
  },
  {
    "path": "examples/redux/.babelrc",
    "content": "{\n  \"presets\": [\"es2015\", \"stage-1\"]\n}\n"
  },
  {
    "path": "examples/redux/.gitignore",
    "content": "node_modules/*\n*.log\n"
  },
  {
    "path": "examples/redux/README.md",
    "content": "# Redux\n\nThis is a simple example of using Normalizr with Redux and Redux-Thunk. The command-line utility allows you to pull some data from public GitHub repos and browse/display the normalized data as saved to the Redux state tree.\n\n![redux example in use](/examples/redux/usage.gif)\n\n## Running\n\nFrom this directory, run the following and follow the on-screen options:\n\n```sh\n# from the root directory:\nyarn # or npm install\n# from this directory:\nyarn # or npm install\nnpm run start\n```\n"
  },
  {
    "path": "examples/redux/index.js",
    "content": "import * as Action from './src/redux/actions';\nimport * as Selector from './src/redux/selectors';\nimport inquirer from 'inquirer';\nimport store from './src/redux';\n\nconst REPO = 'paularmstrong/normalizr';\n\nconst start = () => {\n  inquirer\n    .prompt([\n      {\n        type: 'input',\n        name: 'repo',\n        message: 'What is the slug of the repo you wish to browseMain?',\n        default: REPO,\n        validate: (input) => {\n          if (!/^[a-zA-Z0-9]+\\/[a-zA-Z0-9]+/.test(input)) {\n            return 'Repo slug must be in the form \"user/project\"';\n          }\n          return true;\n        },\n      },\n    ])\n    .then(({ repo }) => {\n      store.dispatch(Action.setRepo(repo));\n      main();\n    });\n};\n\nconst main = () => {\n  return inquirer\n    .prompt([\n      {\n        type: 'list',\n        name: 'action',\n        message: 'What would you like to do?',\n        choices: ['Browse current state', 'Get new data', new inquirer.Separator(), 'Quit'],\n      },\n    ])\n    .then(({ action }) => {\n      switch (action) {\n        case 'Browse current state':\n          return browseMain();\n        case 'Get new data':\n          return pull();\n        default:\n          return process.exit();\n      }\n    });\n};\n\nconst browseMain = () => {\n  return inquirer\n    .prompt([\n      {\n        type: 'list',\n        name: 'browseMainAction',\n        message: 'What would you like to do?',\n        choices: () => {\n          return [\n            { value: 'print', name: 'Print the entire state tree' },\n            new inquirer.Separator(),\n            ...Object.keys(store.getState()).map((value) => ({ value, name: `Browse ${value}` })),\n            new inquirer.Separator(),\n            { value: 'main', name: 'Go Back to Main Menu' },\n          ];\n        },\n      },\n    ])\n    .then((answers) => {\n      switch (answers.browseMainAction) {\n        case 'main':\n          return main();\n        case 'print':\n          console.log(JSON.stringify(store.getState(), null, 2));\n          return browseMain();\n        default:\n          return browse(answers.browseMainAction);\n      }\n    });\n};\n\nconst browse = (stateKey) => {\n  return inquirer\n    .prompt([\n      {\n        type: 'list',\n        name: 'action',\n        message: `Browse ${stateKey}`,\n        choices: [\n          { value: 'count', name: 'Show # of Objects' },\n          { value: 'keys', name: 'List All Keys' },\n          { value: 'view', name: 'View by Key' },\n          { value: 'all', name: 'View All' },\n          { value: 'denormalize', name: 'Denormalize' },\n          new inquirer.Separator(),\n          { value: 'browseMain', name: 'Go Back to Browse Menu' },\n          { value: 'main', name: 'Go Back to Main Menu' },\n        ],\n      },\n      {\n        type: 'list',\n        name: 'list',\n        message: `Select the ${stateKey} to view:`,\n        choices: Object.keys(store.getState()[stateKey]),\n        when: ({ action }) => action === 'view',\n      },\n    ])\n    .then(({ action, list }) => {\n      const state = store.getState()[stateKey];\n      if (list) {\n        console.log(JSON.stringify(state[list], null, 2));\n      }\n      switch (action) {\n        case 'count':\n          console.log(`-> ${Object.keys(state).length} items.`);\n          return browse(stateKey);\n        case 'keys':\n          Object.keys(state).map((key) => console.log(key));\n          return browse(stateKey);\n        case 'all':\n          console.log(JSON.stringify(state, null, 2));\n          return browse(stateKey);\n        case 'denormalize':\n          return browseDenormalized(stateKey);\n        case 'browseMain':\n          return browseMain();\n        case 'main':\n          return main();\n        default:\n          return browse(stateKey);\n      }\n    });\n};\n\nconst browseDenormalized = (stateKey) => {\n  return inquirer\n    .prompt([\n      {\n        type: 'list',\n        name: 'selector',\n        message: `Denormalize a/and ${stateKey} entity`,\n        choices: [\n          ...Object.keys(store.getState()[stateKey]),\n          new inquirer.Separator(),\n          { value: 'browse', name: 'Go Back to Browse Menu' },\n          { value: 'main', name: 'Go Back to Main Menu' },\n        ],\n      },\n    ])\n    .then(({ selector }) => {\n      switch (selector) {\n        case 'browse':\n          return browse(stateKey);\n        case 'main':\n          return main();\n        default: {\n          const data = Selector[`select${stateKey.replace(/s$/, '')}`](store.getState(), selector);\n          console.log(JSON.stringify(data, null, 2));\n          return browseDenormalized(stateKey);\n        }\n      }\n    });\n};\n\nconst pull = () => {\n  return inquirer\n    .prompt([\n      {\n        type: 'list',\n        name: 'pullAction',\n        message: 'What data would you like to fetch?',\n        choices: () => {\n          return [\n            ...Object.keys(store.getState()).map((value) => ({ value, name: value })),\n            new inquirer.Separator(),\n            { value: 'main', name: 'Go Back to Main Menu' },\n          ];\n        },\n      },\n    ])\n    .then((answers) => {\n      switch (answers.pullAction) {\n        case 'commits':\n          return store.dispatch(Action.getCommits()).then(pull);\n        case 'issues':\n          return store.dispatch(Action.getIssues()).then(pull);\n        case 'labels':\n          return store.dispatch(Action.getLabels()).then(pull);\n        case 'milestones':\n          return store.dispatch(Action.getMilestones()).then(pull);\n        case 'pullRequests':\n          return store.dispatch(Action.getPullRequests()).then(pull);\n        case 'main':\n        default:\n          return main();\n      }\n    });\n};\n\nstart();\n"
  },
  {
    "path": "examples/redux/package.json",
    "content": "{\n  \"name\": \"normalizr-redux-example\",\n  \"version\": \"0.0.0\",\n  \"description\": \"And example of using Normalizr with Redux\",\n  \"main\": \"index.js\",\n  \"author\": \"Paul Armstrong\",\n  \"license\": \"MIT\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"babel-node ./\"\n  },\n  \"dependencies\": {\n    \"babel-cli\": \"^6.18.0\",\n    \"babel-preset-es2015\": \"^6.18.0\",\n    \"babel-preset-stage-1\": \"^6.16.0\",\n    \"github\": \"^14.0.0\",\n    \"inquirer\": \"^6.3.1\",\n    \"redux\": \"^4.0.1\",\n    \"redux-thunk\": \"^2.1.0\"\n  }\n}\n"
  },
  {
    "path": "examples/redux/src/api/index.js",
    "content": "import GitHubApi from 'github';\n\nexport default new GitHubApi({\n  headers: {\n    'user-agent': 'Normalizr Redux Example',\n  },\n});\n"
  },
  {
    "path": "examples/redux/src/api/schema.js",
    "content": "import { schema } from '../../../../src';\n\nexport const user = new schema.Entity('users');\n\nexport const commit = new schema.Entity(\n  'commits',\n  {\n    author: user,\n    committer: user,\n  },\n  { idAttribute: 'sha' }\n);\n\nexport const label = new schema.Entity('labels');\n\nexport const milestone = new schema.Entity('milestones', {\n  creator: user,\n});\n\nexport const issue = new schema.Entity('issues', {\n  assignee: user,\n  assignees: [user],\n  labels: [label],\n  milestone,\n  user,\n});\n\nexport const pullRequest = new schema.Entity('pullRequests', {\n  assignee: user,\n  assignees: [user],\n  labels: [label],\n  milestone,\n  user,\n});\n\nexport const issueOrPullRequest = new schema.Array(\n  {\n    issues: issue,\n    pullRequests: pullRequest,\n  },\n  (entity) => (entity.pull_request ? 'pullRequests' : 'issues')\n);\n"
  },
  {
    "path": "examples/redux/src/redux/actions.js",
    "content": "export { getCommits } from './modules/commits';\nexport { getIssues } from './modules/issues';\nexport { getLabels } from './modules/labels';\nexport { getMilestones } from './modules/milestones';\nexport { getPullRequests } from './modules/pull-requests';\nexport { setRepo } from './modules/repos';\n\nexport const ADD_ENTITIES = 'ADD_ENTITIES';\nexport const addEntities = (entities) => ({\n  type: ADD_ENTITIES,\n  payload: entities,\n});\n"
  },
  {
    "path": "examples/redux/src/redux/index.js",
    "content": "import * as schema from '../api/schema';\nimport api from '../api';\nimport reducer from './reducer';\nimport thunk from 'redux-thunk';\nimport { applyMiddleware, createStore } from 'redux';\n\nexport default createStore(reducer, applyMiddleware(thunk.withExtraArgument({ api, schema })));\n"
  },
  {
    "path": "examples/redux/src/redux/modules/commits.js",
    "content": "import * as Repo from './repos';\nimport { commit } from '../../api/schema';\nimport { ADD_ENTITIES, addEntities } from '../actions';\nimport { denormalize, normalize } from '../../../../../src';\n\nexport const STATE_KEY = 'commits';\n\nexport default function reducer(state = {}, action) {\n  switch (action.type) {\n    case ADD_ENTITIES:\n      return {\n        ...state,\n        ...action.payload.commits,\n      };\n\n    default:\n      return state;\n  }\n}\n\nexport const getCommits = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => {\n  const state = getState();\n  const owner = Repo.selectOwner(state);\n  const repo = Repo.selectRepo(state);\n  return api.repos\n    .getCommits({\n      owner,\n      repo,\n    })\n    .then((response) => {\n      const data = normalize(response, [schema.commit]);\n      dispatch(addEntities(data.entities));\n      return response;\n    })\n    .catch((error) => {\n      console.error(error);\n    });\n};\n\nexport const selectHydrated = (state, id) => denormalize(id, commit, state);\n"
  },
  {
    "path": "examples/redux/src/redux/modules/issues.js",
    "content": "import * as Repo from './repos';\nimport { issue } from '../../api/schema';\nimport { ADD_ENTITIES, addEntities } from '../actions';\nimport { denormalize, normalize } from '../../../../../src';\n\nexport const STATE_KEY = 'issues';\n\nexport default function reducer(state = {}, action) {\n  switch (action.type) {\n    case ADD_ENTITIES:\n      return {\n        ...state,\n        ...action.payload.issues,\n      };\n\n    default:\n      return state;\n  }\n}\n\nexport const getIssues = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => {\n  const state = getState();\n  const owner = Repo.selectOwner(state);\n  const repo = Repo.selectRepo(state);\n  return api.issues\n    .getForRepo({\n      owner,\n      repo,\n    })\n    .then((response) => {\n      const data = normalize(response, [schema.issue]);\n      dispatch(addEntities(data.entities));\n      return response;\n    })\n    .catch((error) => {\n      console.error(error);\n    });\n};\n\nexport const selectHydrated = (state, id) => denormalize(id, issue, state);\n"
  },
  {
    "path": "examples/redux/src/redux/modules/labels.js",
    "content": "import * as Repo from './repos';\nimport { label } from '../../api/schema';\nimport { ADD_ENTITIES, addEntities } from '../actions';\nimport { denormalize, normalize } from '../../../../../src';\n\nexport const STATE_KEY = 'labels';\n\nexport default function reducer(state = {}, action) {\n  switch (action.type) {\n    case ADD_ENTITIES:\n      return {\n        ...state,\n        ...action.payload.labels,\n      };\n\n    default:\n      return state;\n  }\n}\n\nexport const getLabels = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => {\n  const state = getState();\n  const owner = Repo.selectOwner(state);\n  const repo = Repo.selectRepo(state);\n  return api.issues\n    .getLabels({\n      owner,\n      repo,\n    })\n    .then((response) => {\n      const data = normalize(response, [schema.label]);\n      dispatch(addEntities(data.entities));\n      return response;\n    })\n    .catch((error) => {\n      console.error(error);\n    });\n};\n\nexport const selectHydrated = (state, id) => denormalize(id, label, state);\n"
  },
  {
    "path": "examples/redux/src/redux/modules/milestones.js",
    "content": "import * as Repo from './repos';\nimport { milestone } from '../../api/schema';\nimport { ADD_ENTITIES, addEntities } from '../actions';\nimport { denormalize, normalize } from '../../../../../src';\n\nexport const STATE_KEY = 'milestones';\n\nexport default function reducer(state = {}, action) {\n  switch (action.type) {\n    case ADD_ENTITIES:\n      return {\n        ...state,\n        ...action.payload.milestones,\n      };\n\n    default:\n      return state;\n  }\n}\n\nexport const getMilestones = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => {\n  const state = getState();\n  const owner = Repo.selectOwner(state);\n  const repo = Repo.selectRepo(state);\n  return api.issues\n    .getMilestones({\n      owner,\n      repo,\n    })\n    .then((response) => {\n      const data = normalize(response, [schema.milestone]);\n      dispatch(addEntities(data.entities));\n      return response;\n    })\n    .catch((error) => {\n      console.error(error);\n    });\n};\n\nexport const selectHydrated = (state, id) => denormalize(id, milestone, state);\n"
  },
  {
    "path": "examples/redux/src/redux/modules/pull-requests.js",
    "content": "import * as Repo from './repos';\nimport { pullRequest } from '../../api/schema';\nimport { ADD_ENTITIES, addEntities } from '../actions';\nimport { denormalize, normalize } from '../../../../../src';\n\nexport const STATE_KEY = 'pullRequests';\n\nexport default function reducer(state = {}, action) {\n  switch (action.type) {\n    case ADD_ENTITIES:\n      return {\n        ...state,\n        ...action.payload.pullRequests,\n      };\n\n    default:\n      return state;\n  }\n}\n\nexport const getPullRequests = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => {\n  const state = getState();\n  const owner = Repo.selectOwner(state);\n  const repo = Repo.selectRepo(state);\n  return api.pullRequests\n    .getAll({\n      owner,\n      repo,\n    })\n    .then((response) => {\n      const data = normalize(response, [schema.pullRequest]);\n      dispatch(addEntities(data.entities));\n      return response;\n    })\n    .catch((error) => {\n      console.error(error);\n    });\n};\n\nexport const selectHydrated = (state, id) => denormalize(id, pullRequest, state);\n"
  },
  {
    "path": "examples/redux/src/redux/modules/repos.js",
    "content": "export const STATE_KEY = 'repo';\n\nexport default function reducer(state = {}, action) {\n  switch (action.type) {\n    case Action.SET_REPO:\n      return {\n        ...state,\n        ...action.payload,\n      };\n\n    default:\n      return state;\n  }\n}\n\nconst Action = {\n  SET_REPO: 'SET_REPO',\n};\n\nexport const setRepo = (slug) => {\n  const [owner, repo] = slug.split('/');\n  return {\n    type: Action.SET_REPO,\n    payload: { owner, repo },\n  };\n};\n\nexport const selectOwner = (state) => state[STATE_KEY].owner;\nexport const selectRepo = (state) => state[STATE_KEY].repo;\n"
  },
  {
    "path": "examples/redux/src/redux/modules/users.js",
    "content": "import { ADD_ENTITIES } from '../actions';\nimport { denormalize } from '../../../../../src';\nimport { user } from '../../api/schema';\n\nexport const STATE_KEY = 'users';\n\nexport default function reducer(state = {}, action) {\n  switch (action.type) {\n    case ADD_ENTITIES:\n      return Object.entries(action.payload.users).reduce((mergedUsers, [id, user]) => {\n        return {\n          ...mergedUsers,\n          [id]: {\n            ...(mergedUsers[id] || {}),\n            ...user,\n          },\n        };\n      }, state);\n\n    default:\n      return state;\n  }\n}\n\nexport const selectHydrated = (state, id) => denormalize(id, user, state);\n"
  },
  {
    "path": "examples/redux/src/redux/reducer.js",
    "content": "import { combineReducers } from 'redux';\nimport commits, { STATE_KEY as COMMITS_STATE_KEY } from './modules/commits';\nimport issues, { STATE_KEY as ISSUES_STATE_KEY } from './modules/issues';\nimport labels, { STATE_KEY as LABELS_STATE_KEY } from './modules/labels';\nimport milestones, { STATE_KEY as MILESTONES_STATE_KEY } from './modules/milestones';\nimport pullRequests, { STATE_KEY as PULLREQUESTS_STATE_KEY } from './modules/pull-requests';\nimport repos, { STATE_KEY as REPO_STATE_KEY } from './modules/repos';\nimport users, { STATE_KEY as USERS_STATE_KEY } from './modules/users';\n\nconst reducer = combineReducers({\n  [COMMITS_STATE_KEY]: commits,\n  [ISSUES_STATE_KEY]: issues,\n  [LABELS_STATE_KEY]: labels,\n  [MILESTONES_STATE_KEY]: milestones,\n  [PULLREQUESTS_STATE_KEY]: pullRequests,\n  [REPO_STATE_KEY]: repos,\n  [USERS_STATE_KEY]: users,\n});\n\nexport default reducer;\n"
  },
  {
    "path": "examples/redux/src/redux/selectors.js",
    "content": "export { selectHydrated as selectcommit } from './modules/commits';\nexport { selectHydrated as selectissue } from './modules/issues';\nexport { selectHydrated as selectlabel } from './modules/labels';\nexport { selectHydrated as selectmilestone } from './modules/milestones';\nexport { selectHydrated as selectpullRequest } from './modules/pull-requests';\n"
  },
  {
    "path": "examples/relationships/README.md",
    "content": "# Dealing with Relationships\n\nOccasionally, it is useful to have all one-to-one, one-to-many, and many-to-many relationship data on entities. Normalizr does not handle this automatically, but this example shows a simple way of adding relationship handling on a special-case basis.\n\n## Running\n\n```sh\n# from the root directory:\nyarn\n# from this directory:\n../../node_modules/.bin/babel-node ./index.js\n```\n\n## Files\n\n* [index.js](/examples/relationships/index.js): Pulls live data from the GitHub API for this project's issues and normalizes the JSON.\n* [input.json](/examples/relationships/input.json): The raw JSON data before normalization.\n* [output.json](/examples/relationships/output.json): The normalized output.\n* [schema.js](/examples/relationships/schema.js): The schema used to normalize the GitHub issues.\n"
  },
  {
    "path": "examples/relationships/index.js",
    "content": "import fs from 'fs';\nimport input from './input.json';\nimport { normalize } from '../../src';\nimport path from 'path';\nimport postsSchema from './schema';\n\nconst normalizedData = normalize(input, postsSchema);\nconst output = JSON.stringify(normalizedData, null, 2);\nfs.writeFileSync(path.resolve(__dirname, './output.json'), output);\n"
  },
  {
    "path": "examples/relationships/input.json",
    "content": "[\n    {\n        \"id\": \"1\",\n        \"title\": \"My first post!\",\n        \"author\": {\n            \"id\": \"123\",\n            \"name\": \"Paul\"\n        },\n        \"comments\": [\n            {\n                \"id\": \"249\",\n                \"content\": \"Nice post!\",\n                \"commenter\": {\n                    \"id\": \"245\",\n                    \"name\": \"Jane\"\n                }\n            },\n            {\n                \"id\": \"250\",\n                \"content\": \"Thanks!\",\n                \"commenter\": {\n                    \"id\": \"123\",\n                    \"name\": \"Paul\"\n                }\n            }\n        ]\n    },\n    {\n        \"id\": \"2\",\n        \"title\": \"This other post\",\n        \"author\": {\n            \"id\": \"123\",\n            \"name\": \"Paul\"\n        },\n        \"comments\": [\n            {\n                \"id\": \"251\",\n                \"content\": \"Your other post was nicer\",\n                \"commenter\": {\n                    \"id\": \"245\",\n                    \"name\": \"Jane\"\n                }\n            },\n            {\n                \"id\": \"252\",\n                \"content\": \"I am a spammer!\",\n                \"commenter\": {\n                    \"id\": \"246\",\n                    \"name\": \"Spambot5000\"\n                }\n            }\n        ]\n    }\n]\n"
  },
  {
    "path": "examples/relationships/output.json",
    "content": "{\n  \"entities\": {\n    \"users\": {\n      \"123\": {\n        \"id\": \"123\",\n        \"name\": \"Paul\",\n        \"posts\": [\n          \"1\",\n          \"2\"\n        ],\n        \"comments\": [\n          \"250\"\n        ]\n      },\n      \"245\": {\n        \"id\": \"245\",\n        \"name\": \"Jane\",\n        \"comments\": [\n          \"249\",\n          \"251\"\n        ],\n        \"posts\": []\n      },\n      \"246\": {\n        \"id\": \"246\",\n        \"name\": \"Spambot5000\",\n        \"comments\": [\n          \"252\"\n        ]\n      }\n    },\n    \"comments\": {\n      \"249\": {\n        \"id\": \"249\",\n        \"content\": \"Nice post!\",\n        \"commenter\": \"245\",\n        \"post\": \"1\"\n      },\n      \"250\": {\n        \"id\": \"250\",\n        \"content\": \"Thanks!\",\n        \"commenter\": \"123\",\n        \"post\": \"1\"\n      },\n      \"251\": {\n        \"id\": \"251\",\n        \"content\": \"Your other post was nicer\",\n        \"commenter\": \"245\",\n        \"post\": \"2\"\n      },\n      \"252\": {\n        \"id\": \"252\",\n        \"content\": \"I am a spammer!\",\n        \"commenter\": \"246\",\n        \"post\": \"2\"\n      }\n    },\n    \"posts\": {\n      \"1\": {\n        \"id\": \"1\",\n        \"title\": \"My first post!\",\n        \"author\": \"123\",\n        \"comments\": [\n          \"249\",\n          \"250\"\n        ]\n      },\n      \"2\": {\n        \"id\": \"2\",\n        \"title\": \"This other post\",\n        \"author\": \"123\",\n        \"comments\": [\n          \"251\",\n          \"252\"\n        ]\n      }\n    }\n  },\n  \"result\": [\n    \"1\",\n    \"2\"\n  ]\n}"
  },
  {
    "path": "examples/relationships/schema.js",
    "content": "import { schema } from '../../src';\n\nconst userProcessStrategy = (value, parent, key) => {\n  switch (key) {\n    case 'author':\n      return { ...value, posts: [parent.id] };\n    case 'commenter':\n      return { ...value, comments: [parent.id] };\n    default:\n      return { ...value };\n  }\n};\n\nconst userMergeStrategy = (entityA, entityB) => {\n  return {\n    ...entityA,\n    ...entityB,\n    posts: [...(entityA.posts || []), ...(entityB.posts || [])],\n    comments: [...(entityA.comments || []), ...(entityB.comments || [])],\n  };\n};\n\nconst user = new schema.Entity(\n  'users',\n  {},\n  {\n    mergeStrategy: userMergeStrategy,\n    processStrategy: userProcessStrategy,\n  }\n);\n\nconst comment = new schema.Entity(\n  'comments',\n  {\n    commenter: user,\n  },\n  {\n    processStrategy: (value, parent, key) => {\n      return { ...value, post: parent.id };\n    },\n  }\n);\n\nconst post = new schema.Entity('posts', {\n  author: user,\n  comments: [comment],\n});\n\nexport default [post];\n"
  },
  {
    "path": "husky.config.js",
    "content": "const runYarnLock = 'yarn install --frozen-lockfile';\n\nmodule.exports = {\n  hooks: {\n    'post-checkout': `if [[ $HUSKY_GIT_PARAMS =~ 1$ ]]; then ${runYarnLock}; fi`,\n    'post-merge': runYarnLock,\n    'post-rebase': 'yarn install',\n    'pre-commit': 'yarn typecheck && yarn lint-staged',\n  },\n};\n"
  },
  {
    "path": "index.d.ts",
    "content": "declare namespace schema {\n  export type StrategyFunction<T> = (value: any, parent: any, key: string) => T;\n  export type SchemaFunction = (value: any, parent: any, key: string) => string;\n  export type MergeFunction = (entityA: any, entityB: any) => any;\n  export type FallbackFunction<T> = (key: string, schema: schema.Entity<T>) => T;\n\n  export class Array<T = any> {\n    constructor(definition: Schema<T>, schemaAttribute?: string | SchemaFunction)\n    define(definition: Schema): void\n  }\n\n  export interface EntityOptions<T = any> {\n    idAttribute?: string | SchemaFunction\n    mergeStrategy?: MergeFunction\n    processStrategy?: StrategyFunction<T>\n    fallbackStrategy?: FallbackFunction<T>\n  }\n\n  export class Entity<T = any> {\n    constructor(key: string | symbol, definition?: Schema, options?: EntityOptions<T>)\n    define(definition: Schema): void\n    key: string\n    getId: SchemaFunction\n    _processStrategy: StrategyFunction<T>\n  }\n\n  export class Object<T = any> {\n    constructor(definition: SchemaObject<T>)\n    define(definition: Schema): void\n  }\n\n  export class Union<T = any> {\n    constructor(definition: Schema<T>, schemaAttribute?: string | SchemaFunction)\n    define(definition: Schema): void\n  }\n\n  export class Values<T = any> {\n    constructor(definition: Schema<T>, schemaAttribute?: string | SchemaFunction)\n    define(definition: Schema): void\n  }\n}\n\nexport type Schema<T = any> =\n  | schema.Entity<T>\n  | schema.Object<T>\n  | schema.Union<T>\n  | schema.Values<T>\n  | SchemaObject<T>\n  | SchemaArray<T>;\n\nexport type SchemaValueFunction<T> = (t: T) => Schema<T>;\nexport type SchemaValue<T> = Schema<T> | SchemaValueFunction<T>;\n\nexport interface SchemaObject<T> {\n  [key: string]: SchemaValue<T>\n}\n\nexport interface SchemaArray<T> extends Array<Schema<T>> {}\n\nexport type NormalizedSchema<E, R> = { entities: E, result: R };\n\nexport function normalize<T = any, E = { [key:string]: { [key:string]: T } | undefined}, R = any>(\n  data: any,\n  schema: Schema<T>\n): NormalizedSchema<E, R>;\n\nexport function denormalize(\n  input: any,\n  schema: Schema,\n  entities: any\n): any;\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n  testMatch: ['**/__tests__/**/*.test.js'],\n};\n"
  },
  {
    "path": "lint-staged.config.js",
    "content": "module.exports = {\n  '*.{md}': ['prettier --write', 'git add'],\n  '*.{js,jsx,json}': ['yarn lint', 'prettier --write', 'git add'],\n  '*.{js,jsx,ts,tsx}': ['jest --bail --findRelatedTests'],\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"normalizr\",\n  \"version\": \"3.6.2\",\n  \"description\": \"Normalizes and denormalizes JSON according to schema for Redux and Flux applications\",\n  \"bugs\": {\n    \"url\": \"https://github.com/paularmstrong/normalizr/issues\"\n  },\n  \"homepage\": \"https://github.com/paularmstrong/normalizr\",\n  \"repository\": {\n    \"url\": \"https://github.com/paularmstrong/normalizr.git\",\n    \"type\": \"git\"\n  },\n  \"keywords\": [\n    \"flux\",\n    \"redux\",\n    \"normalize\",\n    \"denormalize\",\n    \"api\",\n    \"json\"\n  ],\n  \"files\": [\n    \"dist/\",\n    \"index.d.ts\",\n    \"LICENSE\",\n    \"README.md\"\n  ],\n  \"main\": \"dist/normalizr.js\",\n  \"module\": \"dist/normalizr.es.js\",\n  \"typings\": \"index.d.ts\",\n  \"sideEffects\": false,\n  \"scripts\": {\n    \"build\": \"npm run clean && run-p build:*\",\n    \"build:development\": \"NODE_ENV=development rollup -c\",\n    \"build:production\": \"NODE_ENV=production rollup -c\",\n    \"clean\": \"rimraf dist\",\n    \"flow\": \"flow\",\n    \"flow:ci\": \"flow check\",\n    \"lint\": \"yarn lint:cmd --fix\",\n    \"lint:ci\": \"yarn lint:cmd\",\n    \"lint:cmd\": \"eslint . --ext '.js,.json,.snap' --cache\",\n    \"prebuild\": \"npm run clean\",\n    \"precommit\": \"flow check && lint-staged\",\n    \"prepublishOnly\": \"npm run build\",\n    \"test\": \"jest\",\n    \"test:ci\": \"jest --ci\",\n    \"test:coverage\": \"npm run test -- --coverage && cat ./coverage/lcov.info | coveralls\",\n    \"tsc:ci\": \"tsc --noEmit typescript-tests/*\",\n    \"typecheck\": \"run-p flow:ci tsc:ci\"\n  },\n  \"author\": \"Paul Armstrong\",\n  \"contributors\": [\n    \"Dan Abramov\"\n  ],\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.0.0\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.0.0\",\n    \"@babel/plugin-proposal-object-rest-spread\": \"^7.0.0\",\n    \"@babel/preset-env\": \"^7.0.0\",\n    \"@babel/preset-flow\": \"^7.0.0\",\n    \"babel-eslint\": \"^10.0.1\",\n    \"babel-jest\": \"^26.5.2\",\n    \"coveralls\": \"^3.1.0\",\n    \"eslint\": \"^7.11.0\",\n    \"eslint-config-prettier\": \"^6.13.0\",\n    \"eslint-plugin-jest\": \"^24.1.0\",\n    \"eslint-plugin-json\": \"^2.1.2\",\n    \"eslint-plugin-prettier\": \"^3.1.4\",\n    \"flow-bin\": \"^0.136.0\",\n    \"husky\": \"^2.3.0\",\n    \"immutable\": \"^3.8.1\",\n    \"jest\": \"^26.5.3\",\n    \"lint-staged\": \"^8.1.7\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"prettier\": \"^2.1.2\",\n    \"rimraf\": \"^3.0.2\",\n    \"rollup\": \"^2.32.0\",\n    \"rollup-plugin-babel\": \"^4.4.0\",\n    \"rollup-plugin-filesize\": \"^9.0.2\",\n    \"rollup-plugin-terser\": \"^7.0.2\",\n    \"typescript\": \"^3.4.5\"\n  },\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "prettier.config.js",
    "content": "module.exports = {\n  'arrowParens': 'always',\n  'printWidth': 120,\n  'singleQuote': true,\n  'quoteProps': 'preserve',\n};\n"
  },
  {
    "path": "rollup.config.js",
    "content": "import babel from 'rollup-plugin-babel';\nimport filesize from 'rollup-plugin-filesize';\nimport { name } from './package.json';\nimport { terser } from 'rollup-plugin-terser';\n\nconst isProduction = process.env.NODE_ENV === 'production';\n\nconst destBase = 'dist/normalizr';\nconst destExtension = `${isProduction ? '.min' : ''}.js`;\n\nexport default {\n  input: 'src/index.js',\n  output: [\n    { file: `${destBase}${destExtension}`, format: 'cjs' },\n    { file: `${destBase}.es${destExtension}`, format: 'es' },\n    { file: `${destBase}.umd${destExtension}`, format: 'umd', name },\n    { file: `${destBase}.amd${destExtension}`, format: 'amd', name },\n    { file: `${destBase}.browser${destExtension}`, format: 'iife', name },\n  ],\n  plugins: [babel({}), isProduction && terser(), filesize()].filter(Boolean),\n};\n"
  },
  {
    "path": "src/__tests__/__snapshots__/index.test.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`denormalize denormalizes entities 1`] = `\nArray [\n  Object {\n    \"id\": 1,\n    \"type\": \"foo\",\n  },\n  Object {\n    \"id\": 2,\n    \"type\": \"bar\",\n  },\n]\n`;\n\nexports[`denormalize denormalizes nested entities 1`] = `\nObject {\n  \"author\": Object {\n    \"id\": \"8472\",\n    \"name\": \"Paul\",\n  },\n  \"body\": \"This article is great.\",\n  \"comments\": Array [\n    Object {\n      \"comment\": \"I like it!\",\n      \"id\": \"comment-123-4738\",\n      \"user\": Object {\n        \"id\": \"10293\",\n        \"name\": \"Jane\",\n      },\n    },\n  ],\n  \"id\": \"123\",\n  \"title\": \"A Great Article\",\n}\n`;\n\nexports[`denormalize denormalizes with function as idAttribute 1`] = `\nArray [\n  Object {\n    \"guest\": null,\n    \"id\": \"1\",\n    \"name\": \"Esther\",\n  },\n  Object {\n    \"guest\": Object {\n      \"guest_id\": 1,\n    },\n    \"id\": \"2\",\n    \"name\": \"Tom\",\n  },\n]\n`;\n\nexports[`denormalize set to undefined if schema key is not in entities 1`] = `\nObject {\n  \"author\": undefined,\n  \"comments\": Array [\n    Object {\n      \"user\": undefined,\n    },\n  ],\n  \"id\": \"123\",\n}\n`;\n\nexports[`normalize can normalize entity nested inside entity using property from parent 1`] = `\nObject {\n  \"entities\": Object {\n    \"linkables\": Object {\n      \"1\": Object {\n        \"data\": 2,\n        \"id\": 1,\n        \"module_type\": \"article\",\n        \"schema_type\": \"media\",\n      },\n    },\n    \"media\": Object {\n      \"2\": Object {\n        \"id\": 2,\n        \"url\": \"catimage.jpg\",\n      },\n    },\n  },\n  \"result\": 1,\n}\n`;\n\nexports[`normalize can normalize entity nested inside object using property from parent 1`] = `\nObject {\n  \"entities\": Object {\n    \"media\": Object {\n      \"2\": Object {\n        \"id\": 2,\n        \"url\": \"catimage.jpg\",\n      },\n    },\n  },\n  \"result\": Object {\n    \"data\": 2,\n    \"id\": 1,\n    \"module_type\": \"article\",\n    \"schema_type\": \"media\",\n  },\n}\n`;\n\nexports[`normalize can use fully custom entity classes 1`] = `\nObject {\n  \"entities\": Object {\n    \"children\": Object {\n      \"4\": Object {\n        \"id\": 4,\n        \"name\": \"lettuce\",\n      },\n    },\n    \"food\": Object {\n      \"1234\": Object {\n        \"children\": Array [\n          4,\n        ],\n        \"name\": \"tacos\",\n        \"uuid\": \"1234\",\n      },\n    },\n  },\n  \"result\": Object {\n    \"schema\": \"food\",\n    \"uuid\": \"1234\",\n  },\n}\n`;\n\nexports[`normalize ignores null values 1`] = `\nObject {\n  \"entities\": Object {},\n  \"result\": Array [\n    null,\n  ],\n}\n`;\n\nexports[`normalize ignores null values 2`] = `\nObject {\n  \"entities\": Object {},\n  \"result\": Array [\n    undefined,\n  ],\n}\n`;\n\nexports[`normalize ignores null values 3`] = `\nObject {\n  \"entities\": Object {},\n  \"result\": Array [\n    false,\n  ],\n}\n`;\n\nexports[`normalize normalizes entities 1`] = `\nObject {\n  \"entities\": Object {\n    \"tacos\": Object {\n      \"1\": Object {\n        \"id\": 1,\n        \"type\": \"foo\",\n      },\n      \"2\": Object {\n        \"id\": 2,\n        \"type\": \"bar\",\n      },\n    },\n  },\n  \"result\": Array [\n    1,\n    2,\n  ],\n}\n`;\n\nexports[`normalize normalizes entities with circular references 1`] = `\nObject {\n  \"entities\": Object {\n    \"users\": Object {\n      \"123\": Object {\n        \"friends\": Array [\n          123,\n        ],\n        \"id\": 123,\n      },\n    },\n  },\n  \"result\": 123,\n}\n`;\n\nexports[`normalize normalizes nested entities 1`] = `\nObject {\n  \"entities\": Object {\n    \"articles\": Object {\n      \"123\": Object {\n        \"author\": \"8472\",\n        \"body\": \"This article is great.\",\n        \"comments\": Array [\n          \"comment-123-4738\",\n        ],\n        \"id\": \"123\",\n        \"title\": \"A Great Article\",\n      },\n    },\n    \"comments\": Object {\n      \"comment-123-4738\": Object {\n        \"comment\": \"I like it!\",\n        \"id\": \"comment-123-4738\",\n        \"user\": \"10293\",\n      },\n    },\n    \"users\": Object {\n      \"10293\": Object {\n        \"id\": \"10293\",\n        \"name\": \"Jane\",\n      },\n      \"8472\": Object {\n        \"id\": \"8472\",\n        \"name\": \"Paul\",\n      },\n    },\n  },\n  \"result\": \"123\",\n}\n`;\n\nexports[`normalize passes over pre-normalized values 1`] = `\nObject {\n  \"entities\": Object {\n    \"articles\": Object {\n      \"123\": Object {\n        \"author\": 1,\n        \"id\": \"123\",\n        \"title\": \"normalizr is great!\",\n      },\n    },\n  },\n  \"result\": \"123\",\n}\n`;\n\nexports[`normalize uses the non-normalized input when getting the ID for an entity 1`] = `\nObject {\n  \"entities\": Object {\n    \"recommendations\": Object {\n      \"456\": Object {\n        \"user\": \"456\",\n      },\n    },\n    \"users\": Object {\n      \"456\": Object {\n        \"id\": \"456\",\n      },\n    },\n  },\n  \"result\": \"456\",\n}\n`;\n\nexports[`normalize uses the non-normalized input when getting the ID for an entity 2`] = `\nArray [\n  Array [\n    Object {\n      \"user\": Object {\n        \"id\": \"456\",\n      },\n    },\n    Object {\n      \"user\": Object {\n        \"id\": \"456\",\n      },\n    },\n    null,\n  ],\n  Array [\n    Object {\n      \"user\": Object {\n        \"id\": \"456\",\n      },\n    },\n    Object {\n      \"user\": Object {\n        \"id\": \"456\",\n      },\n    },\n    null,\n  ],\n]\n`;\n"
  },
  {
    "path": "src/__tests__/index.test.js",
    "content": "// eslint-env jest\nimport { denormalize, normalize, schema } from '../';\n\ndescribe('normalize', () => {\n  [42, null, undefined, '42', () => {}].forEach((input) => {\n    test(`cannot normalize input that == ${input}`, () => {\n      expect(() => normalize(input, new schema.Entity('test'))).toThrow();\n    });\n  });\n\n  test('cannot normalize without a schema', () => {\n    expect(() => normalize({})).toThrow();\n  });\n\n  test('cannot normalize with null input', () => {\n    const mySchema = new schema.Entity('tacos');\n    expect(() => normalize(null, mySchema)).toThrow(/null/);\n  });\n\n  test('normalizes entities', () => {\n    const mySchema = new schema.Entity('tacos');\n\n    expect(\n      normalize(\n        [\n          { id: 1, type: 'foo' },\n          { id: 2, type: 'bar' },\n        ],\n        [mySchema]\n      )\n    ).toMatchSnapshot();\n  });\n\n  test('normalizes entities with circular references', () => {\n    const user = new schema.Entity('users');\n    user.define({\n      friends: [user],\n    });\n\n    const input = { id: 123, friends: [] };\n    input.friends.push(input);\n\n    expect(normalize(input, user)).toMatchSnapshot();\n  });\n\n  test('normalizes nested entities', () => {\n    const user = new schema.Entity('users');\n    const comment = new schema.Entity('comments', {\n      user: user,\n    });\n    const article = new schema.Entity('articles', {\n      author: user,\n      comments: [comment],\n    });\n\n    const input = {\n      id: '123',\n      title: 'A Great Article',\n      author: {\n        id: '8472',\n        name: 'Paul',\n      },\n      body: 'This article is great.',\n      comments: [\n        {\n          id: 'comment-123-4738',\n          comment: 'I like it!',\n          user: {\n            id: '10293',\n            name: 'Jane',\n          },\n        },\n      ],\n    };\n    expect(normalize(input, article)).toMatchSnapshot();\n  });\n\n  test('does not modify the original input', () => {\n    const user = new schema.Entity('users');\n    const article = new schema.Entity('articles', { author: user });\n    const input = Object.freeze({\n      id: '123',\n      title: 'A Great Article',\n      author: Object.freeze({\n        id: '8472',\n        name: 'Paul',\n      }),\n    });\n    expect(() => normalize(input, article)).not.toThrow();\n  });\n\n  test('ignores null values', () => {\n    const myEntity = new schema.Entity('myentities');\n    expect(normalize([null], [myEntity])).toMatchSnapshot();\n    expect(normalize([undefined], [myEntity])).toMatchSnapshot();\n    expect(normalize([false], [myEntity])).toMatchSnapshot();\n  });\n\n  test('can use fully custom entity classes', () => {\n    class MyEntity extends schema.Entity {\n      schema = {\n        children: [new schema.Entity('children')],\n      };\n\n      getId(entity, parent, key) {\n        return entity.uuid;\n      }\n\n      normalize(input, parent, key, visit, addEntity, visitedEntities) {\n        const entity = { ...input };\n        Object.keys(this.schema).forEach((key) => {\n          const schema = this.schema[key];\n          entity[key] = visit(input[key], input, key, schema, addEntity, visitedEntities);\n        });\n        addEntity(this, entity, parent, key);\n        return {\n          uuid: this.getId(entity),\n          schema: this.key,\n        };\n      }\n    }\n\n    const mySchema = new MyEntity('food');\n    expect(\n      normalize(\n        {\n          uuid: '1234',\n          name: 'tacos',\n          children: [{ id: 4, name: 'lettuce' }],\n        },\n        mySchema\n      )\n    ).toMatchSnapshot();\n  });\n\n  test('uses the non-normalized input when getting the ID for an entity', () => {\n    const userEntity = new schema.Entity('users');\n    const idAttributeFn = jest.fn((nonNormalized, parent, key) => nonNormalized.user.id);\n    const recommendation = new schema.Entity(\n      'recommendations',\n      { user: userEntity },\n      {\n        idAttribute: idAttributeFn,\n      }\n    );\n    expect(normalize({ user: { id: '456' } }, recommendation)).toMatchSnapshot();\n    expect(idAttributeFn.mock.calls).toMatchSnapshot();\n    expect(recommendation.idAttribute).toBe(idAttributeFn);\n  });\n\n  test('passes over pre-normalized values', () => {\n    const userEntity = new schema.Entity('users');\n    const articleEntity = new schema.Entity('articles', { author: userEntity });\n\n    expect(normalize({ id: '123', title: 'normalizr is great!', author: 1 }, articleEntity)).toMatchSnapshot();\n  });\n\n  test('can normalize object without proper object prototype inheritance', () => {\n    const test = { id: 1, elements: [] };\n    test.elements.push(\n      Object.assign(Object.create(null), {\n        id: 18,\n        name: 'test',\n      })\n    );\n\n    const testEntity = new schema.Entity('test', {\n      elements: [new schema.Entity('elements')],\n    });\n\n    expect(() => normalize(test, testEntity)).not.toThrow();\n  });\n\n  test('can normalize entity nested inside entity using property from parent', () => {\n    const linkablesSchema = new schema.Entity('linkables');\n    const mediaSchema = new schema.Entity('media');\n    const listsSchema = new schema.Entity('lists');\n\n    const schemaMap = {\n      media: mediaSchema,\n      lists: listsSchema,\n    };\n\n    linkablesSchema.define({\n      data: (parent) => schemaMap[parent.schema_type],\n    });\n\n    const input = {\n      id: 1,\n      module_type: 'article',\n      schema_type: 'media',\n      data: {\n        id: 2,\n        url: 'catimage.jpg',\n      },\n    };\n\n    expect(normalize(input, linkablesSchema)).toMatchSnapshot();\n  });\n\n  test('can normalize entity nested inside object using property from parent', () => {\n    const mediaSchema = new schema.Entity('media');\n    const listsSchema = new schema.Entity('lists');\n\n    const schemaMap = {\n      media: mediaSchema,\n      lists: listsSchema,\n    };\n\n    const linkablesSchema = {\n      data: (parent) => schemaMap[parent.schema_type],\n    };\n\n    const input = {\n      id: 1,\n      module_type: 'article',\n      schema_type: 'media',\n      data: {\n        id: 2,\n        url: 'catimage.jpg',\n      },\n    };\n\n    expect(normalize(input, linkablesSchema)).toMatchSnapshot();\n  });\n});\n\ndescribe('denormalize', () => {\n  test('cannot denormalize without a schema', () => {\n    expect(() => denormalize({})).toThrow();\n  });\n\n  test('returns the input if undefined', () => {\n    expect(denormalize(undefined, {}, {})).toBeUndefined();\n  });\n\n  test('denormalizes entities', () => {\n    const mySchema = new schema.Entity('tacos');\n    const entities = {\n      tacos: {\n        1: { id: 1, type: 'foo' },\n        2: { id: 2, type: 'bar' },\n      },\n    };\n    expect(denormalize([1, 2], [mySchema], entities)).toMatchSnapshot();\n  });\n\n  test('denormalizes nested entities', () => {\n    const user = new schema.Entity('users');\n    const comment = new schema.Entity('comments', {\n      user: user,\n    });\n    const article = new schema.Entity('articles', {\n      author: user,\n      comments: [comment],\n    });\n\n    const entities = {\n      articles: {\n        123: {\n          author: '8472',\n          body: 'This article is great.',\n          comments: ['comment-123-4738'],\n          id: '123',\n          title: 'A Great Article',\n        },\n      },\n      comments: {\n        'comment-123-4738': {\n          comment: 'I like it!',\n          id: 'comment-123-4738',\n          user: '10293',\n        },\n      },\n      users: {\n        10293: {\n          id: '10293',\n          name: 'Jane',\n        },\n        8472: {\n          id: '8472',\n          name: 'Paul',\n        },\n      },\n    };\n    expect(denormalize('123', article, entities)).toMatchSnapshot();\n  });\n\n  test('set to undefined if schema key is not in entities', () => {\n    const user = new schema.Entity('users');\n    const comment = new schema.Entity('comments', {\n      user: user,\n    });\n    const article = new schema.Entity('articles', {\n      author: user,\n      comments: [comment],\n    });\n\n    const entities = {\n      articles: {\n        123: {\n          id: '123',\n          author: '8472',\n          comments: ['1'],\n        },\n      },\n      comments: {\n        1: {\n          user: '123',\n        },\n      },\n    };\n    expect(denormalize('123', article, entities)).toMatchSnapshot();\n  });\n\n  test('does not modify the original entities', () => {\n    const user = new schema.Entity('users');\n    const article = new schema.Entity('articles', { author: user });\n    const entities = Object.freeze({\n      articles: Object.freeze({\n        123: Object.freeze({\n          id: '123',\n          title: 'A Great Article',\n          author: '8472',\n        }),\n      }),\n      users: Object.freeze({\n        8472: Object.freeze({\n          id: '8472',\n          name: 'Paul',\n        }),\n      }),\n    });\n    expect(() => denormalize('123', article, entities)).not.toThrow();\n  });\n\n  test('denormalizes with function as idAttribute', () => {\n    const normalizedData = {\n      entities: {\n        patrons: {\n          1: { id: '1', guest: null, name: 'Esther' },\n          2: { id: '2', guest: 'guest-2-1', name: 'Tom' },\n        },\n        guests: { 'guest-2-1': { guest_id: 1 } },\n      },\n      result: ['1', '2'],\n    };\n\n    const guestSchema = new schema.Entity(\n      'guests',\n      {},\n      {\n        idAttribute: (value, parent, key) => `${key}-${parent.id}-${value.guest_id}`,\n      }\n    );\n\n    const patronsSchema = new schema.Entity('patrons', {\n      guest: guestSchema,\n    });\n\n    expect(denormalize(normalizedData.result, [patronsSchema], normalizedData.entities)).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "src/index.js",
    "content": "import * as ImmutableUtils from './schemas/ImmutableUtils';\nimport EntitySchema from './schemas/Entity';\nimport UnionSchema from './schemas/Union';\nimport ValuesSchema from './schemas/Values';\nimport ArraySchema, * as ArrayUtils from './schemas/Array';\nimport ObjectSchema, * as ObjectUtils from './schemas/Object';\n\nconst visit = (value, parent, key, schema, addEntity, visitedEntities) => {\n  if (typeof value !== 'object' || !value) {\n    return value;\n  }\n\n  if (typeof schema === 'object' && (!schema.normalize || typeof schema.normalize !== 'function')) {\n    const method = Array.isArray(schema) ? ArrayUtils.normalize : ObjectUtils.normalize;\n    return method(schema, value, parent, key, visit, addEntity, visitedEntities);\n  }\n\n  return schema.normalize(value, parent, key, visit, addEntity, visitedEntities);\n};\n\nconst addEntities = (entities) => (schema, processedEntity, value, parent, key) => {\n  const schemaKey = schema.key;\n  const id = schema.getId(value, parent, key);\n  if (!(schemaKey in entities)) {\n    entities[schemaKey] = {};\n  }\n\n  const existingEntity = entities[schemaKey][id];\n  if (existingEntity) {\n    entities[schemaKey][id] = schema.merge(existingEntity, processedEntity);\n  } else {\n    entities[schemaKey][id] = processedEntity;\n  }\n};\n\nexport const schema = {\n  Array: ArraySchema,\n  Entity: EntitySchema,\n  Object: ObjectSchema,\n  Union: UnionSchema,\n  Values: ValuesSchema,\n};\n\nexport const normalize = (input, schema) => {\n  if (!input || typeof input !== 'object') {\n    throw new Error(\n      `Unexpected input given to normalize. Expected type to be \"object\", found \"${\n        input === null ? 'null' : typeof input\n      }\".`\n    );\n  }\n\n  const entities = {};\n  const addEntity = addEntities(entities);\n  const visitedEntities = {};\n\n  const result = visit(input, input, null, schema, addEntity, visitedEntities);\n  return { entities, result };\n};\n\nconst unvisitEntity = (id, schema, unvisit, getEntity, cache) => {\n  let entity = getEntity(id, schema);\n\n  if (entity === undefined && schema instanceof EntitySchema) {\n    entity = schema.fallback(id, schema);\n  }\n\n  if (typeof entity !== 'object' || entity === null) {\n    return entity;\n  }\n\n  if (!cache[schema.key]) {\n    cache[schema.key] = {};\n  }\n\n  if (!cache[schema.key][id]) {\n    // Ensure we don't mutate it non-immutable objects\n    const entityCopy = ImmutableUtils.isImmutable(entity) ? entity : { ...entity };\n\n    // Need to set this first so that if it is referenced further within the\n    // denormalization the reference will already exist.\n    cache[schema.key][id] = entityCopy;\n    cache[schema.key][id] = schema.denormalize(entityCopy, unvisit);\n  }\n\n  return cache[schema.key][id];\n};\n\nconst getUnvisit = (entities) => {\n  const cache = {};\n  const getEntity = getEntities(entities);\n\n  return function unvisit(input, schema) {\n    if (typeof schema === 'object' && (!schema.denormalize || typeof schema.denormalize !== 'function')) {\n      const method = Array.isArray(schema) ? ArrayUtils.denormalize : ObjectUtils.denormalize;\n      return method(schema, input, unvisit);\n    }\n\n    if (input === undefined || input === null) {\n      return input;\n    }\n\n    if (schema instanceof EntitySchema) {\n      return unvisitEntity(input, schema, unvisit, getEntity, cache);\n    }\n\n    return schema.denormalize(input, unvisit);\n  };\n};\n\nconst getEntities = (entities) => {\n  const isImmutable = ImmutableUtils.isImmutable(entities);\n\n  return (entityOrId, schema) => {\n    const schemaKey = schema.key;\n\n    if (typeof entityOrId === 'object') {\n      return entityOrId;\n    }\n\n    if (isImmutable) {\n      return entities.getIn([schemaKey, entityOrId.toString()]);\n    }\n\n    return entities[schemaKey] && entities[schemaKey][entityOrId];\n  };\n};\n\nexport const denormalize = (input, schema, entities) => {\n  if (typeof input !== 'undefined') {\n    return getUnvisit(entities)(input, schema);\n  }\n};\n"
  },
  {
    "path": "src/schemas/Array.js",
    "content": "import PolymorphicSchema from './Polymorphic';\n\nconst validateSchema = (definition) => {\n  const isArray = Array.isArray(definition);\n  if (isArray && definition.length > 1) {\n    throw new Error(`Expected schema definition to be a single schema, but found ${definition.length}.`);\n  }\n\n  return definition[0];\n};\n\nconst getValues = (input) => (Array.isArray(input) ? input : Object.keys(input).map((key) => input[key]));\n\nexport const normalize = (schema, input, parent, key, visit, addEntity, visitedEntities) => {\n  schema = validateSchema(schema);\n\n  const values = getValues(input);\n\n  // Special case: Arrays pass *their* parent on to their children, since there\n  // is not any special information that can be gathered from themselves directly\n  return values.map((value, index) => visit(value, parent, key, schema, addEntity, visitedEntities));\n};\n\nexport const denormalize = (schema, input, unvisit) => {\n  schema = validateSchema(schema);\n  return input && input.map ? input.map((entityOrId) => unvisit(entityOrId, schema)) : input;\n};\n\nexport default class ArraySchema extends PolymorphicSchema {\n  normalize(input, parent, key, visit, addEntity, visitedEntities) {\n    const values = getValues(input);\n\n    return values\n      .map((value, index) => this.normalizeValue(value, parent, key, visit, addEntity, visitedEntities))\n      .filter((value) => value !== undefined && value !== null);\n  }\n\n  denormalize(input, unvisit) {\n    return input && input.map ? input.map((value) => this.denormalizeValue(value, unvisit)) : input;\n  }\n}\n"
  },
  {
    "path": "src/schemas/Entity.js",
    "content": "import * as ImmutableUtils from './ImmutableUtils';\n\nconst getDefaultGetId = (idAttribute) => (input) =>\n  ImmutableUtils.isImmutable(input) ? input.get(idAttribute) : input[idAttribute];\n\nexport default class EntitySchema {\n  constructor(key, definition = {}, options = {}) {\n    if (!key || typeof key !== 'string') {\n      throw new Error(`Expected a string key for Entity, but found ${key}.`);\n    }\n\n    const {\n      idAttribute = 'id',\n      mergeStrategy = (entityA, entityB) => {\n        return { ...entityA, ...entityB };\n      },\n      processStrategy = (input) => ({ ...input }),\n      fallbackStrategy = (key, schema) => undefined,\n    } = options;\n\n    this._key = key;\n    this._getId = typeof idAttribute === 'function' ? idAttribute : getDefaultGetId(idAttribute);\n    this._idAttribute = idAttribute;\n    this._mergeStrategy = mergeStrategy;\n    this._processStrategy = processStrategy;\n    this._fallbackStrategy = fallbackStrategy;\n    this.define(definition);\n  }\n\n  get key() {\n    return this._key;\n  }\n\n  get idAttribute() {\n    return this._idAttribute;\n  }\n\n  define(definition) {\n    this.schema = Object.keys(definition).reduce((entitySchema, key) => {\n      const schema = definition[key];\n      return { ...entitySchema, [key]: schema };\n    }, this.schema || {});\n  }\n\n  getId(input, parent, key) {\n    return this._getId(input, parent, key);\n  }\n\n  merge(entityA, entityB) {\n    return this._mergeStrategy(entityA, entityB);\n  }\n\n  fallback(id, schema) {\n    return this._fallbackStrategy(id, schema);\n  }\n\n  normalize(input, parent, key, visit, addEntity, visitedEntities) {\n    const id = this.getId(input, parent, key);\n    const entityType = this.key;\n\n    if (!(entityType in visitedEntities)) {\n      visitedEntities[entityType] = {};\n    }\n    if (!(id in visitedEntities[entityType])) {\n      visitedEntities[entityType][id] = [];\n    }\n    if (visitedEntities[entityType][id].some((entity) => entity === input)) {\n      return id;\n    }\n    visitedEntities[entityType][id].push(input);\n\n    const processedEntity = this._processStrategy(input, parent, key);\n    Object.keys(this.schema).forEach((key) => {\n      if (processedEntity.hasOwnProperty(key) && typeof processedEntity[key] === 'object') {\n        const schema = this.schema[key];\n        const resolvedSchema = typeof schema === 'function' ? schema(input) : schema;\n        processedEntity[key] = visit(\n          processedEntity[key],\n          processedEntity,\n          key,\n          resolvedSchema,\n          addEntity,\n          visitedEntities\n        );\n      }\n    });\n\n    addEntity(this, processedEntity, input, parent, key);\n    return id;\n  }\n\n  denormalize(entity, unvisit) {\n    if (ImmutableUtils.isImmutable(entity)) {\n      return ImmutableUtils.denormalizeImmutable(this.schema, entity, unvisit);\n    }\n\n    Object.keys(this.schema).forEach((key) => {\n      if (entity.hasOwnProperty(key)) {\n        const schema = this.schema[key];\n        entity[key] = unvisit(entity[key], schema);\n      }\n    });\n    return entity;\n  }\n}\n"
  },
  {
    "path": "src/schemas/ImmutableUtils.js",
    "content": "/**\n * Helpers to enable Immutable compatibility *without* bringing in\n * the 'immutable' package as a dependency.\n */\n\n/**\n * Check if an object is immutable by checking if it has a key specific\n * to the immutable library.\n *\n * @param  {any} object\n * @return {bool}\n */\nexport function isImmutable(object) {\n  return !!(\n    object &&\n    typeof object.hasOwnProperty === 'function' &&\n    (object.hasOwnProperty('__ownerID') || // Immutable.Map\n      (object._map && object._map.hasOwnProperty('__ownerID')))\n  ); // Immutable.Record\n}\n\n/**\n * Denormalize an immutable entity.\n *\n * @param  {Schema} schema\n * @param  {Immutable.Map|Immutable.Record} input\n * @param  {function} unvisit\n * @param  {function} getDenormalizedEntity\n * @return {Immutable.Map|Immutable.Record}\n */\nexport function denormalizeImmutable(schema, input, unvisit) {\n  return Object.keys(schema).reduce((object, key) => {\n    // Immutable maps cast keys to strings on write so we need to ensure\n    // we're accessing them using string keys.\n    const stringKey = `${key}`;\n\n    if (object.has(stringKey)) {\n      return object.set(stringKey, unvisit(object.get(stringKey), schema[stringKey]));\n    } else {\n      return object;\n    }\n  }, input);\n}\n"
  },
  {
    "path": "src/schemas/Object.js",
    "content": "import * as ImmutableUtils from './ImmutableUtils';\n\nexport const normalize = (schema, input, parent, key, visit, addEntity, visitedEntities) => {\n  const object = { ...input };\n  Object.keys(schema).forEach((key) => {\n    const localSchema = schema[key];\n    const resolvedLocalSchema = typeof localSchema === 'function' ? localSchema(input) : localSchema;\n    const value = visit(input[key], input, key, resolvedLocalSchema, addEntity, visitedEntities);\n    if (value === undefined || value === null) {\n      delete object[key];\n    } else {\n      object[key] = value;\n    }\n  });\n  return object;\n};\n\nexport const denormalize = (schema, input, unvisit) => {\n  if (ImmutableUtils.isImmutable(input)) {\n    return ImmutableUtils.denormalizeImmutable(schema, input, unvisit);\n  }\n\n  const object = { ...input };\n  Object.keys(schema).forEach((key) => {\n    if (object[key] != null) {\n      object[key] = unvisit(object[key], schema[key]);\n    }\n  });\n  return object;\n};\n\nexport default class ObjectSchema {\n  constructor(definition) {\n    this.define(definition);\n  }\n\n  define(definition) {\n    this.schema = Object.keys(definition).reduce((entitySchema, key) => {\n      const schema = definition[key];\n      return { ...entitySchema, [key]: schema };\n    }, this.schema || {});\n  }\n\n  normalize(...args) {\n    return normalize(this.schema, ...args);\n  }\n\n  denormalize(...args) {\n    return denormalize(this.schema, ...args);\n  }\n}\n"
  },
  {
    "path": "src/schemas/Polymorphic.js",
    "content": "import { isImmutable } from './ImmutableUtils';\n\nexport default class PolymorphicSchema {\n  constructor(definition, schemaAttribute) {\n    if (schemaAttribute) {\n      this._schemaAttribute = typeof schemaAttribute === 'string' ? (input) => input[schemaAttribute] : schemaAttribute;\n    }\n    this.define(definition);\n  }\n\n  get isSingleSchema() {\n    return !this._schemaAttribute;\n  }\n\n  define(definition) {\n    this.schema = definition;\n  }\n\n  getSchemaAttribute(input, parent, key) {\n    return !this.isSingleSchema && this._schemaAttribute(input, parent, key);\n  }\n\n  inferSchema(input, parent, key) {\n    if (this.isSingleSchema) {\n      return this.schema;\n    }\n\n    const attr = this.getSchemaAttribute(input, parent, key);\n    return this.schema[attr];\n  }\n\n  normalizeValue(value, parent, key, visit, addEntity, visitedEntities) {\n    const schema = this.inferSchema(value, parent, key);\n    if (!schema) {\n      return value;\n    }\n    const normalizedValue = visit(value, parent, key, schema, addEntity, visitedEntities);\n    return this.isSingleSchema || normalizedValue === undefined || normalizedValue === null\n      ? normalizedValue\n      : { id: normalizedValue, schema: this.getSchemaAttribute(value, parent, key) };\n  }\n\n  denormalizeValue(value, unvisit) {\n    const schemaKey = isImmutable(value) ? value.get('schema') : value.schema;\n    if (!this.isSingleSchema && !schemaKey) {\n      return value;\n    }\n    const id = this.isSingleSchema ? undefined : isImmutable(value) ? value.get('id') : value.id;\n    const schema = this.isSingleSchema ? this.schema : this.schema[schemaKey];\n    return unvisit(id || value, schema);\n  }\n}\n"
  },
  {
    "path": "src/schemas/Union.js",
    "content": "import PolymorphicSchema from './Polymorphic';\n\nexport default class UnionSchema extends PolymorphicSchema {\n  constructor(definition, schemaAttribute) {\n    if (!schemaAttribute) {\n      throw new Error('Expected option \"schemaAttribute\" not found on UnionSchema.');\n    }\n    super(definition, schemaAttribute);\n  }\n\n  normalize(input, parent, key, visit, addEntity, visitedEntities) {\n    return this.normalizeValue(input, parent, key, visit, addEntity, visitedEntities);\n  }\n\n  denormalize(input, unvisit) {\n    return this.denormalizeValue(input, unvisit);\n  }\n}\n"
  },
  {
    "path": "src/schemas/Values.js",
    "content": "import PolymorphicSchema from './Polymorphic';\n\nexport default class ValuesSchema extends PolymorphicSchema {\n  normalize(input, parent, key, visit, addEntity, visitedEntities) {\n    return Object.keys(input).reduce((output, key, index) => {\n      const value = input[key];\n      return value !== undefined && value !== null\n        ? {\n            ...output,\n            [key]: this.normalizeValue(value, input, key, visit, addEntity, visitedEntities),\n          }\n        : output;\n    }, {});\n  }\n\n  denormalize(input, unvisit) {\n    return Object.keys(input).reduce((output, key) => {\n      const entityOrId = input[key];\n      return {\n        ...output,\n        [key]: this.denormalizeValue(entityOrId, unvisit),\n      };\n    }, {});\n  }\n}\n"
  },
  {
    "path": "src/schemas/__tests__/Array.test.js",
    "content": "// eslint-env jest\nimport { fromJS } from 'immutable';\nimport { denormalize, normalize, schema } from '../../';\n\ndescribe(`${schema.Array.name} normalization`, () => {\n  describe('Object', () => {\n    test(`normalizes plain arrays as shorthand for ${schema.Array.name}`, () => {\n      const userSchema = new schema.Entity('user');\n      expect(normalize([{ id: 1 }, { id: 2 }], [userSchema])).toMatchSnapshot();\n    });\n\n    test('throws an error if created with more than one schema', () => {\n      const userSchema = new schema.Entity('users');\n      const catSchema = new schema.Entity('cats');\n      expect(() => normalize([{ id: 1 }], [catSchema, userSchema])).toThrow();\n    });\n\n    test('passes its parent to its children when normalizing', () => {\n      const processStrategy = (entity, parent, key) => {\n        return { ...entity, parentId: parent.id, parentKey: key };\n      };\n      const childEntity = new schema.Entity('children', {}, { processStrategy });\n      const parentEntity = new schema.Entity('parents', {\n        children: [childEntity],\n      });\n\n      expect(\n        normalize(\n          {\n            id: 1,\n            content: 'parent',\n            children: [{ id: 4, content: 'child' }],\n          },\n          parentEntity\n        )\n      ).toMatchSnapshot();\n    });\n\n    test('normalizes Objects using their values', () => {\n      const userSchema = new schema.Entity('user');\n      expect(normalize({ foo: { id: 1 }, bar: { id: 2 } }, [userSchema])).toMatchSnapshot();\n    });\n  });\n\n  describe('Class', () => {\n    test('normalizes a single entity', () => {\n      const cats = new schema.Entity('cats');\n      const listSchema = new schema.Array(cats);\n      expect(normalize([{ id: 1 }, { id: 2 }], listSchema)).toMatchSnapshot();\n    });\n\n    test('normalizes multiple entities', () => {\n      const inferSchemaFn = jest.fn((input, parent, key) => input.type || 'dogs');\n      const catSchema = new schema.Entity('cats');\n      const peopleSchema = new schema.Entity('person');\n      const listSchema = new schema.Array(\n        {\n          cats: catSchema,\n          people: peopleSchema,\n        },\n        inferSchemaFn\n      );\n\n      expect(\n        normalize(\n          [\n            { type: 'cats', id: '123' },\n            { type: 'people', id: '123' },\n            { id: '789', name: 'fido' },\n            { type: 'cats', id: '456' },\n          ],\n          listSchema\n        )\n      ).toMatchSnapshot();\n      expect(inferSchemaFn.mock.calls).toMatchSnapshot();\n    });\n\n    test('normalizes Objects using their values', () => {\n      const userSchema = new schema.Entity('user');\n      const users = new schema.Array(userSchema);\n      expect(normalize({ foo: { id: 1 }, bar: { id: 2 } }, users)).toMatchSnapshot();\n    });\n\n    test('filters out undefined and null normalized values', () => {\n      const userSchema = new schema.Entity('user');\n      const users = new schema.Array(userSchema);\n      expect(normalize([undefined, { id: 123 }, null], users)).toMatchSnapshot();\n    });\n  });\n});\n\ndescribe(`${schema.Array.name} denormalization`, () => {\n  describe('Object', () => {\n    test('denormalizes a single entity', () => {\n      const cats = new schema.Entity('cats');\n      const entities = {\n        cats: {\n          1: { id: 1, name: 'Milo' },\n          2: { id: 2, name: 'Jake' },\n        },\n      };\n      expect(denormalize([1, 2], [cats], entities)).toMatchSnapshot();\n      expect(denormalize([1, 2], [cats], fromJS(entities))).toMatchSnapshot();\n    });\n\n    test('returns the input value if is not an array', () => {\n      const filling = new schema.Entity('fillings');\n      const taco = new schema.Entity('tacos', { fillings: [filling] });\n      const entities = {\n        tacos: {\n          123: {\n            id: '123',\n            fillings: null,\n          },\n        },\n      };\n\n      expect(denormalize('123', taco, entities)).toMatchSnapshot();\n      expect(denormalize('123', taco, fromJS(entities))).toMatchSnapshot();\n    });\n  });\n\n  describe('Class', () => {\n    test('denormalizes a single entity', () => {\n      const cats = new schema.Entity('cats');\n      const entities = {\n        cats: {\n          1: { id: 1, name: 'Milo' },\n          2: { id: 2, name: 'Jake' },\n        },\n      };\n      const catList = new schema.Array(cats);\n      expect(denormalize([1, 2], catList, entities)).toMatchSnapshot();\n      expect(denormalize([1, 2], catList, fromJS(entities))).toMatchSnapshot();\n    });\n\n    test('denormalizes multiple entities', () => {\n      const catSchema = new schema.Entity('cats');\n      const peopleSchema = new schema.Entity('person');\n      const listSchema = new schema.Array(\n        {\n          cats: catSchema,\n          dogs: {},\n          people: peopleSchema,\n        },\n        (input, parent, key) => input.type || 'dogs'\n      );\n\n      const entities = {\n        cats: {\n          123: {\n            id: '123',\n            type: 'cats',\n          },\n          456: {\n            id: '456',\n            type: 'cats',\n          },\n        },\n        person: {\n          123: {\n            id: '123',\n            type: 'people',\n          },\n        },\n      };\n\n      const input = [\n        { id: '123', schema: 'cats' },\n        { id: '123', schema: 'people' },\n        { id: { id: '789' }, schema: 'dogs' },\n        { id: '456', schema: 'cats' },\n      ];\n\n      expect(denormalize(input, listSchema, entities)).toMatchSnapshot();\n      expect(denormalize(input, listSchema, fromJS(entities))).toMatchSnapshot();\n    });\n\n    test('returns the input value if is not an array', () => {\n      const filling = new schema.Entity('fillings');\n      const fillings = new schema.Array(filling);\n      const taco = new schema.Entity('tacos', { fillings });\n      const entities = {\n        tacos: {\n          123: {\n            id: '123',\n            fillings: {},\n          },\n        },\n      };\n\n      expect(denormalize('123', taco, entities)).toMatchSnapshot();\n      expect(denormalize('123', taco, fromJS(entities))).toMatchSnapshot();\n    });\n\n    test('does not assume mapping of schema to attribute values when schemaAttribute is not set', () => {\n      const cats = new schema.Entity('cats');\n      const catRecord = new schema.Object({\n        cat: cats,\n      });\n      const catList = new schema.Array(catRecord);\n      const input = [\n        { cat: { id: 1 }, id: 5 },\n        { cat: { id: 2 }, id: 6 },\n      ];\n      const output = normalize(input, catList);\n      expect(output).toMatchSnapshot();\n      expect(denormalize(output.result, catList, output.entities)).toEqual(input);\n    });\n  });\n});\n"
  },
  {
    "path": "src/schemas/__tests__/Entity.test.js",
    "content": "// eslint-env jest\nimport { denormalize, normalize, schema } from '../../';\nimport { fromJS, Record } from 'immutable';\n\nconst values = (obj) => Object.keys(obj).map((key) => obj[key]);\n\ndescribe(`${schema.Entity.name} normalization`, () => {\n  test('normalizes an entity', () => {\n    const entity = new schema.Entity('item');\n    expect(normalize({ id: 1 }, entity)).toMatchSnapshot();\n  });\n\n  describe('key', () => {\n    test('must be created with a key name', () => {\n      expect(() => new schema.Entity()).toThrow();\n    });\n\n    test('key name must be a string', () => {\n      expect(() => new schema.Entity(42)).toThrow();\n    });\n\n    test('key getter should return key passed to constructor', () => {\n      const user = new schema.Entity('users');\n      expect(user.key).toEqual('users');\n    });\n  });\n\n  describe('idAttribute', () => {\n    test('can use a custom idAttribute string', () => {\n      const user = new schema.Entity('users', {}, { idAttribute: 'id_str' });\n      expect(normalize({ id_str: '134351', name: 'Kathy' }, user)).toMatchSnapshot();\n    });\n\n    test('can normalize entity IDs based on their object key', () => {\n      const user = new schema.Entity('users', {}, { idAttribute: (entity, parent, key) => key });\n      const inputSchema = new schema.Values({ users: user }, () => 'users');\n\n      expect(normalize({ 4: { name: 'taco' }, 56: { name: 'burrito' } }, inputSchema)).toMatchSnapshot();\n    });\n\n    test(\"can build the entity's ID from the parent object\", () => {\n      const user = new schema.Entity(\n        'users',\n        {},\n        {\n          idAttribute: (entity, parent, key) => `${parent.name}-${key}-${entity.id}`,\n        }\n      );\n      const inputSchema = new schema.Object({ user });\n\n      expect(normalize({ name: 'tacos', user: { id: '4', name: 'Jimmy' } }, inputSchema)).toMatchSnapshot();\n    });\n  });\n\n  describe('mergeStrategy', () => {\n    test('defaults to plain merging', () => {\n      const mySchema = new schema.Entity('tacos');\n      expect(\n        normalize(\n          [\n            { id: 1, name: 'foo' },\n            { id: 1, name: 'bar', alias: 'bar' },\n          ],\n          [mySchema]\n        )\n      ).toMatchSnapshot();\n    });\n\n    test('can use a custom merging strategy', () => {\n      const mergeStrategy = (entityA, entityB) => {\n        return { ...entityA, ...entityB, name: entityA.name };\n      };\n      const mySchema = new schema.Entity('tacos', {}, { mergeStrategy });\n\n      expect(\n        normalize(\n          [\n            { id: 1, name: 'foo' },\n            { id: 1, name: 'bar', alias: 'bar' },\n          ],\n          [mySchema]\n        )\n      ).toMatchSnapshot();\n    });\n  });\n\n  describe('processStrategy', () => {\n    test('can use a custom processing strategy', () => {\n      const processStrategy = (entity) => {\n        return { ...entity, slug: `thing-${entity.id}` };\n      };\n      const mySchema = new schema.Entity('tacos', {}, { processStrategy });\n\n      expect(normalize({ id: 1, name: 'foo' }, mySchema)).toMatchSnapshot();\n    });\n\n    test('can use information from the parent in the process strategy', () => {\n      const processStrategy = (entity, parent, key) => {\n        return { ...entity, parentId: parent.id, parentKey: key };\n      };\n      const childEntity = new schema.Entity('children', {}, { processStrategy });\n      const parentEntity = new schema.Entity('parents', {\n        child: childEntity,\n      });\n\n      expect(\n        normalize(\n          {\n            id: 1,\n            content: 'parent',\n            child: { id: 4, content: 'child' },\n          },\n          parentEntity\n        )\n      ).toMatchSnapshot();\n    });\n\n    test('is run before and passed to the schema normalization', () => {\n      const processStrategy = (input) => ({ ...values(input)[0], type: Object.keys(input)[0] });\n      const attachmentEntity = new schema.Entity('attachments');\n      // If not run before, this schema would require a parent object with key \"message\"\n      const myEntity = new schema.Entity(\n        'entries',\n        {\n          data: { attachment: attachmentEntity },\n        },\n        { idAttribute: (input) => values(input)[0].id, processStrategy }\n      );\n\n      expect(normalize({ message: { id: '123', data: { attachment: { id: '456' } } } }, myEntity)).toMatchSnapshot();\n    });\n  });\n});\n\ndescribe(`${schema.Entity.name} denormalization`, () => {\n  test('denormalizes an entity', () => {\n    const mySchema = new schema.Entity('tacos');\n    const entities = {\n      tacos: {\n        1: { id: 1, type: 'foo' },\n      },\n    };\n    expect(denormalize(1, mySchema, entities)).toMatchSnapshot();\n    expect(denormalize(1, mySchema, fromJS(entities))).toMatchSnapshot();\n  });\n\n  test('denormalizes deep entities', () => {\n    const foodSchema = new schema.Entity('foods');\n    const menuSchema = new schema.Entity('menus', {\n      food: foodSchema,\n    });\n\n    const entities = {\n      menus: {\n        1: { id: 1, food: 1 },\n        2: { id: 2 },\n      },\n      foods: {\n        1: { id: 1 },\n      },\n    };\n\n    expect(denormalize(1, menuSchema, entities)).toMatchSnapshot();\n    expect(denormalize(1, menuSchema, fromJS(entities))).toMatchSnapshot();\n\n    expect(denormalize(2, menuSchema, entities)).toMatchSnapshot();\n    expect(denormalize(2, menuSchema, fromJS(entities))).toMatchSnapshot();\n  });\n\n  test('denormalizes to undefined for missing data', () => {\n    const foodSchema = new schema.Entity('foods');\n    const menuSchema = new schema.Entity('menus', {\n      food: foodSchema,\n    });\n\n    const entities = {\n      menus: {\n        1: { id: 1, food: 2 },\n      },\n      foods: {\n        1: { id: 1 },\n      },\n    };\n\n    expect(denormalize(1, menuSchema, entities)).toMatchSnapshot();\n    expect(denormalize(1, menuSchema, fromJS(entities))).toMatchSnapshot();\n\n    expect(denormalize(2, menuSchema, entities)).toMatchSnapshot();\n    expect(denormalize(2, menuSchema, fromJS(entities))).toMatchSnapshot();\n  });\n\n  test('denormalizes deep entities with records', () => {\n    const foodSchema = new schema.Entity('foods');\n    const menuSchema = new schema.Entity('menus', {\n      food: foodSchema,\n    });\n\n    const Food = new Record({ id: null });\n    const Menu = new Record({ id: null, food: null });\n\n    const entities = {\n      menus: {\n        1: new Menu({ id: 1, food: 1 }),\n        2: new Menu({ id: 2 }),\n      },\n      foods: {\n        1: new Food({ id: 1 }),\n      },\n    };\n\n    expect(denormalize(1, menuSchema, entities)).toMatchSnapshot();\n    expect(denormalize(1, menuSchema, fromJS(entities))).toMatchSnapshot();\n\n    expect(denormalize(2, menuSchema, entities)).toMatchSnapshot();\n    expect(denormalize(2, menuSchema, fromJS(entities))).toMatchSnapshot();\n  });\n\n  test('can denormalize already partially denormalized data', () => {\n    const foodSchema = new schema.Entity('foods');\n    const menuSchema = new schema.Entity('menus', {\n      food: foodSchema,\n    });\n\n    const entities = {\n      menus: {\n        1: { id: 1, food: { id: 1 } },\n      },\n      foods: {\n        1: { id: 1 },\n      },\n    };\n\n    expect(denormalize(1, menuSchema, entities)).toMatchSnapshot();\n    expect(denormalize(1, menuSchema, fromJS(entities))).toMatchSnapshot();\n  });\n\n  test('denormalizes recursive dependencies', () => {\n    const user = new schema.Entity('users');\n    const report = new schema.Entity('reports');\n\n    user.define({\n      reports: [report],\n    });\n    report.define({\n      draftedBy: user,\n      publishedBy: user,\n    });\n\n    const entities = {\n      reports: {\n        123: {\n          id: '123',\n          title: 'Weekly report',\n          draftedBy: '456',\n          publishedBy: '456',\n        },\n      },\n      users: {\n        456: {\n          id: '456',\n          role: 'manager',\n          reports: ['123'],\n        },\n      },\n    };\n    expect(denormalize('123', report, entities)).toMatchSnapshot();\n    expect(denormalize('123', report, fromJS(entities))).toMatchSnapshot();\n\n    expect(denormalize('456', user, entities)).toMatchSnapshot();\n    expect(denormalize('456', user, fromJS(entities))).toMatchSnapshot();\n  });\n\n  test('denormalizes entities with referential equality', () => {\n    const user = new schema.Entity('users');\n    const report = new schema.Entity('reports');\n\n    user.define({\n      reports: [report],\n    });\n    report.define({\n      draftedBy: user,\n      publishedBy: user,\n    });\n\n    const entities = {\n      reports: {\n        123: {\n          id: '123',\n          title: 'Weekly report',\n          draftedBy: '456',\n          publishedBy: '456',\n        },\n      },\n      users: {\n        456: {\n          id: '456',\n          role: 'manager',\n          reports: ['123'],\n        },\n      },\n    };\n\n    const denormalizedReport = denormalize('123', report, entities);\n\n    expect(denormalizedReport).toBe(denormalizedReport.draftedBy.reports[0]);\n    expect(denormalizedReport.publishedBy).toBe(denormalizedReport.draftedBy);\n\n    // NOTE: Given how immutable data works, referential equality can't be\n    // maintained with nested denormalization.\n  });\n\n  test('denormalizes with fallback strategy', () => {\n    const user = new schema.Entity(\n      'users',\n      {},\n      {\n        idAttribute: 'userId',\n        fallbackStrategy: (id, schema) => ({\n          [schema.idAttribute]: id,\n          name: 'John Doe',\n        }),\n      }\n    );\n    const report = new schema.Entity('reports', {\n      draftedBy: user,\n      publishedBy: user,\n    });\n\n    const entities = {\n      reports: {\n        123: {\n          id: '123',\n          title: 'Weekly report',\n          draftedBy: '456',\n          publishedBy: '456',\n        },\n      },\n      users: {},\n    };\n\n    const denormalizedReport = denormalize('123', report, entities);\n\n    expect(denormalizedReport.publishedBy).toBe(denormalizedReport.draftedBy);\n    expect(denormalizedReport.publishedBy.name).toBe('John Doe');\n    expect(denormalizedReport.publishedBy.userId).toBe('456');\n    //\n  });\n});\n"
  },
  {
    "path": "src/schemas/__tests__/Object.test.js",
    "content": "// eslint-env jest\nimport { fromJS } from 'immutable';\nimport { denormalize, normalize, schema } from '../../';\n\ndescribe(`${schema.Object.name} normalization`, () => {\n  test('normalizes an object', () => {\n    const userSchema = new schema.Entity('user');\n    const object = new schema.Object({\n      user: userSchema,\n    });\n    expect(normalize({ user: { id: 1 } }, object)).toMatchSnapshot();\n  });\n\n  test(`normalizes plain objects as shorthand for ${schema.Object.name}`, () => {\n    const userSchema = new schema.Entity('user');\n    expect(normalize({ user: { id: 1 } }, { user: userSchema })).toMatchSnapshot();\n  });\n\n  test('filters out undefined and null values', () => {\n    const userSchema = new schema.Entity('user');\n    const users = { foo: userSchema, bar: userSchema, baz: userSchema };\n    expect(normalize({ foo: {}, bar: { id: '1' } }, users)).toMatchSnapshot();\n  });\n});\n\ndescribe(`${schema.Object.name} denormalization`, () => {\n  test('denormalizes an object', () => {\n    const userSchema = new schema.Entity('user');\n    const object = new schema.Object({\n      user: userSchema,\n    });\n    const entities = {\n      user: {\n        1: { id: 1, name: 'Nacho' },\n      },\n    };\n    expect(denormalize({ user: 1 }, object, entities)).toMatchSnapshot();\n    expect(denormalize({ user: 1 }, object, fromJS(entities))).toMatchSnapshot();\n    expect(denormalize(fromJS({ user: 1 }), object, fromJS(entities))).toMatchSnapshot();\n  });\n\n  test('denormalizes plain object shorthand', () => {\n    const userSchema = new schema.Entity('user');\n    const entities = {\n      user: {\n        1: { id: 1, name: 'Jane' },\n      },\n    };\n    expect(denormalize({ user: 1 }, { user: userSchema, tacos: {} }, entities)).toMatchSnapshot();\n    expect(denormalize({ user: 1 }, { user: userSchema, tacos: {} }, fromJS(entities))).toMatchSnapshot();\n    expect(denormalize(fromJS({ user: 1 }), { user: userSchema, tacos: {} }, fromJS(entities))).toMatchSnapshot();\n  });\n\n  test('denormalizes an object that contains a property representing a an object with an id of zero', () => {\n    const userSchema = new schema.Entity('user');\n    const object = new schema.Object({\n      user: userSchema,\n    });\n    const entities = {\n      user: {\n        0: { id: 0, name: 'Chancho' },\n      },\n    };\n    expect(denormalize({ user: 0 }, object, entities)).toMatchSnapshot();\n    expect(denormalize({ user: 0 }, object, fromJS(entities))).toMatchSnapshot();\n    expect(denormalize(fromJS({ user: 0 }), object, fromJS(entities))).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "src/schemas/__tests__/Union.test.js",
    "content": "// eslint-env jest\nimport { fromJS } from 'immutable';\nimport { denormalize, normalize, schema } from '../../';\n\ndescribe(`${schema.Union.name} normalization`, () => {\n  test('throws if not given a schemaAttribute', () => {\n    expect(() => new schema.Union({})).toThrow();\n  });\n\n  test('normalizes an object using string schemaAttribute', () => {\n    const user = new schema.Entity('users');\n    const group = new schema.Entity('groups');\n    const union = new schema.Union(\n      {\n        users: user,\n        groups: group,\n      },\n      'type'\n    );\n\n    expect(normalize({ id: 1, type: 'users' }, union)).toMatchSnapshot();\n    expect(normalize({ id: 2, type: 'groups' }, union)).toMatchSnapshot();\n  });\n\n  test('normalizes an array of multiple entities using a function to infer the schemaAttribute', () => {\n    const user = new schema.Entity('users');\n    const group = new schema.Entity('groups');\n    const union = new schema.Union(\n      {\n        users: user,\n        groups: group,\n      },\n      (input) => {\n        return input.username ? 'users' : input.groupname ? 'groups' : null;\n      }\n    );\n\n    expect(normalize({ id: 1, username: 'Janey' }, union)).toMatchSnapshot();\n    expect(normalize({ id: 2, groupname: 'People' }, union)).toMatchSnapshot();\n    expect(normalize({ id: 3, notdefined: 'yep' }, union)).toMatchSnapshot();\n  });\n});\n\ndescribe(`${schema.Union.name} denormalization`, () => {\n  const user = new schema.Entity('users');\n  const group = new schema.Entity('groups');\n  const entities = {\n    users: {\n      1: { id: 1, username: 'Janey', type: 'users' },\n    },\n    groups: {\n      2: { id: 2, groupname: 'People', type: 'groups' },\n    },\n  };\n\n  test('denormalizes an object using string schemaAttribute', () => {\n    const union = new schema.Union(\n      {\n        users: user,\n        groups: group,\n      },\n      'type'\n    );\n\n    expect(denormalize({ id: 1, schema: 'users' }, union, entities)).toMatchSnapshot();\n    expect(denormalize(fromJS({ id: 1, schema: 'users' }), union, fromJS(entities))).toMatchSnapshot();\n\n    expect(denormalize({ id: 2, schema: 'groups' }, union, entities)).toMatchSnapshot();\n    expect(denormalize(fromJS({ id: 2, schema: 'groups' }), union, fromJS(entities))).toMatchSnapshot();\n  });\n\n  test('denormalizes an array of multiple entities using a function to infer the schemaAttribute', () => {\n    const union = new schema.Union(\n      {\n        users: user,\n        groups: group,\n      },\n      (input) => {\n        return input.username ? 'users' : 'groups';\n      }\n    );\n\n    expect(denormalize({ id: 1, schema: 'users' }, union, entities)).toMatchSnapshot();\n    expect(denormalize(fromJS({ id: 1, schema: 'users' }), union, fromJS(entities))).toMatchSnapshot();\n\n    expect(denormalize({ id: 2, schema: 'groups' }, union, entities)).toMatchSnapshot();\n    expect(denormalize(fromJS({ id: 2, schema: 'groups' }), union, fromJS(entities))).toMatchSnapshot();\n  });\n\n  test('returns the original value no schema is given', () => {\n    const union = new schema.Union(\n      {\n        users: user,\n        groups: group,\n      },\n      (input) => {\n        return input.username ? 'users' : 'groups';\n      }\n    );\n\n    expect(denormalize({ id: 1 }, union, entities)).toMatchSnapshot();\n    expect(denormalize(fromJS({ id: 1 }), union, fromJS(entities))).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "src/schemas/__tests__/Values.test.js",
    "content": "// eslint-env jest\nimport { fromJS } from 'immutable';\nimport { denormalize, normalize, schema } from '../../';\n\ndescribe(`${schema.Values.name} normalization`, () => {\n  test('normalizes the values of an object with the given schema', () => {\n    const cat = new schema.Entity('cats');\n    const dog = new schema.Entity('dogs');\n    const valuesSchema = new schema.Values(\n      {\n        dogs: dog,\n        cats: cat,\n      },\n      (entity, key) => entity.type\n    );\n\n    expect(\n      normalize(\n        {\n          fido: { id: 1, type: 'dogs' },\n          fluffy: { id: 1, type: 'cats' },\n        },\n        valuesSchema\n      )\n    ).toMatchSnapshot();\n  });\n\n  test('can use a function to determine the schema when normalizing', () => {\n    const cat = new schema.Entity('cats');\n    const dog = new schema.Entity('dogs');\n    const valuesSchema = new schema.Values(\n      {\n        dogs: dog,\n        cats: cat,\n      },\n      (entity, key) => `${entity.type}s`\n    );\n\n    expect(\n      normalize(\n        {\n          fido: { id: 1, type: 'dog' },\n          fluffy: { id: 1, type: 'cat' },\n          jim: { id: 2, type: 'lizard' },\n        },\n        valuesSchema\n      )\n    ).toMatchSnapshot();\n  });\n\n  test('filters out null and undefined values', () => {\n    const cat = new schema.Entity('cats');\n    const dog = new schema.Entity('dogs');\n    const valuesSchema = new schema.Values(\n      {\n        dogs: dog,\n        cats: cat,\n      },\n      (entity, key) => entity.type\n    );\n\n    expect(\n      normalize(\n        {\n          fido: undefined,\n          milo: null,\n          fluffy: { id: 1, type: 'cats' },\n        },\n        valuesSchema\n      )\n    ).toMatchSnapshot();\n  });\n});\n\ndescribe(`${schema.Values.name} denormalization`, () => {\n  test('denormalizes the values of an object with the given schema', () => {\n    const cat = new schema.Entity('cats');\n    const dog = new schema.Entity('dogs');\n    const valuesSchema = new schema.Values(\n      {\n        dogs: dog,\n        cats: cat,\n      },\n      (entity, key) => entity.type\n    );\n\n    const entities = {\n      cats: { 1: { id: 1, type: 'cats' } },\n      dogs: { 1: { id: 1, type: 'dogs' } },\n    };\n\n    expect(\n      denormalize(\n        {\n          fido: { id: 1, schema: 'dogs' },\n          fluffy: { id: 1, schema: 'cats' },\n        },\n        valuesSchema,\n        entities\n      )\n    ).toMatchSnapshot();\n\n    expect(\n      denormalize(\n        {\n          fido: { id: 1, schema: 'dogs' },\n          fluffy: { id: 1, schema: 'cats' },\n        },\n        valuesSchema,\n        fromJS(entities)\n      )\n    ).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "src/schemas/__tests__/__snapshots__/Array.test.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`ArraySchema denormalization Class denormalizes a single entity 1`] = `\nArray [\n  Object {\n    \"id\": 1,\n    \"name\": \"Milo\",\n  },\n  Object {\n    \"id\": 2,\n    \"name\": \"Jake\",\n  },\n]\n`;\n\nexports[`ArraySchema denormalization Class denormalizes a single entity 2`] = `\nArray [\n  Immutable.Map {\n    \"id\": 1,\n    \"name\": \"Milo\",\n  },\n  Immutable.Map {\n    \"id\": 2,\n    \"name\": \"Jake\",\n  },\n]\n`;\n\nexports[`ArraySchema denormalization Class denormalizes multiple entities 1`] = `\nArray [\n  Object {\n    \"id\": \"123\",\n    \"type\": \"cats\",\n  },\n  Object {\n    \"id\": \"123\",\n    \"type\": \"people\",\n  },\n  Object {\n    \"id\": \"789\",\n  },\n  Object {\n    \"id\": \"456\",\n    \"type\": \"cats\",\n  },\n]\n`;\n\nexports[`ArraySchema denormalization Class denormalizes multiple entities 2`] = `\nArray [\n  Immutable.Map {\n    \"id\": \"123\",\n    \"type\": \"cats\",\n  },\n  Immutable.Map {\n    \"id\": \"123\",\n    \"type\": \"people\",\n  },\n  Object {\n    \"id\": \"789\",\n  },\n  Immutable.Map {\n    \"id\": \"456\",\n    \"type\": \"cats\",\n  },\n]\n`;\n\nexports[`ArraySchema denormalization Class does not assume mapping of schema to attribute values when schemaAttribute is not set 1`] = `\nObject {\n  \"entities\": Object {\n    \"cats\": Object {\n      \"1\": Object {\n        \"id\": 1,\n      },\n      \"2\": Object {\n        \"id\": 2,\n      },\n    },\n  },\n  \"result\": Array [\n    Object {\n      \"cat\": 1,\n      \"id\": 5,\n    },\n    Object {\n      \"cat\": 2,\n      \"id\": 6,\n    },\n  ],\n}\n`;\n\nexports[`ArraySchema denormalization Class returns the input value if is not an array 1`] = `\nObject {\n  \"fillings\": Object {},\n  \"id\": \"123\",\n}\n`;\n\nexports[`ArraySchema denormalization Class returns the input value if is not an array 2`] = `\nImmutable.Map {\n  \"id\": \"123\",\n  \"fillings\": Immutable.Map {},\n}\n`;\n\nexports[`ArraySchema denormalization Object denormalizes a single entity 1`] = `\nArray [\n  Object {\n    \"id\": 1,\n    \"name\": \"Milo\",\n  },\n  Object {\n    \"id\": 2,\n    \"name\": \"Jake\",\n  },\n]\n`;\n\nexports[`ArraySchema denormalization Object denormalizes a single entity 2`] = `\nArray [\n  Immutable.Map {\n    \"id\": 1,\n    \"name\": \"Milo\",\n  },\n  Immutable.Map {\n    \"id\": 2,\n    \"name\": \"Jake\",\n  },\n]\n`;\n\nexports[`ArraySchema denormalization Object returns the input value if is not an array 1`] = `\nObject {\n  \"fillings\": null,\n  \"id\": \"123\",\n}\n`;\n\nexports[`ArraySchema denormalization Object returns the input value if is not an array 2`] = `\nImmutable.Map {\n  \"id\": \"123\",\n  \"fillings\": null,\n}\n`;\n\nexports[`ArraySchema normalization Class filters out undefined and null normalized values 1`] = `\nObject {\n  \"entities\": Object {\n    \"user\": Object {\n      \"123\": Object {\n        \"id\": 123,\n      },\n    },\n  },\n  \"result\": Array [\n    123,\n  ],\n}\n`;\n\nexports[`ArraySchema normalization Class normalizes Objects using their values 1`] = `\nObject {\n  \"entities\": Object {\n    \"user\": Object {\n      \"1\": Object {\n        \"id\": 1,\n      },\n      \"2\": Object {\n        \"id\": 2,\n      },\n    },\n  },\n  \"result\": Array [\n    1,\n    2,\n  ],\n}\n`;\n\nexports[`ArraySchema normalization Class normalizes a single entity 1`] = `\nObject {\n  \"entities\": Object {\n    \"cats\": Object {\n      \"1\": Object {\n        \"id\": 1,\n      },\n      \"2\": Object {\n        \"id\": 2,\n      },\n    },\n  },\n  \"result\": Array [\n    1,\n    2,\n  ],\n}\n`;\n\nexports[`ArraySchema normalization Class normalizes multiple entities 1`] = `\nObject {\n  \"entities\": Object {\n    \"cats\": Object {\n      \"123\": Object {\n        \"id\": \"123\",\n        \"type\": \"cats\",\n      },\n      \"456\": Object {\n        \"id\": \"456\",\n        \"type\": \"cats\",\n      },\n    },\n    \"person\": Object {\n      \"123\": Object {\n        \"id\": \"123\",\n        \"type\": \"people\",\n      },\n    },\n  },\n  \"result\": Array [\n    Object {\n      \"id\": \"123\",\n      \"schema\": \"cats\",\n    },\n    Object {\n      \"id\": \"123\",\n      \"schema\": \"people\",\n    },\n    Object {\n      \"id\": \"789\",\n      \"name\": \"fido\",\n    },\n    Object {\n      \"id\": \"456\",\n      \"schema\": \"cats\",\n    },\n  ],\n}\n`;\n\nexports[`ArraySchema normalization Class normalizes multiple entities 2`] = `\nArray [\n  Array [\n    Object {\n      \"id\": \"123\",\n      \"type\": \"cats\",\n    },\n    Array [\n      Object {\n        \"id\": \"123\",\n        \"type\": \"cats\",\n      },\n      Object {\n        \"id\": \"123\",\n        \"type\": \"people\",\n      },\n      Object {\n        \"id\": \"789\",\n        \"name\": \"fido\",\n      },\n      Object {\n        \"id\": \"456\",\n        \"type\": \"cats\",\n      },\n    ],\n    null,\n  ],\n  Array [\n    Object {\n      \"id\": \"123\",\n      \"type\": \"cats\",\n    },\n    Array [\n      Object {\n        \"id\": \"123\",\n        \"type\": \"cats\",\n      },\n      Object {\n        \"id\": \"123\",\n        \"type\": \"people\",\n      },\n      Object {\n        \"id\": \"789\",\n        \"name\": \"fido\",\n      },\n      Object {\n        \"id\": \"456\",\n        \"type\": \"cats\",\n      },\n    ],\n    null,\n  ],\n  Array [\n    Object {\n      \"id\": \"123\",\n      \"type\": \"people\",\n    },\n    Array [\n      Object {\n        \"id\": \"123\",\n        \"type\": \"cats\",\n      },\n      Object {\n        \"id\": \"123\",\n        \"type\": \"people\",\n      },\n      Object {\n        \"id\": \"789\",\n        \"name\": \"fido\",\n      },\n      Object {\n        \"id\": \"456\",\n        \"type\": \"cats\",\n      },\n    ],\n    null,\n  ],\n  Array [\n    Object {\n      \"id\": \"123\",\n      \"type\": \"people\",\n    },\n    Array [\n      Object {\n        \"id\": \"123\",\n        \"type\": \"cats\",\n      },\n      Object {\n        \"id\": \"123\",\n        \"type\": \"people\",\n      },\n      Object {\n        \"id\": \"789\",\n        \"name\": \"fido\",\n      },\n      Object {\n        \"id\": \"456\",\n        \"type\": \"cats\",\n      },\n    ],\n    null,\n  ],\n  Array [\n    Object {\n      \"id\": \"789\",\n      \"name\": \"fido\",\n    },\n    Array [\n      Object {\n        \"id\": \"123\",\n        \"type\": \"cats\",\n      },\n      Object {\n        \"id\": \"123\",\n        \"type\": \"people\",\n      },\n      Object {\n        \"id\": \"789\",\n        \"name\": \"fido\",\n      },\n      Object {\n        \"id\": \"456\",\n        \"type\": \"cats\",\n      },\n    ],\n    null,\n  ],\n  Array [\n    Object {\n      \"id\": \"456\",\n      \"type\": \"cats\",\n    },\n    Array [\n      Object {\n        \"id\": \"123\",\n        \"type\": \"cats\",\n      },\n      Object {\n        \"id\": \"123\",\n        \"type\": \"people\",\n      },\n      Object {\n        \"id\": \"789\",\n        \"name\": \"fido\",\n      },\n      Object {\n        \"id\": \"456\",\n        \"type\": \"cats\",\n      },\n    ],\n    null,\n  ],\n  Array [\n    Object {\n      \"id\": \"456\",\n      \"type\": \"cats\",\n    },\n    Array [\n      Object {\n        \"id\": \"123\",\n        \"type\": \"cats\",\n      },\n      Object {\n        \"id\": \"123\",\n        \"type\": \"people\",\n      },\n      Object {\n        \"id\": \"789\",\n        \"name\": \"fido\",\n      },\n      Object {\n        \"id\": \"456\",\n        \"type\": \"cats\",\n      },\n    ],\n    null,\n  ],\n]\n`;\n\nexports[`ArraySchema normalization Object normalizes Objects using their values 1`] = `\nObject {\n  \"entities\": Object {\n    \"user\": Object {\n      \"1\": Object {\n        \"id\": 1,\n      },\n      \"2\": Object {\n        \"id\": 2,\n      },\n    },\n  },\n  \"result\": Array [\n    1,\n    2,\n  ],\n}\n`;\n\nexports[`ArraySchema normalization Object normalizes plain arrays as shorthand for ArraySchema 1`] = `\nObject {\n  \"entities\": Object {\n    \"user\": Object {\n      \"1\": Object {\n        \"id\": 1,\n      },\n      \"2\": Object {\n        \"id\": 2,\n      },\n    },\n  },\n  \"result\": Array [\n    1,\n    2,\n  ],\n}\n`;\n\nexports[`ArraySchema normalization Object passes its parent to its children when normalizing 1`] = `\nObject {\n  \"entities\": Object {\n    \"children\": Object {\n      \"4\": Object {\n        \"content\": \"child\",\n        \"id\": 4,\n        \"parentId\": 1,\n        \"parentKey\": \"children\",\n      },\n    },\n    \"parents\": Object {\n      \"1\": Object {\n        \"children\": Array [\n          4,\n        ],\n        \"content\": \"parent\",\n        \"id\": 1,\n      },\n    },\n  },\n  \"result\": 1,\n}\n`;\n"
  },
  {
    "path": "src/schemas/__tests__/__snapshots__/Entity.test.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`EntitySchema denormalization can denormalize already partially denormalized data 1`] = `\nObject {\n  \"food\": Object {\n    \"id\": 1,\n  },\n  \"id\": 1,\n}\n`;\n\nexports[`EntitySchema denormalization can denormalize already partially denormalized data 2`] = `\nImmutable.Map {\n  \"id\": 1,\n  \"food\": Immutable.Map {\n    \"id\": 1,\n  },\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes an entity 1`] = `\nObject {\n  \"id\": 1,\n  \"type\": \"foo\",\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes an entity 2`] = `\nImmutable.Map {\n  \"id\": 1,\n  \"type\": \"foo\",\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes deep entities 1`] = `\nObject {\n  \"food\": Object {\n    \"id\": 1,\n  },\n  \"id\": 1,\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes deep entities 2`] = `\nImmutable.Map {\n  \"id\": 1,\n  \"food\": Immutable.Map {\n    \"id\": 1,\n  },\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes deep entities 3`] = `\nObject {\n  \"id\": 2,\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes deep entities 4`] = `\nImmutable.Map {\n  \"id\": 2,\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes deep entities with records 1`] = `\nImmutable.Record {\n  \"id\": 1,\n  \"food\": Immutable.Record {\n    \"id\": 1,\n  },\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes deep entities with records 2`] = `\nImmutable.Record {\n  \"id\": 1,\n  \"food\": Immutable.Record {\n    \"id\": 1,\n  },\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes deep entities with records 3`] = `\nImmutable.Record {\n  \"id\": 2,\n  \"food\": null,\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes deep entities with records 4`] = `\nImmutable.Record {\n  \"id\": 2,\n  \"food\": null,\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes recursive dependencies 1`] = `\nObject {\n  \"draftedBy\": Object {\n    \"id\": \"456\",\n    \"reports\": Array [\n      [Circular],\n    ],\n    \"role\": \"manager\",\n  },\n  \"id\": \"123\",\n  \"publishedBy\": Object {\n    \"id\": \"456\",\n    \"reports\": Array [\n      [Circular],\n    ],\n    \"role\": \"manager\",\n  },\n  \"title\": \"Weekly report\",\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes recursive dependencies 2`] = `\nImmutable.Map {\n  \"id\": \"123\",\n  \"title\": \"Weekly report\",\n  \"draftedBy\": Immutable.Map {\n    \"id\": \"456\",\n    \"role\": \"manager\",\n    \"reports\": Immutable.List [\n      Immutable.Map {\n        \"id\": \"123\",\n        \"title\": \"Weekly report\",\n        \"draftedBy\": \"456\",\n        \"publishedBy\": \"456\",\n      },\n    ],\n  },\n  \"publishedBy\": Immutable.Map {\n    \"id\": \"456\",\n    \"role\": \"manager\",\n    \"reports\": Immutable.List [\n      Immutable.Map {\n        \"id\": \"123\",\n        \"title\": \"Weekly report\",\n        \"draftedBy\": \"456\",\n        \"publishedBy\": \"456\",\n      },\n    ],\n  },\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes recursive dependencies 3`] = `\nObject {\n  \"id\": \"456\",\n  \"reports\": Array [\n    Object {\n      \"draftedBy\": [Circular],\n      \"id\": \"123\",\n      \"publishedBy\": [Circular],\n      \"title\": \"Weekly report\",\n    },\n  ],\n  \"role\": \"manager\",\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes recursive dependencies 4`] = `\nImmutable.Map {\n  \"id\": \"456\",\n  \"role\": \"manager\",\n  \"reports\": Immutable.List [\n    Immutable.Map {\n      \"id\": \"123\",\n      \"title\": \"Weekly report\",\n      \"draftedBy\": Immutable.Map {\n        \"id\": \"456\",\n        \"role\": \"manager\",\n        \"reports\": Immutable.List [\n          \"123\",\n        ],\n      },\n      \"publishedBy\": Immutable.Map {\n        \"id\": \"456\",\n        \"role\": \"manager\",\n        \"reports\": Immutable.List [\n          \"123\",\n        ],\n      },\n    },\n  ],\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes to undefined for missing data 1`] = `\nObject {\n  \"food\": undefined,\n  \"id\": 1,\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes to undefined for missing data 2`] = `\nImmutable.Map {\n  \"id\": 1,\n  \"food\": undefined,\n}\n`;\n\nexports[`EntitySchema denormalization denormalizes to undefined for missing data 3`] = `undefined`;\n\nexports[`EntitySchema denormalization denormalizes to undefined for missing data 4`] = `undefined`;\n\nexports[`EntitySchema normalization idAttribute can build the entity's ID from the parent object 1`] = `\nObject {\n  \"entities\": Object {\n    \"users\": Object {\n      \"tacos-user-4\": Object {\n        \"id\": \"4\",\n        \"name\": \"Jimmy\",\n      },\n    },\n  },\n  \"result\": Object {\n    \"name\": \"tacos\",\n    \"user\": \"tacos-user-4\",\n  },\n}\n`;\n\nexports[`EntitySchema normalization idAttribute can normalize entity IDs based on their object key 1`] = `\nObject {\n  \"entities\": Object {\n    \"users\": Object {\n      \"4\": Object {\n        \"name\": \"taco\",\n      },\n      \"56\": Object {\n        \"name\": \"burrito\",\n      },\n    },\n  },\n  \"result\": Object {\n    \"4\": Object {\n      \"id\": \"4\",\n      \"schema\": \"users\",\n    },\n    \"56\": Object {\n      \"id\": \"56\",\n      \"schema\": \"users\",\n    },\n  },\n}\n`;\n\nexports[`EntitySchema normalization idAttribute can use a custom idAttribute string 1`] = `\nObject {\n  \"entities\": Object {\n    \"users\": Object {\n      \"134351\": Object {\n        \"id_str\": \"134351\",\n        \"name\": \"Kathy\",\n      },\n    },\n  },\n  \"result\": \"134351\",\n}\n`;\n\nexports[`EntitySchema normalization mergeStrategy can use a custom merging strategy 1`] = `\nObject {\n  \"entities\": Object {\n    \"tacos\": Object {\n      \"1\": Object {\n        \"alias\": \"bar\",\n        \"id\": 1,\n        \"name\": \"foo\",\n      },\n    },\n  },\n  \"result\": Array [\n    1,\n    1,\n  ],\n}\n`;\n\nexports[`EntitySchema normalization mergeStrategy defaults to plain merging 1`] = `\nObject {\n  \"entities\": Object {\n    \"tacos\": Object {\n      \"1\": Object {\n        \"alias\": \"bar\",\n        \"id\": 1,\n        \"name\": \"bar\",\n      },\n    },\n  },\n  \"result\": Array [\n    1,\n    1,\n  ],\n}\n`;\n\nexports[`EntitySchema normalization normalizes an entity 1`] = `\nObject {\n  \"entities\": Object {\n    \"item\": Object {\n      \"1\": Object {\n        \"id\": 1,\n      },\n    },\n  },\n  \"result\": 1,\n}\n`;\n\nexports[`EntitySchema normalization processStrategy can use a custom processing strategy 1`] = `\nObject {\n  \"entities\": Object {\n    \"tacos\": Object {\n      \"1\": Object {\n        \"id\": 1,\n        \"name\": \"foo\",\n        \"slug\": \"thing-1\",\n      },\n    },\n  },\n  \"result\": 1,\n}\n`;\n\nexports[`EntitySchema normalization processStrategy can use information from the parent in the process strategy 1`] = `\nObject {\n  \"entities\": Object {\n    \"children\": Object {\n      \"4\": Object {\n        \"content\": \"child\",\n        \"id\": 4,\n        \"parentId\": 1,\n        \"parentKey\": \"child\",\n      },\n    },\n    \"parents\": Object {\n      \"1\": Object {\n        \"child\": 4,\n        \"content\": \"parent\",\n        \"id\": 1,\n      },\n    },\n  },\n  \"result\": 1,\n}\n`;\n\nexports[`EntitySchema normalization processStrategy is run before and passed to the schema normalization 1`] = `\nObject {\n  \"entities\": Object {\n    \"attachments\": Object {\n      \"456\": Object {\n        \"id\": \"456\",\n      },\n    },\n    \"entries\": Object {\n      \"123\": Object {\n        \"data\": Object {\n          \"attachment\": \"456\",\n        },\n        \"id\": \"123\",\n        \"type\": \"message\",\n      },\n    },\n  },\n  \"result\": \"123\",\n}\n`;\n"
  },
  {
    "path": "src/schemas/__tests__/__snapshots__/Object.test.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`ObjectSchema denormalization denormalizes an object 1`] = `\nObject {\n  \"user\": Object {\n    \"id\": 1,\n    \"name\": \"Nacho\",\n  },\n}\n`;\n\nexports[`ObjectSchema denormalization denormalizes an object 2`] = `\nObject {\n  \"user\": Immutable.Map {\n    \"id\": 1,\n    \"name\": \"Nacho\",\n  },\n}\n`;\n\nexports[`ObjectSchema denormalization denormalizes an object 3`] = `\nImmutable.Map {\n  \"user\": Immutable.Map {\n    \"id\": 1,\n    \"name\": \"Nacho\",\n  },\n}\n`;\n\nexports[`ObjectSchema denormalization denormalizes an object that contains a property representing a an object with an id of zero 1`] = `\nObject {\n  \"user\": Object {\n    \"id\": 0,\n    \"name\": \"Chancho\",\n  },\n}\n`;\n\nexports[`ObjectSchema denormalization denormalizes an object that contains a property representing a an object with an id of zero 2`] = `\nObject {\n  \"user\": Immutable.Map {\n    \"id\": 0,\n    \"name\": \"Chancho\",\n  },\n}\n`;\n\nexports[`ObjectSchema denormalization denormalizes an object that contains a property representing a an object with an id of zero 3`] = `\nImmutable.Map {\n  \"user\": Immutable.Map {\n    \"id\": 0,\n    \"name\": \"Chancho\",\n  },\n}\n`;\n\nexports[`ObjectSchema denormalization denormalizes plain object shorthand 1`] = `\nObject {\n  \"user\": Object {\n    \"id\": 1,\n    \"name\": \"Jane\",\n  },\n}\n`;\n\nexports[`ObjectSchema denormalization denormalizes plain object shorthand 2`] = `\nObject {\n  \"user\": Immutable.Map {\n    \"id\": 1,\n    \"name\": \"Jane\",\n  },\n}\n`;\n\nexports[`ObjectSchema denormalization denormalizes plain object shorthand 3`] = `\nImmutable.Map {\n  \"user\": Immutable.Map {\n    \"id\": 1,\n    \"name\": \"Jane\",\n  },\n}\n`;\n\nexports[`ObjectSchema normalization filters out undefined and null values 1`] = `\nObject {\n  \"entities\": Object {\n    \"user\": Object {\n      \"1\": Object {\n        \"id\": \"1\",\n      },\n      \"undefined\": Object {},\n    },\n  },\n  \"result\": Object {\n    \"bar\": \"1\",\n  },\n}\n`;\n\nexports[`ObjectSchema normalization normalizes an object 1`] = `\nObject {\n  \"entities\": Object {\n    \"user\": Object {\n      \"1\": Object {\n        \"id\": 1,\n      },\n    },\n  },\n  \"result\": Object {\n    \"user\": 1,\n  },\n}\n`;\n\nexports[`ObjectSchema normalization normalizes plain objects as shorthand for ObjectSchema 1`] = `\nObject {\n  \"entities\": Object {\n    \"user\": Object {\n      \"1\": Object {\n        \"id\": 1,\n      },\n    },\n  },\n  \"result\": Object {\n    \"user\": 1,\n  },\n}\n`;\n"
  },
  {
    "path": "src/schemas/__tests__/__snapshots__/Union.test.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`UnionSchema denormalization denormalizes an array of multiple entities using a function to infer the schemaAttribute 1`] = `\nObject {\n  \"id\": 1,\n  \"type\": \"users\",\n  \"username\": \"Janey\",\n}\n`;\n\nexports[`UnionSchema denormalization denormalizes an array of multiple entities using a function to infer the schemaAttribute 2`] = `\nImmutable.Map {\n  \"id\": 1,\n  \"username\": \"Janey\",\n  \"type\": \"users\",\n}\n`;\n\nexports[`UnionSchema denormalization denormalizes an array of multiple entities using a function to infer the schemaAttribute 3`] = `\nObject {\n  \"groupname\": \"People\",\n  \"id\": 2,\n  \"type\": \"groups\",\n}\n`;\n\nexports[`UnionSchema denormalization denormalizes an array of multiple entities using a function to infer the schemaAttribute 4`] = `\nImmutable.Map {\n  \"id\": 2,\n  \"groupname\": \"People\",\n  \"type\": \"groups\",\n}\n`;\n\nexports[`UnionSchema denormalization denormalizes an object using string schemaAttribute 1`] = `\nObject {\n  \"id\": 1,\n  \"type\": \"users\",\n  \"username\": \"Janey\",\n}\n`;\n\nexports[`UnionSchema denormalization denormalizes an object using string schemaAttribute 2`] = `\nImmutable.Map {\n  \"id\": 1,\n  \"username\": \"Janey\",\n  \"type\": \"users\",\n}\n`;\n\nexports[`UnionSchema denormalization denormalizes an object using string schemaAttribute 3`] = `\nObject {\n  \"groupname\": \"People\",\n  \"id\": 2,\n  \"type\": \"groups\",\n}\n`;\n\nexports[`UnionSchema denormalization denormalizes an object using string schemaAttribute 4`] = `\nImmutable.Map {\n  \"id\": 2,\n  \"groupname\": \"People\",\n  \"type\": \"groups\",\n}\n`;\n\nexports[`UnionSchema denormalization returns the original value no schema is given 1`] = `\nObject {\n  \"id\": 1,\n}\n`;\n\nexports[`UnionSchema denormalization returns the original value no schema is given 2`] = `\nImmutable.Map {\n  \"id\": 1,\n}\n`;\n\nexports[`UnionSchema normalization normalizes an array of multiple entities using a function to infer the schemaAttribute 1`] = `\nObject {\n  \"entities\": Object {\n    \"users\": Object {\n      \"1\": Object {\n        \"id\": 1,\n        \"username\": \"Janey\",\n      },\n    },\n  },\n  \"result\": Object {\n    \"id\": 1,\n    \"schema\": \"users\",\n  },\n}\n`;\n\nexports[`UnionSchema normalization normalizes an array of multiple entities using a function to infer the schemaAttribute 2`] = `\nObject {\n  \"entities\": Object {\n    \"groups\": Object {\n      \"2\": Object {\n        \"groupname\": \"People\",\n        \"id\": 2,\n      },\n    },\n  },\n  \"result\": Object {\n    \"id\": 2,\n    \"schema\": \"groups\",\n  },\n}\n`;\n\nexports[`UnionSchema normalization normalizes an array of multiple entities using a function to infer the schemaAttribute 3`] = `\nObject {\n  \"entities\": Object {},\n  \"result\": Object {\n    \"id\": 3,\n    \"notdefined\": \"yep\",\n  },\n}\n`;\n\nexports[`UnionSchema normalization normalizes an object using string schemaAttribute 1`] = `\nObject {\n  \"entities\": Object {\n    \"users\": Object {\n      \"1\": Object {\n        \"id\": 1,\n        \"type\": \"users\",\n      },\n    },\n  },\n  \"result\": Object {\n    \"id\": 1,\n    \"schema\": \"users\",\n  },\n}\n`;\n\nexports[`UnionSchema normalization normalizes an object using string schemaAttribute 2`] = `\nObject {\n  \"entities\": Object {\n    \"groups\": Object {\n      \"2\": Object {\n        \"id\": 2,\n        \"type\": \"groups\",\n      },\n    },\n  },\n  \"result\": Object {\n    \"id\": 2,\n    \"schema\": \"groups\",\n  },\n}\n`;\n"
  },
  {
    "path": "src/schemas/__tests__/__snapshots__/Values.test.js.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`ValuesSchema denormalization denormalizes the values of an object with the given schema 1`] = `\nObject {\n  \"fido\": Object {\n    \"id\": 1,\n    \"type\": \"dogs\",\n  },\n  \"fluffy\": Object {\n    \"id\": 1,\n    \"type\": \"cats\",\n  },\n}\n`;\n\nexports[`ValuesSchema denormalization denormalizes the values of an object with the given schema 2`] = `\nObject {\n  \"fido\": Immutable.Map {\n    \"id\": 1,\n    \"type\": \"dogs\",\n  },\n  \"fluffy\": Immutable.Map {\n    \"id\": 1,\n    \"type\": \"cats\",\n  },\n}\n`;\n\nexports[`ValuesSchema normalization can use a function to determine the schema when normalizing 1`] = `\nObject {\n  \"entities\": Object {\n    \"cats\": Object {\n      \"1\": Object {\n        \"id\": 1,\n        \"type\": \"cat\",\n      },\n    },\n    \"dogs\": Object {\n      \"1\": Object {\n        \"id\": 1,\n        \"type\": \"dog\",\n      },\n    },\n  },\n  \"result\": Object {\n    \"fido\": Object {\n      \"id\": 1,\n      \"schema\": \"dogs\",\n    },\n    \"fluffy\": Object {\n      \"id\": 1,\n      \"schema\": \"cats\",\n    },\n    \"jim\": Object {\n      \"id\": 2,\n      \"type\": \"lizard\",\n    },\n  },\n}\n`;\n\nexports[`ValuesSchema normalization filters out null and undefined values 1`] = `\nObject {\n  \"entities\": Object {\n    \"cats\": Object {\n      \"1\": Object {\n        \"id\": 1,\n        \"type\": \"cats\",\n      },\n    },\n  },\n  \"result\": Object {\n    \"fluffy\": Object {\n      \"id\": 1,\n      \"schema\": \"cats\",\n    },\n  },\n}\n`;\n\nexports[`ValuesSchema normalization normalizes the values of an object with the given schema 1`] = `\nObject {\n  \"entities\": Object {\n    \"cats\": Object {\n      \"1\": Object {\n        \"id\": 1,\n        \"type\": \"cats\",\n      },\n    },\n    \"dogs\": Object {\n      \"1\": Object {\n        \"id\": 1,\n        \"type\": \"dogs\",\n      },\n    },\n  },\n  \"result\": Object {\n    \"fido\": Object {\n      \"id\": 1,\n      \"schema\": \"dogs\",\n    },\n    \"fluffy\": Object {\n      \"id\": 1,\n      \"schema\": \"cats\",\n    },\n  },\n}\n`;\n"
  },
  {
    "path": "typescript-tests/array.ts",
    "content": "import { denormalize, normalize, schema } from '../index'\n\nconst data = [{ id: '123', name: 'Jim' }, { id: '456', name: 'Jane' }];\nconst userSchema = new schema.Entity('users');\n\nconst userListSchema = new schema.Array(userSchema);\nconst normalizedData = normalize(data, userListSchema);\n\nconst userListSchemaAlt = [userSchema];\nconst normalizedDataAlt = normalize(data, userListSchemaAlt);\n\nconst denormalizedData = denormalize(normalizedData.result, userListSchema, normalizedData.entities);\n"
  },
  {
    "path": "typescript-tests/array_schema.ts",
    "content": "import { denormalize, normalize, schema } from '../index'\n\nconst data = [{ id: 1, type: 'admin' }, { id: 2, type: 'user' }];\nconst userSchema = new schema.Entity('users');\nconst adminSchema = new schema.Entity('admins');\n\nconst myArray = new schema.Array(\n  {\n    admins: adminSchema,\n    users: userSchema\n  },\n  (input, parent, key) => `${input.type}s`\n);\n\nconst normalizedData = normalize(data, myArray);\n\nconst denormalizedData = denormalize(normalizedData.result, myArray, normalizedData.entities);\n"
  },
  {
    "path": "typescript-tests/entity.ts",
    "content": "import { denormalize, normalize, schema } from '../index'\n\ntype User = {\n  id_str: string;\n  name: string;\n};\n\ntype Tweet = {\n  id_str: string;\n  url: string;\n  user: User;\n};\n\nconst data = {\n  /* ...*/\n};\nconst user = new schema.Entity<User>(\n  'users',\n  {},\n  { idAttribute: 'id_str', fallbackStrategy: (key) => ({ id_str: key, name: 'Unknown' }) }\n);\nconst tweet = new schema.Entity(\n  'tweets',\n  { user: user },\n  {\n    idAttribute: 'id_str',\n    // Apply everything from entityB over entityA, except for \"favorites\"\n    mergeStrategy: (entityA, entityB) => ({\n      ...entityA,\n      ...entityB,\n      favorites: entityA.favorites\n    }),\n    // Remove the URL field from the entity\n    processStrategy: (entity: Tweet, parent, key) => {\n      const { url, ...entityWithoutUrl } = entity;\n      return entityWithoutUrl;\n    }\n  }\n);\n\nconst normalizedData = normalize(data, tweet);\nconst denormalizedData = denormalize(normalizedData.result, tweet, normalizedData.entities);\n\nconst isTweet = tweet.key === 'tweets';\n"
  },
  {
    "path": "typescript-tests/github.ts",
    "content": "import { normalize, schema } from '../index'\n\nconst user = new schema.Entity('users');\n\nconst label = new schema.Entity('labels');\n\nconst milestone = new schema.Entity('milestones', {\n  creator: user\n});\n\nconst issue = new schema.Entity('issues', {\n  assignee: user,\n  assignees: [user],\n  labels: label,\n  milestone,\n  user\n});\n\nconst pullRequest = new schema.Entity('pullRequests', {\n  assignee: user,\n  assignees: [user],\n  labels: label,\n  milestone,\n  user\n});\n\nconst issueOrPullRequest = new schema.Array(\n  {\n    issues: issue,\n    pullRequests: pullRequest\n  },\n  (entity: any) => (entity.pull_request ? 'pullRequests' : 'issues')\n);\n\nconst data = {\n  /* ...*/\n};\nconst normalizedData = normalize(data, issueOrPullRequest);\nconsole.log(normalizedData);\n"
  },
  {
    "path": "typescript-tests/object.ts",
    "content": "import { normalize, schema } from '../index'\n\ntype Response = {\n  users: Array<{ id: string }>\n}\nconst data: Response = { users: [ { id: 'foo' } ] };\nconst user = new schema.Entity('users');\n\n{\n  const responseSchema = new schema.Object({ users: new schema.Array(user) });\n  const normalizedData = normalize(data, responseSchema);\n}\n\n{\n  const responseSchema = new schema.Object<Response>({ users: (response: Response) => new schema.Array(user) });\n  const normalizedData = normalize(data, responseSchema);\n}\n\n{\n  const responseSchema = { users: new schema.Array(user) };\n  const normalizedData = normalize(data, responseSchema);\n}\n"
  },
  {
    "path": "typescript-tests/relationships.ts",
    "content": "import { normalize, schema } from '../index'\n\nconst userProcessStrategy = (value: any, parent: any, key: string) => {\n  switch (key) {\n    case 'author':\n      return { ...value, posts: [parent.id] };\n    case 'commenter':\n      return { ...value, comments: [parent.id] };\n    default:\n      return { ...value };\n  }\n};\n\nconst userMergeStrategy = (entityA: any, entityB: any) => {\n  return {\n    ...entityA,\n    ...entityB,\n    posts: [...(entityA.posts || []), ...(entityB.posts || [])],\n    comments: [...(entityA.comments || []), ...(entityB.comments || [])]\n  };\n};\n\nconst user = new schema.Entity(\n  'users',\n  {},\n  {\n    mergeStrategy: userMergeStrategy,\n    processStrategy: userProcessStrategy\n  }\n);\n\nconst comment = new schema.Entity(\n  'comments',\n  {\n    commenter: user\n  },\n  {\n    processStrategy: (value: any, parent: any, key: string) => {\n      return { ...value, post: parent.id };\n    }\n  }\n);\n\nconst post = new schema.Entity('posts', {\n  author: user,\n  comments: [comment]\n});\n\nconst data = {\n  /* ...*/\n};\nconst normalizedData = normalize(data, post);\nconsole.log(normalizedData);\n"
  },
  {
    "path": "typescript-tests/union.ts",
    "content": "import { normalize, schema } from '../index'\n\nconst data = { owner: { id: 1, type: 'user' } };\n\nconst user = new schema.Entity('users');\nconst group = new schema.Entity('groups');\nconst unionSchema = new schema.Union(\n  {\n    user: user,\n    group: group\n  },\n  'type'\n);\n\nconst normalizedData = normalize(data, { owner: unionSchema });\n"
  },
  {
    "path": "typescript-tests/values.ts",
    "content": "import { normalize, schema } from '../index'\n\nconst data = { firstThing: { id: 1 }, secondThing: { id: 2 } };\n\nconst item = new schema.Entity('items');\nconst valuesSchema = new schema.Values(item);\n\nconst normalizedData = normalize(data, valuesSchema);\n"
  }
]