Showing preview only (215K chars total). Download the full file or copy to clipboard to get everything.
Repository: paularmstrong/normalizr
Branch: master
Commit: c2ab080641c7
Files: 86
Total size: 196.2 KB
Directory structure:
gitextract_b0ejolxe/
├── .babelrc.js
├── .eslintignore
├── .eslintrc.js
├── .flowconfig
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug.md
│ │ ├── feature_request.md
│ │ └── support_question.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── lock.yml
│ └── workflows/
│ └── close-pull-request.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│ ├── README.md
│ ├── api.md
│ ├── faqs.md
│ ├── introduction.md
│ ├── jsonapi.md
│ └── quickstart.md
├── examples/
│ ├── .eslintrc
│ ├── github/
│ │ ├── README.md
│ │ ├── index.js
│ │ ├── output.json
│ │ └── schema.js
│ ├── redux/
│ │ ├── .babelrc
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.js
│ │ ├── package.json
│ │ └── src/
│ │ ├── api/
│ │ │ ├── index.js
│ │ │ └── schema.js
│ │ └── redux/
│ │ ├── actions.js
│ │ ├── index.js
│ │ ├── modules/
│ │ │ ├── commits.js
│ │ │ ├── issues.js
│ │ │ ├── labels.js
│ │ │ ├── milestones.js
│ │ │ ├── pull-requests.js
│ │ │ ├── repos.js
│ │ │ └── users.js
│ │ ├── reducer.js
│ │ └── selectors.js
│ └── relationships/
│ ├── README.md
│ ├── index.js
│ ├── input.json
│ ├── output.json
│ └── schema.js
├── husky.config.js
├── index.d.ts
├── jest.config.js
├── lint-staged.config.js
├── package.json
├── prettier.config.js
├── rollup.config.js
├── src/
│ ├── __tests__/
│ │ ├── __snapshots__/
│ │ │ └── index.test.js.snap
│ │ └── index.test.js
│ ├── index.js
│ └── schemas/
│ ├── Array.js
│ ├── Entity.js
│ ├── ImmutableUtils.js
│ ├── Object.js
│ ├── Polymorphic.js
│ ├── Union.js
│ ├── Values.js
│ └── __tests__/
│ ├── Array.test.js
│ ├── Entity.test.js
│ ├── Object.test.js
│ ├── Union.test.js
│ ├── Values.test.js
│ └── __snapshots__/
│ ├── Array.test.js.snap
│ ├── Entity.test.js.snap
│ ├── Object.test.js.snap
│ ├── Union.test.js.snap
│ └── Values.test.js.snap
└── typescript-tests/
├── array.ts
├── array_schema.ts
├── entity.ts
├── github.ts
├── object.ts
├── relationships.ts
├── union.ts
└── values.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc.js
================================================
const { NODE_ENV, BABEL_ENV } = process.env;
const cjs = BABEL_ENV === 'cjs' || NODE_ENV === 'test';
module.exports = {
presets: [['@babel/preset-env', { loose: true }]],
plugins: [
// cjs && 'transform-es2015-modules-commonjs',
['@babel/plugin-proposal-object-rest-spread', { loose: true }],
['@babel/plugin-proposal-class-properties', { loose: true }]
].filter(Boolean)
};
================================================
FILE: .eslintignore
================================================
dist/*
node_modules/*
examples/*/node_modules/*
coverage/*
================================================
FILE: .eslintrc.js
================================================
module.exports = {
// babel parser to support ES6/7 features
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 7,
ecmaFeatures: {
experimentalObjectRestSpread: true,
jsx: true,
},
sourceType: 'module',
},
extends: ['plugin:jest/recommended', 'prettier'],
plugins: ['json', 'prettier'],
env: {
es6: true,
node: true,
},
globals: {
document: false,
navigator: false,
window: false,
},
rules: {
'accessor-pairs': 'error',
camelcase: 'off',
'constructor-super': 'error',
curly: ['error', 'all'],
'default-case': ['error', { commentPattern: '^no default$' }],
eqeqeq: ['error', 'allow-null'],
'handle-callback-err': ['error', '^(err|error)$'],
'new-cap': ['error', { newIsCap: true, capIsNew: false }],
'no-alert': 'warn',
'no-array-constructor': 'error',
'no-caller': 'error',
'no-case-declarations': 'error',
'no-class-assign': 'error',
'no-compare-neg-zero': 'error',
'no-cond-assign': 'error',
'no-console': ['error', { allow: ['warn', 'error'] }],
'no-const-assign': 'error',
'no-control-regex': 'error',
'no-debugger': 'error',
'no-delete-var': 'error',
'no-dupe-args': 'error',
'no-dupe-class-members': 'error',
'no-dupe-keys': 'error',
'no-duplicate-case': 'error',
'no-empty-character-class': 'error',
'no-empty-pattern': 'error',
'no-eval': 'error',
'no-ex-assign': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'error',
'no-extra-boolean-cast': 'error',
'no-fallthrough': 'error',
'no-func-assign': 'error',
'no-implied-eval': 'error',
'no-inner-declarations': ['error', 'functions'],
'no-invalid-regexp': 'error',
'no-iterator': 'error',
'no-label-var': 'error',
'no-labels': ['error', { allowLoop: false, allowSwitch: false }],
'no-lone-blocks': 'error',
'no-loop-func': 'error',
'no-multi-str': 'error',
'no-native-reassign': 'error',
'no-negated-in-lhs': 'error',
'no-new': 'error',
'no-new-func': 'error',
'no-new-object': 'error',
'no-new-require': 'error',
'no-new-symbol': 'error',
'no-new-wrappers': 'error',
'no-obj-calls': 'error',
'no-octal': 'error',
'no-octal-escape': 'error',
'no-path-concat': 'error',
'no-proto': 'error',
'no-redeclare': 'error',
'no-regex-spaces': 'error',
'no-return-assign': ['error', 'except-parens'],
'no-script-url': 'error',
'no-self-assign': 'error',
'no-self-compare': 'error',
'no-sequences': 'error',
'no-shadow-restricted-names': 'error',
'no-sparse-arrays': 'error',
'no-this-before-super': 'error',
'no-throw-literal': 'error',
'no-undef': 'error',
'no-undef-init': 'error',
'no-unexpected-multiline': 'error',
'no-unmodified-loop-condition': 'error',
'no-unneeded-ternary': ['error', { defaultAssignment: false }],
'no-unreachable': 'error',
'no-unsafe-finally': 'error',
'no-unused-vars': ['error', { vars: 'all', args: 'none' }],
'no-useless-call': 'error',
'no-useless-computed-key': 'error',
'no-useless-concat': 'error',
'no-useless-constructor': 'error',
'no-useless-escape': 'error',
'no-var': 'error',
'no-with': 'error',
'prefer-const': 'error',
'prefer-rest-params': 'error',
'prefer-template': 'error',
radix: 'error',
'require-yield': 'error',
'sort-imports': ['error', { memberSyntaxSortOrder: ['none', 'all', 'single', 'multiple'], ignoreCase: true }],
'spaced-comment': [
'error',
'always',
{ markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] },
],
'use-isnan': 'error',
'valid-typeof': 'error',
yoda: ['error', 'never'],
'prettier/prettier': 'error',
'jest/consistent-test-it': ['error', { fn: 'test' }],
'jest/no-disabled-tests': 'error',
'jest/no-test-prefixes': 'error',
'jest/prefer-to-be-null': 'error',
'jest/prefer-to-be-undefined': 'error',
'jest/prefer-to-have-length': 'error',
'jest/valid-describe': 'error',
'jest/valid-expect': 'error',
'jest/valid-expect-in-promise': 'error',
},
};
================================================
FILE: .flowconfig
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github:
- paularmstrong
================================================
FILE: .github/ISSUE_TEMPLATE/bug.md
================================================
---
name: 🐛 Bug Report
about: If something isn't working as expected 🤔.
---
# Problem
A short explanation of your problem or use-case is helpful!
<!--
Instead of the below, please also consider
forking the following Codesandbox: https://codesandbox.io/s/normalizr-test-case-0yfcf
Paste your link here when done.
-->
**Input**
Here's how I'm using normalizr:
```js
// Add as much relevant code and input as possible.
const myData = {
// This section is really helpful! A minimum test case goes a long way!
};
const mySchema = new schema.Entity('myschema');
normalize(myData, mySchema);
```
**Output**
Here's what I expect to see when I run the above:
```js
{
result: [1, 2],
entities: { ... }
}
```
Here's what I _actually_ see when I run the above:
```js
{
result: [1, 2],
entities: { ... }
}
```
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: 🚀 Feature Request
about: I have a suggestion (and I might like to implement it myself 😀)!
---
<!--
When trying to solve more solutions with Normalizr, please keep in mind some of the following goals of the package:
* Be lightweight: small package size (single-digit KiBs, gzipped)
* Be easy: too many options in an API can become confusing
* Be clear: the intended purpose of every method should be as obvious as possible
* Is it easy to do this in "userland"? Would it be better off done there?
-->
# Problem
Explain the problem you'd like to have Normalizr handle.
# Solution
Explain 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.
================================================
FILE: .github/ISSUE_TEMPLATE/support_question.md
================================================
---
name: 🤗 Support or Question
about: If you have a question 💬, please check for help on StackOverflow!
---
Issues 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 😁.
---
If you have a support request or question please submit them to one of this resources:
* StackOverflow: https://stackoverflow.com/questions/tagged/normalizr using the tag `normalizr`
* 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
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
Thank you so much for contributing to open source and the Normalizr project!
-->
# Problem
Explain the problem that this pull request aims to resolve.
# Solution
Explain your approach. Sometimes it helps to justify your approach against some others that you didn't choose to explain why yours is better.
# TODO
- [ ] Add & update tests
- [ ] Ensure CI is passing (lint, tests, flow)
- [ ] Update relevant documentation
================================================
FILE: .github/lock.yml
================================================
# Configuration for lock-threads - https://github.com/dessant/lock-threads
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 182
# Issues and pull requests with these labels will not be locked. Set to `[]` to disable
exemptLabels: []
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: Outdated
# Comment to post before locking. Set to `false` to disable
lockComment: >
This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a new issue for
related bugs.
================================================
FILE: .github/workflows/close-pull-request.yml
================================================
name: Close Pull Request
on:
pull_request_target:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: superbrothers/close-pull-request@v3
with:
comment: 'Normalizr is no longer maintained and does not accept pull requests. Please maintain your own fork of this repository.'
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules/*
dist/*
*.log
coverage/*
.coveralls.yml
.eslintcache
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- '10'
- '12'
- '14'
script:
- npm run lint:ci
- npm run test:ci
- npm run build
- npm run typecheck
after_success:
- npm run test:coverage
================================================
FILE: CHANGELOG.md
================================================
# v3.6.1
- **Fixed** Add types for fallback strategy
- **Chore** Upgraded development dependencies
# v3.6.0
- **Added** `fallbackStrategy` for denormalization (#422)
- **Fixed** entities can be `undefined` in TS defs if none found (#435)
# v3.5.0
- **Added** ability to dynamically set nested schema type (#415)
- **Changed** Enable loose transformation for object spread operator to improve performance (#431)
- **Fixed** don't use schema to attribute mapping on singular array schemas (#387)
- **Fixed** When normalize() receives null input, don't say it is an object (#411)
- **Fixed** Improve performance of circular reference detection (#420)
# v3.4.0
- **Changed** Now built with Babel 7
- **Added** Support for circular references (gh-335)
- **Added** Symbols are valid keys for Entity keys (gh-369)
- **Added/Changed** Typescript definitions include generics for `normalize` (gh-363)
- **Fixed** denormalization skipping of falsy valued ids used in `Object` schemas (gh-345)
- **Chore** Update dev dependencies
- **Chore** Added Greenkeeper
# v3.3.0
- **Added** ES Module builds
- **Fixed** type error with typescript on array+object shorthand (gh-322)
# v3.2.0
- **Added** Support denormalizing from Immutable entities (gh-228)
- **Added** Brought back `get idAttribute()` to `schema.Entity` (gh-226)
- **Fixed** Gracefully handle missing data in `denormalize` (gh-232)
- **Fixed** Prevent infinite recursion in `denormalize` (gh-220)
# v3.1.0
- **Added** `denormalize`. (gh-214)
- **Changed** No longer requires all input in a polymorphic schema (`Array`, `Union`, `Values`) have a matching schema definition. (gh-208)
- **Changed** Builds do both rollup and plain babel file conversions. `"main"` property in package.json points to babel-converted files.
# v3.0.0
The entire normalizr package has been rewritten from v2.x for this version. Please refer to the [documentation](/docs) for all changes.
## Added
- `schema.Entity`
- `processStrategy` for modifying `Entity` objects before they're moved to the `entities` stack.
- `mergeStrategy` for merging with multiple entities with the same ID.
- Added `schema.Object`, with a shorthand of `{}`
- Added `schema.Array`, with a shorthand of `[ schema ]`
## Changed
- `Schema` has been moved to a `schema` namespace, available at `schema.Entity`
- `arrayOf` has been replaced by `schema.Array` or `[]`
- `unionOf` has been replaced by `schema.Union`
- `valuesOf` has been replaced by `schema.Values`
## Removed
- `normalize` no longer accepts an optional `options` argument. All options are assigned at the schema level.
- Entity schema no longer accepts `defaults` as an option. Use a custom `processStrategy` option to apply defaults as needed.
- `assignEntity` has been replaced by `processStrategy`
- `meta` option. See `processStrategy`
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
## Issues
1. Follow the [Issue Template](/.github/ISSUE_TEMPLATE.md) provided when opening a new Issue.
2. Provide a minimal, reproducible test-case.
3. Do not ask for help or usage questions in Issues. Use [StackOverflow](http://stackoverflow.com/questions/tagged/normalizr) for those.
## Pull Requests
First, thank you so much for contributing to open source and the Normalizr project!
Follow the instructions on the Pull Request Template (shown when you open a new PR) and make sure you've done the following:
- [ ] Add & update tests
- [ ] Ensure CI is passing (lint, tests)
- [ ] Update relevant documentation
## Setup
Normalizr uses [yarn](https://yarnpkg.com) for development dependency management. Ensure you have it installed before continuing.
```sh
yarn
```
## Running Tests
```sh
npm run test
```
## Lint
Standard 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:
```sh
npm run lint
```
## Building
Normalizr 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:
```sh
npm run build
```
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 Dan Abramov, Paul Armstrong
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# normalizr [](https://travis-ci.org/paularmstrong/normalizr) [](https://coveralls.io/github/paularmstrong/normalizr?branch=master) [](https://www.npmjs.com/package/normalizr) [](https://www.npmjs.com/package/normalizr)
# 📣 Normalizr is no longer maintained
Due 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.
## FAQs
### Should I still use Normalizr?
If 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.
### What should I do if I want other features or found a bug?
Fork [Normalizr on Github](https://github.com/paularmstrong/normalizr) and maintain a version yourself.
### Can I contribute back to Normalizr?
There 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.
================================================
FILE: docs/README.md
================================================
# normalizr [](https://travis-ci.org/paularmstrong/normalizr) [](https://coveralls.io/github/paularmstrong/normalizr?branch=master) [](https://www.npmjs.com/package/normalizr) [](https://www.npmjs.com/package/normalizr)
## Install
Install from the NPM repository using yarn or npm:
```shell
yarn add normalizr
```
```shell
npm install normalizr
```
## Motivation
Many 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/).
## Solution
Normalizr is a small, but powerful utility for taking JSON with a schema definition and returning nested entities with their IDs, gathered in dictionaries.
## Documentation
- [Introduction](/docs/introduction.md)
- [Build Files](/docs/introduction.md#build-files)
- [Quick Start](/docs/quickstart.md)
- [API](/docs/api.md)
- [normalize](/docs/api.md#normalizedata-schema)
- [denormalize](/docs/api.md#denormalizeinput-schema-entities)
- [schema](/docs/api.md#schema)
- [Using with JSONAPI](/docs/jsonapi.md)
## Examples
- [Normalizing GitHub Issues](/examples/github)
- [Relational Data](/examples/relationships)
- [Interactive Redux](/examples/redux)
## Quick Start
Consider a typical blog post. The API response for a single post might look something like this:
```json
{
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"commenter": {
"id": "2",
"name": "Nicole"
}
}
]
}
```
We have two nested entity types within our `article`: `users` and `comments`. Using various `schema`, we can normalize all three entity types down:
```js
import { normalize, schema } from 'normalizr';
// Define a users schema
const user = new schema.Entity('users');
// Define your comments schema
const comment = new schema.Entity('comments', {
commenter: user,
});
// Define your article
const article = new schema.Entity('articles', {
author: user,
comments: [comment],
});
const normalizedData = normalize(originalData, article);
```
Now, `normalizedData` will be:
```js
{
result: "123",
entities: {
"articles": {
"123": {
id: "123",
author: "1",
title: "My awesome blog post",
comments: [ "324" ]
}
},
"users": {
"1": { "id": "1", "name": "Paul" },
"2": { "id": "2", "name": "Nicole" }
},
"comments": {
"324": { id: "324", "commenter": "2" }
}
}
}
```
## Dependencies
None.
## Credits
Normalizr 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).
================================================
FILE: docs/api.md
================================================
# API
- [normalize](#normalizedata-schema)
- [denormalize](#denormalizeinput-schema-entities)
- [schema](#schema)
- [Array](#arraydefinition-schemaattribute)
- [Entity](#entitykey-definition---options--)
- [Object](#objectdefinition)
- [Union](#uniondefinition-schemaattribute)
- [Values](#valuesdefinition-schemaattribute)
## `normalize(data, schema)`
Normalizes input data per the schema definition provided.
- `data`: **required** Input JSON (or plain JS object) data that needs normalization.
- `schema`: **required** A schema definition
### Usage
```js
import { normalize, schema } from 'normalizr';
const myData = { users: [{ id: 1 }, { id: 2 }] };
const user = new schema.Entity('users');
const mySchema = { users: [user] };
const normalizedData = normalize(myData, mySchema);
```
### Output
```js
{
result: { users: [ 1, 2 ] },
entities: {
users: {
'1': { id: 1 },
'2': { id: 2 }
}
}
}
```
## `denormalize(input, schema, entities)`
Denormalizes an input based on schema and provided entities from a plain object or Immutable data. The reverse of `normalize`.
_Special Note:_ Be careful with denormalization. Prematurely reverting your data to large, nested objects could cause performance impacts in React (and other) applications.
If 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.
- `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`.
- `schema`: **required** A schema definition that was used to get the value for `input`.
- `entities`: **required** An object, keyed by entity schema names that may appear in the denormalized output. Also accepts an object with Immutable data.
### Usage
```js
import { denormalize, schema } from 'normalizr';
const user = new schema.Entity('users');
const mySchema = { users: [user] };
const entities = { users: { '1': { id: 1 }, '2': { id: 2 } } };
const denormalizedData = denormalize({ users: [1, 2] }, mySchema, entities);
```
### Output
```js
{
users: [{ id: 1 }, { id: 2 }];
}
```
## `schema`
### `Array(definition, schemaAttribute)`
Creates 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.
_Note: The same behavior can be defined with shorthand syntax: `[ mySchema ]`_
- `definition`: **required** A singular schema that this array contains _or_ a mapping of schema to attribute values.
- `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.
Can be a string or a function. If given a function, accepts the following arguments:
_ `value`: The input value of the entity.
_ `parent`: The parent object of the input array. \* `key`: The key at which the input array appears on the parent object.
#### Instance Methods
- `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.
#### Usage
To describe a simple array of a singular entity type:
```js
const data = [{ id: '123', name: 'Jim' }, { id: '456', name: 'Jane' }];
const userSchema = new schema.Entity('users');
const userListSchema = new schema.Array(userSchema);
// or use shorthand syntax:
const userListSchema = [userSchema];
const normalizedData = normalize(data, userListSchema);
```
#### Output
```js
{
entities: {
users: {
'123': { id: '123', name: 'Jim' },
'456': { id: '456', name: 'Jane' }
}
},
result: [ '123', '456' ]
}
```
If your input data is an array of more than one type of entity, it is necessary to define a schema mapping.
_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._
For example:
```js
const data = [{ id: 1, type: 'admin' }, { id: 2, type: 'user' }];
const userSchema = new schema.Entity('users');
const adminSchema = new schema.Entity('admins');
const myArray = new schema.Array(
{
admins: adminSchema,
users: userSchema
},
(input, parent, key) => `${input.type}s`
);
const normalizedData = normalize(data, myArray);
```
#### Output
```js
{
entities: {
admins: { '1': { id: 1, type: 'admin' } },
users: { '2': { id: 2, type: 'user' } }
},
result: [
{ id: 1, schema: 'admins' },
{ id: 2, schema: 'users' }
]
}
```
### `Entity(key, definition = {}, options = {})`
- `key`: **required** The key name under which all entities of this type will be listed in the normalized response. Must be a string name.
- `definition`: A definition of the nested entities found within this entity. Defaults to empty object.
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.
- `options`:
- `idAttribute`: The attribute where unique IDs for each of this entity type can be found.
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.
As a function, accepts the following arguments, in order:
- `value`: The input value of the entity.
- `parent`: The parent object of the input array.
- `key`: The key at which the input array appears on the parent object.
- `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.
- `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.
_Note: It is recommended to always return a copy of your input and not modify the original._
The function accepts the following arguments, in order:
- `value`: The input value of the entity.
- `parent`: The parent object of the input array.
- `key`: The key at which the input array appears on the parent object.
- `fallbackStrategy(key, schema)`: Strategy to use when denormalizing data structures with id references to missing entities.
- `key`: The key at which the input array appears on the parent object.
- `schema`: The schema of the missing entity
#### Instance Methods
- `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.
#### Instance Attributes
- `key`: Returns the key provided to the constructor.
- `idAttribute`: Returns the idAttribute provided to the constructor in options.
#### Usage
```js
const data = { id_str: '123', url: 'https://twitter.com', user: { id_str: '456', name: 'Jimmy' } };
const user = new schema.Entity('users', {}, { idAttribute: 'id_str' });
const tweet = new schema.Entity(
'tweets',
{ user: user },
{
idAttribute: 'id_str',
// Apply everything from entityB over entityA, except for "favorites"
mergeStrategy: (entityA, entityB) => ({
...entityA,
...entityB,
favorites: entityA.favorites
}),
// Remove the URL field from the entity
processStrategy: (entity) => omit(entity, 'url')
}
);
const normalizedData = normalize(data, tweet);
```
#### Output
```js
{
entities: {
tweets: { '123': { id_str: '123', user: '456' } },
users: { '456': { id_str: '456', name: 'Jimmy' } }
},
result: '123'
}
```
#### `idAttribute` Usage
When passing the `idAttribute` a function, it should return the IDs value.
For Example:
```js
const data = [{ id: '1', guest_id: null, name: 'Esther' }, { id: '1', guest_id: '22', name: 'Tom' }];
const patronsSchema = new schema.Entity('patrons', undefined, {
// idAttribute *functions* must return the ids **value** (not key)
idAttribute: (value) => (value.guest_id ? `${value.id}-${value.guest_id}` : value.id)
});
normalize(data, [patronsSchema]);
```
#### Output
```js
{
entities: {
patrons: {
'1': { id: '1', guest_id: null, name: 'Esther' },
'1-22': { id: '1', guest_id: '22', name: 'Tom' },
}
},
result: ['1', '1-22']
}
```
#### `fallbackStrategy` Usage
```js
const users = {
'1': { id: '1', name: "Emily", requestState: 'SUCCEEDED' },
'2': { id: '2', name: "Douglas", requestState: 'SUCCEEDED' }
};
const books = {
'1': {id: '1', name: "Book 1", author: 1 },
'2': {id: '2', name: "Book 2", author: 2 },
'3': {id: '3', name: "Book 3", author: 3 }
};
const authorSchema = new schema.Entity('authors', {}, {
fallbackStrategy: (key, schema) => {
return {
[schema.idAttribute]: key,
name: 'Unknown',
requestState: 'NONE'
};
}
});
const bookSchema = new schema.Entity('books', {
author: authorSchema
});
denormalize([1, 2, 3], [bookSchema], {
books,
authors: users
})
```
#### Output
```js
[
{
id: '1',
name: "Book 1",
author: { id: '1', name: "Emily", requestState: 'SUCCEEDED' }
},
{
id: '2',
name: "Book 2",
author: { id: '2', name: "Douglas", requestState: 'SUCCEEDED' },
},
{
id: '3',
name: "Book 3",
author: { id: '3', name: "Unknown", requestState: 'NONE' },
}
]
```
### `Object(definition)`
Define a plain object mapping that has values needing to be normalized into Entities. _Note: The same behavior can be defined with shorthand syntax: `{ ... }`_
- `definition`: **required** A definition of the nested entities found within this object. Defaults to empty object.
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.
#### Instance Methods
- `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.
#### Usage
```js
// Example data response
const data = { users: [{ id: '123', name: 'Beth' }] };
const user = new schema.Entity('users');
const responseSchema = new schema.Object({ users: new schema.Array(user) });
// or shorthand
const responseSchema = { users: new schema.Array(user) };
const normalizedData = normalize(data, responseSchema);
```
#### Output
```js
{
entities: {
users: { '123': { id: '123', name: 'Beth' } }
},
result: { users: [ '123' ] }
}
```
### `Union(definition, schemaAttribute)`
Describe 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.
- `definition`: **required** An object mapping the definition of the nested entities found within the input array
- `schemaAttribute`: **required** The attribute on each entity found that defines what schema, per the definition mapping, to use when normalizing.
Can be a string or a function. If given a function, accepts the following arguments:
- `value`: The input value of the entity.
- `parent`: The parent object of the input array.
- `key`: The key at which the input array appears on the parent object.
#### Instance Methods
- `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.
#### Usage
_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._
```js
const data = { owner: { id: 1, type: 'user', name: 'Anne' } };
const user = new schema.Entity('users');
const group = new schema.Entity('groups');
const unionSchema = new schema.Union(
{
user: user,
group: group
},
'type'
);
const normalizedData = normalize(data, { owner: unionSchema });
```
#### Output
```js
{
entities: {
users: { '1': { id: 1, type: 'user', name: 'Anne' } }
},
result: { owner: { id: 1, schema: 'user' } }
}
```
### `Values(definition, schemaAttribute)`
Describes a map whose values follow the given schema.
- `definition`: **required** A singular schema that this array contains _or_ a mapping of schema to attribute values.
- `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.
Can be a string or a function. If given a function, accepts the following arguments:
- `value`: The input value of the entity.
- `parent`: The parent object of the input array.
- `key`: The key at which the input array appears on the parent object.
#### Instance Methods
- `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.
#### Usage
```js
const data = { firstThing: { id: 1 }, secondThing: { id: 2 } };
const item = new schema.Entity('items');
const valuesSchema = new schema.Values(item);
const normalizedData = normalize(data, valuesSchema);
```
#### Output
```js
{
entities: {
items: { '1': { id: 1 }, '2': { id: 2 } }
},
result: { firstThing: 1, secondThing: 2 }
}
```
If 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`.
_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._
For example:
```js
const data = {
'1': { id: 1, type: 'admin' },
'2': { id: 2, type: 'user' }
};
const userSchema = new schema.Entity('users');
const adminSchema = new schema.Entity('admins');
const valuesSchema = new schema.Values(
{
admins: adminSchema,
users: userSchema
},
(input, parent, key) => `${input.type}s`
);
const normalizedData = normalize(data, valuesSchema);
```
#### Output
```js
{
entities: {
admins: { '1': { id: 1, type: 'admin' } },
users: { '2': { id: 2, type: 'user' } }
},
result: {
'1': { id: 1, schema: 'admins' },
'2': { id: 2, schema: 'users' }
}
}
```
================================================
FILE: docs/faqs.md
================================================
# Frequently Asked Questions
If 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.
================================================
FILE: docs/introduction.md
================================================
# Introduction
## Motivation
Many 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/).
## Solution
Normalizr is a small, but powerful utility for taking JSON with a schema definition and returning nested entities with their IDs, gathered in dictionaries.
### Example
The following nested object:
```js
[
{
id: 1,
title: 'Some Article',
author: {
id: 1,
name: 'Dan'
}
},
{
id: 2,
title: 'Other Article',
author: {
id: 1,
name: 'Dan'
}
}
];
```
Can be normalized to:
```js
{
result: [1, 2],
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
}
```
## Build Files
Normalizr is built for various environments
- `src/*`
- 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`.
- `normalizr.js`, `normalizr.min.js`
- [CommonJS](http://davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/)
- `normalizr.amd.js`, `normalizr.amd.min.js`
- [Asynchronous Module Definition](http://davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/)
- `normalizr.umd.js`, `normalizr.umn.min.js`
- [Universal Module Definition](http://davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/)
- `normalizr.browser.js`, `normalizr.browser.min.js`
- [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.
- 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.
================================================
FILE: docs/jsonapi.md
================================================
# Normalizr and JSONAPI
If 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.
Just 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\*:
- [stevenpetryk/jsonapi-normalizer](https://github.com/stevenpetryk/jsonapi-normalizer)
- [yury-dymov/json-api-normalizer](https://github.com/yury-dymov/json-api-normalizer)
- [JSONAPI client libraries](http://jsonapi.org/implementations/#client-libraries-javascript)
**Note:** These are in no particular order. Review all libraries on your own before deciding which is best for your particular use-case.
================================================
FILE: docs/quickstart.md
================================================
# Quick Start
Consider a typical blog post. The API response for a single post might look something like this:
```json
{
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"commenter": {
"id": "2",
"name": "Nicole"
}
}
]
}
```
We have two nested entity types within our `article`: `users` and `comments`. Using various `schema`, we can normalize all three entity types down:
```js
import { normalize, schema } from 'normalizr';
// Define a users schema
const user = new schema.Entity('users');
// Define your comments schema
const comment = new schema.Entity('comments', {
commenter: user
});
// Define your article
const article = new schema.Entity('articles', {
author: user,
comments: [comment]
});
const normalizedData = normalize(originalData, article);
```
Now, `normalizedData` will be:
```js
{
result: "123",
entities: {
"articles": {
"123": {
id: "123",
author: "1",
title: "My awesome blog post",
comments: [ "324" ]
}
},
"users": {
"1": { "id": "1", "name": "Paul" },
"2": { "id": "2", "name": "Nicole" }
},
"comments": {
"324": { id: "324", "commenter": "2" }
}
}
}
```
================================================
FILE: examples/.eslintrc
================================================
{
"rules": {
"no-console": "off"
}
}
================================================
FILE: examples/github/README.md
================================================
# Normalizing GitHub Issues
This is a barebones example for node to illustrate how normalizing the GitHub Issues API endpoint could work.
## Running
```sh
# from the root directory:
yarn
# from this directory:
../../node_modules/.bin/babel-node ./index.js
```
## Files
* [index.js](/examples/github/index.js): Pulls live data from the GitHub API for this project's issues and normalizes the JSON.
* [output.json](/examples/github/output.json): A sample of the normalized output.
* [schema.js](/examples/github/schema.js): The schema used to normalize the GitHub issues.
================================================
FILE: examples/github/index.js
================================================
import * as schema from './schema';
import fs from 'fs';
import https from 'https';
import { normalize } from '../../src';
import path from 'path';
let data = '';
const request = https.request(
{
host: 'api.github.com',
path: '/repos/paularmstrong/normalizr/issues',
method: 'get',
headers: {
'user-agent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',
},
},
(res) => {
res.on('data', (d) => {
data += d;
});
res.on('end', () => {
const normalizedData = normalize(JSON.parse(data), schema.issueOrPullRequest);
const out = JSON.stringify(normalizedData, null, 2);
fs.writeFileSync(path.resolve(__dirname, './output.json'), out);
});
res.on('error', (e) => {
console.log(e);
});
}
);
request.end();
================================================
FILE: examples/github/output.json
================================================
{
"entities": {
"users": {
"14322": {
"login": "whistlerbrk",
"id": 14322,
"avatar_url": "https://avatars.githubusercontent.com/u/14322?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/whistlerbrk",
"html_url": "https://github.com/whistlerbrk",
"followers_url": "https://api.github.com/users/whistlerbrk/followers",
"following_url": "https://api.github.com/users/whistlerbrk/following{/other_user}",
"gists_url": "https://api.github.com/users/whistlerbrk/gists{/gist_id}",
"starred_url": "https://api.github.com/users/whistlerbrk/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/whistlerbrk/subscriptions",
"organizations_url": "https://api.github.com/users/whistlerbrk/orgs",
"repos_url": "https://api.github.com/users/whistlerbrk/repos",
"events_url": "https://api.github.com/users/whistlerbrk/events{/privacy}",
"received_events_url": "https://api.github.com/users/whistlerbrk/received_events",
"type": "User",
"site_admin": false
},
"33297": {
"login": "paularmstrong",
"id": 33297,
"avatar_url": "https://avatars.githubusercontent.com/u/33297?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/paularmstrong",
"html_url": "https://github.com/paularmstrong",
"followers_url": "https://api.github.com/users/paularmstrong/followers",
"following_url": "https://api.github.com/users/paularmstrong/following{/other_user}",
"gists_url": "https://api.github.com/users/paularmstrong/gists{/gist_id}",
"starred_url": "https://api.github.com/users/paularmstrong/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/paularmstrong/subscriptions",
"organizations_url": "https://api.github.com/users/paularmstrong/orgs",
"repos_url": "https://api.github.com/users/paularmstrong/repos",
"events_url": "https://api.github.com/users/paularmstrong/events{/privacy}",
"received_events_url": "https://api.github.com/users/paularmstrong/received_events",
"type": "User",
"site_admin": false
},
"69485": {
"login": "unindented",
"id": 69485,
"avatar_url": "https://avatars.githubusercontent.com/u/69485?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/unindented",
"html_url": "https://github.com/unindented",
"followers_url": "https://api.github.com/users/unindented/followers",
"following_url": "https://api.github.com/users/unindented/following{/other_user}",
"gists_url": "https://api.github.com/users/unindented/gists{/gist_id}",
"starred_url": "https://api.github.com/users/unindented/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/unindented/subscriptions",
"organizations_url": "https://api.github.com/users/unindented/orgs",
"repos_url": "https://api.github.com/users/unindented/repos",
"events_url": "https://api.github.com/users/unindented/events{/privacy}",
"received_events_url": "https://api.github.com/users/unindented/received_events",
"type": "User",
"site_admin": false
},
"84749": {
"login": "pcompassion",
"id": 84749,
"avatar_url": "https://avatars.githubusercontent.com/u/84749?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/pcompassion",
"html_url": "https://github.com/pcompassion",
"followers_url": "https://api.github.com/users/pcompassion/followers",
"following_url": "https://api.github.com/users/pcompassion/following{/other_user}",
"gists_url": "https://api.github.com/users/pcompassion/gists{/gist_id}",
"starred_url": "https://api.github.com/users/pcompassion/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/pcompassion/subscriptions",
"organizations_url": "https://api.github.com/users/pcompassion/orgs",
"repos_url": "https://api.github.com/users/pcompassion/repos",
"events_url": "https://api.github.com/users/pcompassion/events{/privacy}",
"received_events_url": "https://api.github.com/users/pcompassion/received_events",
"type": "User",
"site_admin": false
},
"655857": {
"login": "adjohu",
"id": 655857,
"avatar_url": "https://avatars.githubusercontent.com/u/655857?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/adjohu",
"html_url": "https://github.com/adjohu",
"followers_url": "https://api.github.com/users/adjohu/followers",
"following_url": "https://api.github.com/users/adjohu/following{/other_user}",
"gists_url": "https://api.github.com/users/adjohu/gists{/gist_id}",
"starred_url": "https://api.github.com/users/adjohu/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/adjohu/subscriptions",
"organizations_url": "https://api.github.com/users/adjohu/orgs",
"repos_url": "https://api.github.com/users/adjohu/repos",
"events_url": "https://api.github.com/users/adjohu/events{/privacy}",
"received_events_url": "https://api.github.com/users/adjohu/received_events",
"type": "User",
"site_admin": false
},
"853220": {
"login": "babsonmatt",
"id": 853220,
"avatar_url": "https://avatars.githubusercontent.com/u/853220?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/babsonmatt",
"html_url": "https://github.com/babsonmatt",
"followers_url": "https://api.github.com/users/babsonmatt/followers",
"following_url": "https://api.github.com/users/babsonmatt/following{/other_user}",
"gists_url": "https://api.github.com/users/babsonmatt/gists{/gist_id}",
"starred_url": "https://api.github.com/users/babsonmatt/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/babsonmatt/subscriptions",
"organizations_url": "https://api.github.com/users/babsonmatt/orgs",
"repos_url": "https://api.github.com/users/babsonmatt/repos",
"events_url": "https://api.github.com/users/babsonmatt/events{/privacy}",
"received_events_url": "https://api.github.com/users/babsonmatt/received_events",
"type": "User",
"site_admin": false
},
"969003": {
"login": "YoruNoHikage",
"id": 969003,
"avatar_url": "https://avatars.githubusercontent.com/u/969003?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/YoruNoHikage",
"html_url": "https://github.com/YoruNoHikage",
"followers_url": "https://api.github.com/users/YoruNoHikage/followers",
"following_url": "https://api.github.com/users/YoruNoHikage/following{/other_user}",
"gists_url": "https://api.github.com/users/YoruNoHikage/gists{/gist_id}",
"starred_url": "https://api.github.com/users/YoruNoHikage/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/YoruNoHikage/subscriptions",
"organizations_url": "https://api.github.com/users/YoruNoHikage/orgs",
"repos_url": "https://api.github.com/users/YoruNoHikage/repos",
"events_url": "https://api.github.com/users/YoruNoHikage/events{/privacy}",
"received_events_url": "https://api.github.com/users/YoruNoHikage/received_events",
"type": "User",
"site_admin": false
},
"1499050": {
"login": "poyannabati",
"id": 1499050,
"avatar_url": "https://avatars.githubusercontent.com/u/1499050?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/poyannabati",
"html_url": "https://github.com/poyannabati",
"followers_url": "https://api.github.com/users/poyannabati/followers",
"following_url": "https://api.github.com/users/poyannabati/following{/other_user}",
"gists_url": "https://api.github.com/users/poyannabati/gists{/gist_id}",
"starred_url": "https://api.github.com/users/poyannabati/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/poyannabati/subscriptions",
"organizations_url": "https://api.github.com/users/poyannabati/orgs",
"repos_url": "https://api.github.com/users/poyannabati/repos",
"events_url": "https://api.github.com/users/poyannabati/events{/privacy}",
"received_events_url": "https://api.github.com/users/poyannabati/received_events",
"type": "User",
"site_admin": false
},
"1591483": {
"login": "arb",
"id": 1591483,
"avatar_url": "https://avatars.githubusercontent.com/u/1591483?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/arb",
"html_url": "https://github.com/arb",
"followers_url": "https://api.github.com/users/arb/followers",
"following_url": "https://api.github.com/users/arb/following{/other_user}",
"gists_url": "https://api.github.com/users/arb/gists{/gist_id}",
"starred_url": "https://api.github.com/users/arb/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/arb/subscriptions",
"organizations_url": "https://api.github.com/users/arb/orgs",
"repos_url": "https://api.github.com/users/arb/repos",
"events_url": "https://api.github.com/users/arb/events{/privacy}",
"received_events_url": "https://api.github.com/users/arb/received_events",
"type": "User",
"site_admin": false
},
"1690457": {
"login": "TryingToImprove",
"id": 1690457,
"avatar_url": "https://avatars.githubusercontent.com/u/1690457?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/TryingToImprove",
"html_url": "https://github.com/TryingToImprove",
"followers_url": "https://api.github.com/users/TryingToImprove/followers",
"following_url": "https://api.github.com/users/TryingToImprove/following{/other_user}",
"gists_url": "https://api.github.com/users/TryingToImprove/gists{/gist_id}",
"starred_url": "https://api.github.com/users/TryingToImprove/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/TryingToImprove/subscriptions",
"organizations_url": "https://api.github.com/users/TryingToImprove/orgs",
"repos_url": "https://api.github.com/users/TryingToImprove/repos",
"events_url": "https://api.github.com/users/TryingToImprove/events{/privacy}",
"received_events_url": "https://api.github.com/users/TryingToImprove/received_events",
"type": "User",
"site_admin": false
},
"5119347": {
"login": "lqzerogg",
"id": 5119347,
"avatar_url": "https://avatars.githubusercontent.com/u/5119347?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/lqzerogg",
"html_url": "https://github.com/lqzerogg",
"followers_url": "https://api.github.com/users/lqzerogg/followers",
"following_url": "https://api.github.com/users/lqzerogg/following{/other_user}",
"gists_url": "https://api.github.com/users/lqzerogg/gists{/gist_id}",
"starred_url": "https://api.github.com/users/lqzerogg/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/lqzerogg/subscriptions",
"organizations_url": "https://api.github.com/users/lqzerogg/orgs",
"repos_url": "https://api.github.com/users/lqzerogg/repos",
"events_url": "https://api.github.com/users/lqzerogg/events{/privacy}",
"received_events_url": "https://api.github.com/users/lqzerogg/received_events",
"type": "User",
"site_admin": false
},
"6775919": {
"login": "BerkeleyTrue",
"id": 6775919,
"avatar_url": "https://avatars.githubusercontent.com/u/6775919?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/BerkeleyTrue",
"html_url": "https://github.com/BerkeleyTrue",
"followers_url": "https://api.github.com/users/BerkeleyTrue/followers",
"following_url": "https://api.github.com/users/BerkeleyTrue/following{/other_user}",
"gists_url": "https://api.github.com/users/BerkeleyTrue/gists{/gist_id}",
"starred_url": "https://api.github.com/users/BerkeleyTrue/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/BerkeleyTrue/subscriptions",
"organizations_url": "https://api.github.com/users/BerkeleyTrue/orgs",
"repos_url": "https://api.github.com/users/BerkeleyTrue/repos",
"events_url": "https://api.github.com/users/BerkeleyTrue/events{/privacy}",
"received_events_url": "https://api.github.com/users/BerkeleyTrue/received_events",
"type": "User",
"site_admin": false
},
"14313723": {
"login": "vimalceg",
"id": 14313723,
"avatar_url": "https://avatars.githubusercontent.com/u/14313723?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/vimalceg",
"html_url": "https://github.com/vimalceg",
"followers_url": "https://api.github.com/users/vimalceg/followers",
"following_url": "https://api.github.com/users/vimalceg/following{/other_user}",
"gists_url": "https://api.github.com/users/vimalceg/gists{/gist_id}",
"starred_url": "https://api.github.com/users/vimalceg/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/vimalceg/subscriptions",
"organizations_url": "https://api.github.com/users/vimalceg/orgs",
"repos_url": "https://api.github.com/users/vimalceg/repos",
"events_url": "https://api.github.com/users/vimalceg/events{/privacy}",
"received_events_url": "https://api.github.com/users/vimalceg/received_events",
"type": "User",
"site_admin": false
}
},
"labels": {
"undefined": {
"0": {
"id": 373884166,
"url": "https://api.github.com/repos/paularmstrong/normalizr/labels/Priority:%20Low",
"name": "Priority: Low",
"color": "009800",
"default": false
},
"1": {
"id": 373884281,
"url": "https://api.github.com/repos/paularmstrong/normalizr/labels/Status:%20Accepted",
"name": "Status: Accepted",
"color": "009800",
"default": false
},
"2": {
"id": 373884447,
"url": "https://api.github.com/repos/paularmstrong/normalizr/labels/Type:%20Bug",
"name": "Type: Bug",
"color": "e11d21",
"default": false
},
"3": {
"id": 373884449,
"url": "https://api.github.com/repos/paularmstrong/normalizr/labels/Type:%20Question",
"name": "Type: Question",
"color": "cc317c",
"default": false
}
}
},
"milestones": {
"1883567": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/milestones/2",
"html_url": "https://github.com/paularmstrong/normalizr/milestone/2",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/milestones/2/labels",
"id": 1883567,
"number": 2,
"title": "2.3.0",
"description": "",
"creator": 33297,
"open_issues": 2,
"closed_issues": 1,
"state": "closed",
"created_at": "2016-07-14T15:24:52Z",
"updated_at": "2016-12-21T21:54:06Z",
"due_on": "2016-08-20T07:00:00Z",
"closed_at": "2016-12-19T19:32:20Z"
},
"2205635": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/milestones/3",
"html_url": "https://github.com/paularmstrong/normalizr/milestone/3",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/milestones/3/labels",
"id": 2205635,
"number": 3,
"title": "3.0.0",
"description": "Fully re-written, simpler, faster, more extendable.",
"creator": 33297,
"open_issues": 3,
"closed_issues": 0,
"state": "open",
"created_at": "2016-12-19T19:32:16Z",
"updated_at": "2016-12-21T15:42:53Z",
"due_on": "2017-01-03T08:00:00Z",
"closed_at": null
}
},
"issues": {
"127301608": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/54",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/54/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/54/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/54/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/54",
"id": 127301608,
"number": 54,
"title": "Should empty arrays be preserved on their entity?",
"user": 853220,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 5,
"created_at": "2016-01-18T20:07:54Z",
"updated_at": "2016-05-24T14:00:18Z",
"closed_at": null,
"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"
},
"134547187": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/68",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/68/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/68/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/68/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/68",
"id": 134547187,
"number": 68,
"title": "Dealing with collections keyed by ID without an ID in the entity itself",
"user": 655857,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 4,
"created_at": "2016-02-18T11:02:18Z",
"updated_at": "2016-05-24T13:59:36Z",
"closed_at": null,
"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"
},
"134935104": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/70",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/70/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/70/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/70/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/70",
"id": 134935104,
"number": 70,
"title": "Keep consistency for relations",
"user": 969003,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 9,
"created_at": "2016-02-19T18:10:17Z",
"updated_at": "2016-05-24T13:45:14Z",
"closed_at": null,
"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"
},
"154328039": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/105",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/105/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/105/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/105/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/105",
"id": 154328039,
"number": 105,
"title": "Update & Organize Documentation",
"user": 33297,
"state": "open",
"locked": false,
"assignee": 33297,
"assignees": [
33297
],
"milestone": 1883567,
"comments": 0,
"created_at": "2016-05-11T19:52:34Z",
"updated_at": "2016-07-14T15:24:58Z",
"closed_at": null,
"body": ""
},
"154328158": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/106",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/106/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/106/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/106/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/106",
"id": 154328158,
"number": 106,
"title": "Clean up tests",
"user": 33297,
"state": "open",
"locked": false,
"assignee": 33297,
"assignees": [
33297
],
"milestone": 1883567,
"comments": 0,
"created_at": "2016-05-11T19:53:07Z",
"updated_at": "2016-07-14T15:25:19Z",
"closed_at": null,
"body": "Currently hard to read through. Should be better organized and easier to maintain.\n"
},
"161111737": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/126",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/126/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/126/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/126/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/126",
"id": 161111737,
"number": 126,
"title": "normalize is take some time if object has more number of keys.",
"user": 14313723,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 3,
"created_at": "2016-06-20T03:41:07Z",
"updated_at": "2016-07-05T22:55:19Z",
"closed_at": null,
"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"
},
"178773735": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/154",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/154/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/154/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/154/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/154",
"id": 178773735,
"number": 154,
"title": "Will arrayOf support a parameter that not a schema?",
"user": 5119347,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 2,
"created_at": "2016-09-23T03:05:59Z",
"updated_at": "2016-09-27T01:58:49Z",
"closed_at": null,
"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"
},
"186469900": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/165",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/165/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/165/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/165/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/165",
"id": 186469900,
"number": 165,
"title": "Django rest framework and normalizr ",
"user": 84749,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 2,
"created_at": "2016-11-01T06:46:33Z",
"updated_at": "2016-11-02T06:30:40Z",
"closed_at": null,
"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"
},
"188006036": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/172",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/172/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/172/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/172/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/172",
"id": 188006036,
"number": 172,
"title": "Set field on child entity based on parent entity on normalization",
"user": 1499050,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 2,
"created_at": "2016-11-08T14:29:51Z",
"updated_at": "2016-12-21T15:02:10Z",
"closed_at": null,
"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? "
},
"191124603": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/176",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/176/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/176/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/176/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/176",
"id": 191124603,
"number": 176,
"title": "bizarre uncaught reference when attempting import of normalizr",
"user": 14322,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 3,
"created_at": "2016-11-22T21:08:09Z",
"updated_at": "2016-11-28T10:11:54Z",
"closed_at": null,
"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"
},
"196525159": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/185",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/185/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/185/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/185/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/185",
"id": 196525159,
"number": 185,
"title": "Request: Add a changelog",
"user": 6775919,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 1,
"created_at": "2016-12-19T21:29:14Z",
"updated_at": "2016-12-19T21:40:26Z",
"closed_at": null,
"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!"
},
"196531836": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/186",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/186/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/186/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/186/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/186",
"id": 196531836,
"number": 186,
"title": "results for arrayOf returns array instead of map as of version 2.3.0",
"user": 6775919,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 0,
"created_at": "2016-12-19T22:01:30Z",
"updated_at": "2016-12-19T22:10:31Z",
"closed_at": null,
"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"
},
"196954427": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/187",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/187/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/187/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/187/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/187",
"id": 196954427,
"number": 187,
"title": "v3 Docs",
"user": 33297,
"state": "open",
"locked": false,
"assignee": 33297,
"assignees": [
33297
],
"milestone": 2205635,
"comments": 0,
"created_at": "2016-12-21T15:04:39Z",
"updated_at": "2016-12-21T21:53:28Z",
"closed_at": null,
"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."
},
"196954928": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/188",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/188/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/188/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/188/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/188",
"id": 196954928,
"number": 188,
"title": "Add In-Repo Examples",
"user": 33297,
"state": "open",
"locked": false,
"assignee": 33297,
"assignees": [
33297
],
"milestone": 2205635,
"comments": 0,
"created_at": "2016-12-21T15:06:29Z",
"updated_at": "2016-12-21T21:53:28Z",
"closed_at": null,
"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."
},
"196963939": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/189",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/189/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/189/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/189/events",
"html_url": "https://github.com/paularmstrong/normalizr/issues/189",
"id": 196963939,
"number": 189,
"title": "Wanted: Typescript definitions & tests for v3.0.0",
"user": 33297,
"state": "open",
"locked": false,
"assignee": 69485,
"assignees": [
69485
],
"milestone": 2205635,
"comments": 2,
"created_at": "2016-12-21T15:42:53Z",
"updated_at": "2016-12-22T14:57:41Z",
"closed_at": null,
"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"
}
},
"pullRequests": {
"165986889": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/135",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/135/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/135/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/135/events",
"html_url": "https://github.com/paularmstrong/normalizr/pull/135",
"id": 165986889,
"number": 135,
"title": "It should be possible to use relations",
"user": 1690457,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 1,
"created_at": "2016-07-17T17:38:38Z",
"updated_at": "2016-09-25T15:15:05Z",
"closed_at": null,
"pull_request": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/pulls/135",
"html_url": "https://github.com/paularmstrong/normalizr/pull/135",
"diff_url": "https://github.com/paularmstrong/normalizr/pull/135.diff",
"patch_url": "https://github.com/paularmstrong/normalizr/pull/135.patch"
},
"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"
},
"183956234": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/161",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/161/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/161/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/161/events",
"html_url": "https://github.com/paularmstrong/normalizr/pull/161",
"id": 183956234,
"number": 161,
"title": "Pass parent object to idAttribute.",
"user": 1591483,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 9,
"created_at": "2016-10-19T13:06:03Z",
"updated_at": "2016-11-29T18:51:34Z",
"closed_at": null,
"pull_request": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/pulls/161",
"html_url": "https://github.com/paularmstrong/normalizr/pull/161",
"diff_url": "https://github.com/paularmstrong/normalizr/pull/161.diff",
"patch_url": "https://github.com/paularmstrong/normalizr/pull/161.patch"
},
"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"
},
"188066564": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/issues/173",
"repository_url": "https://api.github.com/repos/paularmstrong/normalizr",
"labels_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/173/labels{/name}",
"comments_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/173/comments",
"events_url": "https://api.github.com/repos/paularmstrong/normalizr/issues/173/events",
"html_url": "https://github.com/paularmstrong/normalizr/pull/173",
"id": 188066564,
"number": 173,
"title": "Added schema option for assigning the parent id on the child entity",
"user": 1499050,
"state": "open",
"locked": false,
"assignee": null,
"assignees": [],
"milestone": null,
"comments": 2,
"created_at": "2016-11-08T18:22:42Z",
"updated_at": "2016-12-21T15:00:24Z",
"closed_at": null,
"pull_request": {
"url": "https://api.github.com/repos/paularmstrong/normalizr/pulls/173",
"html_url": "https://github.com/paularmstrong/normalizr/pull/173",
"diff_url": "https://github.com/paularmstrong/normalizr/pull/173.diff",
"patch_url": "https://github.com/paularmstrong/normalizr/pull/173.patch"
},
"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"
}
}
},
"result": [
{
"id": 196963939,
"schema": "issues"
},
{
"id": 196954928,
"schema": "issues"
},
{
"id": 196954427,
"schema": "issues"
},
{
"id": 196531836,
"schema": "issues"
},
{
"id": 196525159,
"schema": "issues"
},
{
"id": 191124603,
"schema": "issues"
},
{
"id": 188066564,
"schema": "pullRequests"
},
{
"id": 188006036,
"schema": "issues"
},
{
"id": 186469900,
"schema": "issues"
},
{
"id": 183956234,
"schema": "pullRequests"
},
{
"id": 178773735,
"schema": "issues"
},
{
"id": 165986889,
"schema": "pullRequests"
},
{
"id": 161111737,
"schema": "issues"
},
{
"id": 154328158,
"schema": "issues"
},
{
"id": 154328039,
"schema": "issues"
},
{
"id": 134935104,
"schema": "issues"
},
{
"id": 134547187,
"schema": "issues"
},
{
"id": 127301608,
"schema": "issues"
}
]
}
================================================
FILE: examples/github/schema.js
================================================
import { schema } from '../../src';
export const user = new schema.Entity('users');
export const label = new schema.Entity('labels');
export const milestone = new schema.Entity('milestones', {
creator: user,
});
export const issue = new schema.Entity('issues', {
assignee: user,
assignees: [user],
labels: label,
milestone,
user,
});
export const pullRequest = new schema.Entity('pullRequests', {
assignee: user,
assignees: [user],
labels: label,
milestone,
user,
});
export const issueOrPullRequest = new schema.Array(
{
issues: issue,
pullRequests: pullRequest,
},
(entity) => (entity.pull_request ? 'pullRequests' : 'issues')
);
================================================
FILE: examples/redux/.babelrc
================================================
{
"presets": ["es2015", "stage-1"]
}
================================================
FILE: examples/redux/.gitignore
================================================
node_modules/*
*.log
================================================
FILE: examples/redux/README.md
================================================
# Redux
This 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.

## Running
From this directory, run the following and follow the on-screen options:
```sh
# from the root directory:
yarn # or npm install
# from this directory:
yarn # or npm install
npm run start
```
================================================
FILE: examples/redux/index.js
================================================
import * as Action from './src/redux/actions';
import * as Selector from './src/redux/selectors';
import inquirer from 'inquirer';
import store from './src/redux';
const REPO = 'paularmstrong/normalizr';
const start = () => {
inquirer
.prompt([
{
type: 'input',
name: 'repo',
message: 'What is the slug of the repo you wish to browseMain?',
default: REPO,
validate: (input) => {
if (!/^[a-zA-Z0-9]+\/[a-zA-Z0-9]+/.test(input)) {
return 'Repo slug must be in the form "user/project"';
}
return true;
},
},
])
.then(({ repo }) => {
store.dispatch(Action.setRepo(repo));
main();
});
};
const main = () => {
return inquirer
.prompt([
{
type: 'list',
name: 'action',
message: 'What would you like to do?',
choices: ['Browse current state', 'Get new data', new inquirer.Separator(), 'Quit'],
},
])
.then(({ action }) => {
switch (action) {
case 'Browse current state':
return browseMain();
case 'Get new data':
return pull();
default:
return process.exit();
}
});
};
const browseMain = () => {
return inquirer
.prompt([
{
type: 'list',
name: 'browseMainAction',
message: 'What would you like to do?',
choices: () => {
return [
{ value: 'print', name: 'Print the entire state tree' },
new inquirer.Separator(),
...Object.keys(store.getState()).map((value) => ({ value, name: `Browse ${value}` })),
new inquirer.Separator(),
{ value: 'main', name: 'Go Back to Main Menu' },
];
},
},
])
.then((answers) => {
switch (answers.browseMainAction) {
case 'main':
return main();
case 'print':
console.log(JSON.stringify(store.getState(), null, 2));
return browseMain();
default:
return browse(answers.browseMainAction);
}
});
};
const browse = (stateKey) => {
return inquirer
.prompt([
{
type: 'list',
name: 'action',
message: `Browse ${stateKey}`,
choices: [
{ value: 'count', name: 'Show # of Objects' },
{ value: 'keys', name: 'List All Keys' },
{ value: 'view', name: 'View by Key' },
{ value: 'all', name: 'View All' },
{ value: 'denormalize', name: 'Denormalize' },
new inquirer.Separator(),
{ value: 'browseMain', name: 'Go Back to Browse Menu' },
{ value: 'main', name: 'Go Back to Main Menu' },
],
},
{
type: 'list',
name: 'list',
message: `Select the ${stateKey} to view:`,
choices: Object.keys(store.getState()[stateKey]),
when: ({ action }) => action === 'view',
},
])
.then(({ action, list }) => {
const state = store.getState()[stateKey];
if (list) {
console.log(JSON.stringify(state[list], null, 2));
}
switch (action) {
case 'count':
console.log(`-> ${Object.keys(state).length} items.`);
return browse(stateKey);
case 'keys':
Object.keys(state).map((key) => console.log(key));
return browse(stateKey);
case 'all':
console.log(JSON.stringify(state, null, 2));
return browse(stateKey);
case 'denormalize':
return browseDenormalized(stateKey);
case 'browseMain':
return browseMain();
case 'main':
return main();
default:
return browse(stateKey);
}
});
};
const browseDenormalized = (stateKey) => {
return inquirer
.prompt([
{
type: 'list',
name: 'selector',
message: `Denormalize a/and ${stateKey} entity`,
choices: [
...Object.keys(store.getState()[stateKey]),
new inquirer.Separator(),
{ value: 'browse', name: 'Go Back to Browse Menu' },
{ value: 'main', name: 'Go Back to Main Menu' },
],
},
])
.then(({ selector }) => {
switch (selector) {
case 'browse':
return browse(stateKey);
case 'main':
return main();
default: {
const data = Selector[`select${stateKey.replace(/s$/, '')}`](store.getState(), selector);
console.log(JSON.stringify(data, null, 2));
return browseDenormalized(stateKey);
}
}
});
};
const pull = () => {
return inquirer
.prompt([
{
type: 'list',
name: 'pullAction',
message: 'What data would you like to fetch?',
choices: () => {
return [
...Object.keys(store.getState()).map((value) => ({ value, name: value })),
new inquirer.Separator(),
{ value: 'main', name: 'Go Back to Main Menu' },
];
},
},
])
.then((answers) => {
switch (answers.pullAction) {
case 'commits':
return store.dispatch(Action.getCommits()).then(pull);
case 'issues':
return store.dispatch(Action.getIssues()).then(pull);
case 'labels':
return store.dispatch(Action.getLabels()).then(pull);
case 'milestones':
return store.dispatch(Action.getMilestones()).then(pull);
case 'pullRequests':
return store.dispatch(Action.getPullRequests()).then(pull);
case 'main':
default:
return main();
}
});
};
start();
================================================
FILE: examples/redux/package.json
================================================
{
"name": "normalizr-redux-example",
"version": "0.0.0",
"description": "And example of using Normalizr with Redux",
"main": "index.js",
"author": "Paul Armstrong",
"license": "MIT",
"private": true,
"scripts": {
"start": "babel-node ./"
},
"dependencies": {
"babel-cli": "^6.18.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-stage-1": "^6.16.0",
"github": "^14.0.0",
"inquirer": "^6.3.1",
"redux": "^4.0.1",
"redux-thunk": "^2.1.0"
}
}
================================================
FILE: examples/redux/src/api/index.js
================================================
import GitHubApi from 'github';
export default new GitHubApi({
headers: {
'user-agent': 'Normalizr Redux Example',
},
});
================================================
FILE: examples/redux/src/api/schema.js
================================================
import { schema } from '../../../../src';
export const user = new schema.Entity('users');
export const commit = new schema.Entity(
'commits',
{
author: user,
committer: user,
},
{ idAttribute: 'sha' }
);
export const label = new schema.Entity('labels');
export const milestone = new schema.Entity('milestones', {
creator: user,
});
export const issue = new schema.Entity('issues', {
assignee: user,
assignees: [user],
labels: [label],
milestone,
user,
});
export const pullRequest = new schema.Entity('pullRequests', {
assignee: user,
assignees: [user],
labels: [label],
milestone,
user,
});
export const issueOrPullRequest = new schema.Array(
{
issues: issue,
pullRequests: pullRequest,
},
(entity) => (entity.pull_request ? 'pullRequests' : 'issues')
);
================================================
FILE: examples/redux/src/redux/actions.js
================================================
export { getCommits } from './modules/commits';
export { getIssues } from './modules/issues';
export { getLabels } from './modules/labels';
export { getMilestones } from './modules/milestones';
export { getPullRequests } from './modules/pull-requests';
export { setRepo } from './modules/repos';
export const ADD_ENTITIES = 'ADD_ENTITIES';
export const addEntities = (entities) => ({
type: ADD_ENTITIES,
payload: entities,
});
================================================
FILE: examples/redux/src/redux/index.js
================================================
import * as schema from '../api/schema';
import api from '../api';
import reducer from './reducer';
import thunk from 'redux-thunk';
import { applyMiddleware, createStore } from 'redux';
export default createStore(reducer, applyMiddleware(thunk.withExtraArgument({ api, schema })));
================================================
FILE: examples/redux/src/redux/modules/commits.js
================================================
import * as Repo from './repos';
import { commit } from '../../api/schema';
import { ADD_ENTITIES, addEntities } from '../actions';
import { denormalize, normalize } from '../../../../../src';
export const STATE_KEY = 'commits';
export default function reducer(state = {}, action) {
switch (action.type) {
case ADD_ENTITIES:
return {
...state,
...action.payload.commits,
};
default:
return state;
}
}
export const getCommits = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => {
const state = getState();
const owner = Repo.selectOwner(state);
const repo = Repo.selectRepo(state);
return api.repos
.getCommits({
owner,
repo,
})
.then((response) => {
const data = normalize(response, [schema.commit]);
dispatch(addEntities(data.entities));
return response;
})
.catch((error) => {
console.error(error);
});
};
export const selectHydrated = (state, id) => denormalize(id, commit, state);
================================================
FILE: examples/redux/src/redux/modules/issues.js
================================================
import * as Repo from './repos';
import { issue } from '../../api/schema';
import { ADD_ENTITIES, addEntities } from '../actions';
import { denormalize, normalize } from '../../../../../src';
export const STATE_KEY = 'issues';
export default function reducer(state = {}, action) {
switch (action.type) {
case ADD_ENTITIES:
return {
...state,
...action.payload.issues,
};
default:
return state;
}
}
export const getIssues = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => {
const state = getState();
const owner = Repo.selectOwner(state);
const repo = Repo.selectRepo(state);
return api.issues
.getForRepo({
owner,
repo,
})
.then((response) => {
const data = normalize(response, [schema.issue]);
dispatch(addEntities(data.entities));
return response;
})
.catch((error) => {
console.error(error);
});
};
export const selectHydrated = (state, id) => denormalize(id, issue, state);
================================================
FILE: examples/redux/src/redux/modules/labels.js
================================================
import * as Repo from './repos';
import { label } from '../../api/schema';
import { ADD_ENTITIES, addEntities } from '../actions';
import { denormalize, normalize } from '../../../../../src';
export const STATE_KEY = 'labels';
export default function reducer(state = {}, action) {
switch (action.type) {
case ADD_ENTITIES:
return {
...state,
...action.payload.labels,
};
default:
return state;
}
}
export const getLabels = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => {
const state = getState();
const owner = Repo.selectOwner(state);
const repo = Repo.selectRepo(state);
return api.issues
.getLabels({
owner,
repo,
})
.then((response) => {
const data = normalize(response, [schema.label]);
dispatch(addEntities(data.entities));
return response;
})
.catch((error) => {
console.error(error);
});
};
export const selectHydrated = (state, id) => denormalize(id, label, state);
================================================
FILE: examples/redux/src/redux/modules/milestones.js
================================================
import * as Repo from './repos';
import { milestone } from '../../api/schema';
import { ADD_ENTITIES, addEntities } from '../actions';
import { denormalize, normalize } from '../../../../../src';
export const STATE_KEY = 'milestones';
export default function reducer(state = {}, action) {
switch (action.type) {
case ADD_ENTITIES:
return {
...state,
...action.payload.milestones,
};
default:
return state;
}
}
export const getMilestones = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => {
const state = getState();
const owner = Repo.selectOwner(state);
const repo = Repo.selectRepo(state);
return api.issues
.getMilestones({
owner,
repo,
})
.then((response) => {
const data = normalize(response, [schema.milestone]);
dispatch(addEntities(data.entities));
return response;
})
.catch((error) => {
console.error(error);
});
};
export const selectHydrated = (state, id) => denormalize(id, milestone, state);
================================================
FILE: examples/redux/src/redux/modules/pull-requests.js
================================================
import * as Repo from './repos';
import { pullRequest } from '../../api/schema';
import { ADD_ENTITIES, addEntities } from '../actions';
import { denormalize, normalize } from '../../../../../src';
export const STATE_KEY = 'pullRequests';
export default function reducer(state = {}, action) {
switch (action.type) {
case ADD_ENTITIES:
return {
...state,
...action.payload.pullRequests,
};
default:
return state;
}
}
export const getPullRequests = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => {
const state = getState();
const owner = Repo.selectOwner(state);
const repo = Repo.selectRepo(state);
return api.pullRequests
.getAll({
owner,
repo,
})
.then((response) => {
const data = normalize(response, [schema.pullRequest]);
dispatch(addEntities(data.entities));
return response;
})
.catch((error) => {
console.error(error);
});
};
export const selectHydrated = (state, id) => denormalize(id, pullRequest, state);
================================================
FILE: examples/redux/src/redux/modules/repos.js
================================================
export const STATE_KEY = 'repo';
export default function reducer(state = {}, action) {
switch (action.type) {
case Action.SET_REPO:
return {
...state,
...action.payload,
};
default:
return state;
}
}
const Action = {
SET_REPO: 'SET_REPO',
};
export const setRepo = (slug) => {
const [owner, repo] = slug.split('/');
return {
type: Action.SET_REPO,
payload: { owner, repo },
};
};
export const selectOwner = (state) => state[STATE_KEY].owner;
export const selectRepo = (state) => state[STATE_KEY].repo;
================================================
FILE: examples/redux/src/redux/modules/users.js
================================================
import { ADD_ENTITIES } from '../actions';
import { denormalize } from '../../../../../src';
import { user } from '../../api/schema';
export const STATE_KEY = 'users';
export default function reducer(state = {}, action) {
switch (action.type) {
case ADD_ENTITIES:
return Object.entries(action.payload.users).reduce((mergedUsers, [id, user]) => {
return {
...mergedUsers,
[id]: {
...(mergedUsers[id] || {}),
...user,
},
};
}, state);
default:
return state;
}
}
export const selectHydrated = (state, id) => denormalize(id, user, state);
================================================
FILE: examples/redux/src/redux/reducer.js
================================================
import { combineReducers } from 'redux';
import commits, { STATE_KEY as COMMITS_STATE_KEY } from './modules/commits';
import issues, { STATE_KEY as ISSUES_STATE_KEY } from './modules/issues';
import labels, { STATE_KEY as LABELS_STATE_KEY } from './modules/labels';
import milestones, { STATE_KEY as MILESTONES_STATE_KEY } from './modules/milestones';
import pullRequests, { STATE_KEY as PULLREQUESTS_STATE_KEY } from './modules/pull-requests';
import repos, { STATE_KEY as REPO_STATE_KEY } from './modules/repos';
import users, { STATE_KEY as USERS_STATE_KEY } from './modules/users';
const reducer = combineReducers({
[COMMITS_STATE_KEY]: commits,
[ISSUES_STATE_KEY]: issues,
[LABELS_STATE_KEY]: labels,
[MILESTONES_STATE_KEY]: milestones,
[PULLREQUESTS_STATE_KEY]: pullRequests,
[REPO_STATE_KEY]: repos,
[USERS_STATE_KEY]: users,
});
export default reducer;
================================================
FILE: examples/redux/src/redux/selectors.js
================================================
export { selectHydrated as selectcommit } from './modules/commits';
export { selectHydrated as selectissue } from './modules/issues';
export { selectHydrated as selectlabel } from './modules/labels';
export { selectHydrated as selectmilestone } from './modules/milestones';
export { selectHydrated as selectpullRequest } from './modules/pull-requests';
================================================
FILE: examples/relationships/README.md
================================================
# Dealing with Relationships
Occasionally, 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.
## Running
```sh
# from the root directory:
yarn
# from this directory:
../../node_modules/.bin/babel-node ./index.js
```
## Files
* [index.js](/examples/relationships/index.js): Pulls live data from the GitHub API for this project's issues and normalizes the JSON.
* [input.json](/examples/relationships/input.json): The raw JSON data before normalization.
* [output.json](/examples/relationships/output.json): The normalized output.
* [schema.js](/examples/relationships/schema.js): The schema used to normalize the GitHub issues.
================================================
FILE: examples/relationships/index.js
================================================
import fs from 'fs';
import input from './input.json';
import { normalize } from '../../src';
import path from 'path';
import postsSchema from './schema';
const normalizedData = normalize(input, postsSchema);
const output = JSON.stringify(normalizedData, null, 2);
fs.writeFileSync(path.resolve(__dirname, './output.json'), output);
================================================
FILE: examples/relationships/input.json
================================================
[
{
"id": "1",
"title": "My first post!",
"author": {
"id": "123",
"name": "Paul"
},
"comments": [
{
"id": "249",
"content": "Nice post!",
"commenter": {
"id": "245",
"name": "Jane"
}
},
{
"id": "250",
"content": "Thanks!",
"commenter": {
"id": "123",
"name": "Paul"
}
}
]
},
{
"id": "2",
"title": "This other post",
"author": {
"id": "123",
"name": "Paul"
},
"comments": [
{
"id": "251",
"content": "Your other post was nicer",
"commenter": {
"id": "245",
"name": "Jane"
}
},
{
"id": "252",
"content": "I am a spammer!",
"commenter": {
"id": "246",
"name": "Spambot5000"
}
}
]
}
]
================================================
FILE: examples/relationships/output.json
================================================
{
"entities": {
"users": {
"123": {
"id": "123",
"name": "Paul",
"posts": [
"1",
"2"
],
"comments": [
"250"
]
},
"245": {
"id": "245",
"name": "Jane",
"comments": [
"249",
"251"
],
"posts": []
},
"246": {
"id": "246",
"name": "Spambot5000",
"comments": [
"252"
]
}
},
"comments": {
"249": {
"id": "249",
"content": "Nice post!",
"commenter": "245",
"post": "1"
},
"250": {
"id": "250",
"content": "Thanks!",
"commenter": "123",
"post": "1"
},
"251": {
"id": "251",
"content": "Your other post was nicer",
"commenter": "245",
"post": "2"
},
"252": {
"id": "252",
"content": "I am a spammer!",
"commenter": "246",
"post": "2"
}
},
"posts": {
"1": {
"id": "1",
"title": "My first post!",
"author": "123",
"comments": [
"249",
"250"
]
},
"2": {
"id": "2",
"title": "This other post",
"author": "123",
"comments": [
"251",
"252"
]
}
}
},
"result": [
"1",
"2"
]
}
================================================
FILE: examples/relationships/schema.js
================================================
import { schema } from '../../src';
const userProcessStrategy = (value, parent, key) => {
switch (key) {
case 'author':
return { ...value, posts: [parent.id] };
case 'commenter':
return { ...value, comments: [parent.id] };
default:
return { ...value };
}
};
const userMergeStrategy = (entityA, entityB) => {
return {
...entityA,
...entityB,
posts: [...(entityA.posts || []), ...(entityB.posts || [])],
comments: [...(entityA.comments || []), ...(entityB.comments || [])],
};
};
const user = new schema.Entity(
'users',
{},
{
mergeStrategy: userMergeStrategy,
processStrategy: userProcessStrategy,
}
);
const comment = new schema.Entity(
'comments',
{
commenter: user,
},
{
processStrategy: (value, parent, key) => {
return { ...value, post: parent.id };
},
}
);
const post = new schema.Entity('posts', {
author: user,
comments: [comment],
});
export default [post];
================================================
FILE: husky.config.js
================================================
const runYarnLock = 'yarn install --frozen-lockfile';
module.exports = {
hooks: {
'post-checkout': `if [[ $HUSKY_GIT_PARAMS =~ 1$ ]]; then ${runYarnLock}; fi`,
'post-merge': runYarnLock,
'post-rebase': 'yarn install',
'pre-commit': 'yarn typecheck && yarn lint-staged',
},
};
================================================
FILE: index.d.ts
================================================
declare namespace schema {
export type StrategyFunction<T> = (value: any, parent: any, key: string) => T;
export type SchemaFunction = (value: any, parent: any, key: string) => string;
export type MergeFunction = (entityA: any, entityB: any) => any;
export type FallbackFunction<T> = (key: string, schema: schema.Entity<T>) => T;
export class Array<T = any> {
constructor(definition: Schema<T>, schemaAttribute?: string | SchemaFunction)
define(definition: Schema): void
}
export interface EntityOptions<T = any> {
idAttribute?: string | SchemaFunction
mergeStrategy?: MergeFunction
processStrategy?: StrategyFunction<T>
fallbackStrategy?: FallbackFunction<T>
}
export class Entity<T = any> {
constructor(key: string | symbol, definition?: Schema, options?: EntityOptions<T>)
define(definition: Schema): void
key: string
getId: SchemaFunction
_processStrategy: StrategyFunction<T>
}
export class Object<T = any> {
constructor(definition: SchemaObject<T>)
define(definition: Schema): void
}
export class Union<T = any> {
constructor(definition: Schema<T>, schemaAttribute?: string | SchemaFunction)
define(definition: Schema): void
}
export class Values<T = any> {
constructor(definition: Schema<T>, schemaAttribute?: string | SchemaFunction)
define(definition: Schema): void
}
}
export type Schema<T = any> =
| schema.Entity<T>
| schema.Object<T>
| schema.Union<T>
| schema.Values<T>
| SchemaObject<T>
| SchemaArray<T>;
export type SchemaValueFunction<T> = (t: T) => Schema<T>;
export type SchemaValue<T> = Schema<T> | SchemaValueFunction<T>;
export interface SchemaObject<T> {
[key: string]: SchemaValue<T>
}
export interface SchemaArray<T> extends Array<Schema<T>> {}
export type NormalizedSchema<E, R> = { entities: E, result: R };
export function normalize<T = any, E = { [key:string]: { [key:string]: T } | undefined}, R = any>(
data: any,
schema: Schema<T>
): NormalizedSchema<E, R>;
export function denormalize(
input: any,
schema: Schema,
entities: any
): any;
================================================
FILE: jest.config.js
================================================
module.exports = {
testMatch: ['**/__tests__/**/*.test.js'],
};
================================================
FILE: lint-staged.config.js
================================================
module.exports = {
'*.{md}': ['prettier --write', 'git add'],
'*.{js,jsx,json}': ['yarn lint', 'prettier --write', 'git add'],
'*.{js,jsx,ts,tsx}': ['jest --bail --findRelatedTests'],
};
================================================
FILE: package.json
================================================
{
"name": "normalizr",
"version": "3.6.2",
"description": "Normalizes and denormalizes JSON according to schema for Redux and Flux applications",
"bugs": {
"url": "https://github.com/paularmstrong/normalizr/issues"
},
"homepage": "https://github.com/paularmstrong/normalizr",
"repository": {
"url": "https://github.com/paularmstrong/normalizr.git",
"type": "git"
},
"keywords": [
"flux",
"redux",
"normalize",
"denormalize",
"api",
"json"
],
"files": [
"dist/",
"index.d.ts",
"LICENSE",
"README.md"
],
"main": "dist/normalizr.js",
"module": "dist/normalizr.es.js",
"typings": "index.d.ts",
"sideEffects": false,
"scripts": {
"build": "npm run clean && run-p build:*",
"build:development": "NODE_ENV=development rollup -c",
"build:production": "NODE_ENV=production rollup -c",
"clean": "rimraf dist",
"flow": "flow",
"flow:ci": "flow check",
"lint": "yarn lint:cmd --fix",
"lint:ci": "yarn lint:cmd",
"lint:cmd": "eslint . --ext '.js,.json,.snap' --cache",
"prebuild": "npm run clean",
"precommit": "flow check && lint-staged",
"prepublishOnly": "npm run build",
"test": "jest",
"test:ci": "jest --ci",
"test:coverage": "npm run test -- --coverage && cat ./coverage/lcov.info | coveralls",
"tsc:ci": "tsc --noEmit typescript-tests/*",
"typecheck": "run-p flow:ci tsc:ci"
},
"author": "Paul Armstrong",
"contributors": [
"Dan Abramov"
],
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-flow": "^7.0.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^26.5.2",
"coveralls": "^3.1.0",
"eslint": "^7.11.0",
"eslint-config-prettier": "^6.13.0",
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-json": "^2.1.2",
"eslint-plugin-prettier": "^3.1.4",
"flow-bin": "^0.136.0",
"husky": "^2.3.0",
"immutable": "^3.8.1",
"jest": "^26.5.3",
"lint-staged": "^8.1.7",
"npm-run-all": "^4.1.5",
"prettier": "^2.1.2",
"rimraf": "^3.0.2",
"rollup": "^2.32.0",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-filesize": "^9.0.2",
"rollup-plugin-terser": "^7.0.2",
"typescript": "^3.4.5"
},
"dependencies": {}
}
================================================
FILE: prettier.config.js
================================================
module.exports = {
'arrowParens': 'always',
'printWidth': 120,
'singleQuote': true,
'quoteProps': 'preserve',
};
================================================
FILE: rollup.config.js
================================================
import babel from 'rollup-plugin-babel';
import filesize from 'rollup-plugin-filesize';
import { name } from './package.json';
import { terser } from 'rollup-plugin-terser';
const isProduction = process.env.NODE_ENV === 'production';
const destBase = 'dist/normalizr';
const destExtension = `${isProduction ? '.min' : ''}.js`;
export default {
input: 'src/index.js',
output: [
{ file: `${destBase}${destExtension}`, format: 'cjs' },
{ file: `${destBase}.es${destExtension}`, format: 'es' },
{ file: `${destBase}.umd${destExtension}`, format: 'umd', name },
{ file: `${destBase}.amd${destExtension}`, format: 'amd', name },
{ file: `${destBase}.browser${destExtension}`, format: 'iife', name },
],
plugins: [babel({}), isProduction && terser(), filesize()].filter(Boolean),
};
================================================
FILE: src/__tests__/__snapshots__/index.test.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`denormalize denormalizes entities 1`] = `
Array [
Object {
"id": 1,
"type": "foo",
},
Object {
"id": 2,
"type": "bar",
},
]
`;
exports[`denormalize denormalizes nested entities 1`] = `
Object {
"author": Object {
"id": "8472",
"name": "Paul",
},
"body": "This article is great.",
"comments": Array [
Object {
"comment": "I like it!",
"id": "comment-123-4738",
"user": Object {
"id": "10293",
"name": "Jane",
},
},
],
"id": "123",
"title": "A Great Article",
}
`;
exports[`denormalize denormalizes with function as idAttribute 1`] = `
Array [
Object {
"guest": null,
"id": "1",
"name": "Esther",
},
Object {
"guest": Object {
"guest_id": 1,
},
"id": "2",
"name": "Tom",
},
]
`;
exports[`denormalize set to undefined if schema key is not in entities 1`] = `
Object {
"author": undefined,
"comments": Array [
Object {
"user": undefined,
},
],
"id": "123",
}
`;
exports[`normalize can normalize entity nested inside entity using property from parent 1`] = `
Object {
"entities": Object {
"linkables": Object {
"1": Object {
"data": 2,
"id": 1,
"module_type": "article",
"schema_type": "media",
},
},
"media": Object {
"2": Object {
"id": 2,
"url": "catimage.jpg",
},
},
},
"result": 1,
}
`;
exports[`normalize can normalize entity nested inside object using property from parent 1`] = `
Object {
"entities": Object {
"media": Object {
"2": Object {
"id": 2,
"url": "catimage.jpg",
},
},
},
"result": Object {
"data": 2,
"id": 1,
"module_type": "article",
"schema_type": "media",
},
}
`;
exports[`normalize can use fully custom entity classes 1`] = `
Object {
"entities": Object {
"children": Object {
"4": Object {
"id": 4,
"name": "lettuce",
},
},
"food": Object {
"1234": Object {
"children": Array [
4,
],
"name": "tacos",
"uuid": "1234",
},
},
},
"result": Object {
"schema": "food",
"uuid": "1234",
},
}
`;
exports[`normalize ignores null values 1`] = `
Object {
"entities": Object {},
"result": Array [
null,
],
}
`;
exports[`normalize ignores null values 2`] = `
Object {
"entities": Object {},
"result": Array [
undefined,
],
}
`;
exports[`normalize ignores null values 3`] = `
Object {
"entities": Object {},
"result": Array [
false,
],
}
`;
exports[`normalize normalizes entities 1`] = `
Object {
"entities": Object {
"tacos": Object {
"1": Object {
"id": 1,
"type": "foo",
},
"2": Object {
"id": 2,
"type": "bar",
},
},
},
"result": Array [
1,
2,
],
}
`;
exports[`normalize normalizes entities with circular references 1`] = `
Object {
"entities": Object {
"users": Object {
"123": Object {
"friends": Array [
123,
],
"id": 123,
},
},
},
"result": 123,
}
`;
exports[`normalize normalizes nested entities 1`] = `
Object {
"entities": Object {
"articles": Object {
"123": Object {
"author": "8472",
"body": "This article is great.",
"comments": Array [
"comment-123-4738",
],
"id": "123",
"title": "A Great Article",
},
},
"comments": Object {
"comment-123-4738": Object {
"comment": "I like it!",
"id": "comment-123-4738",
"user": "10293",
},
},
"users": Object {
"10293": Object {
"id": "10293",
"name": "Jane",
},
"8472": Object {
"id": "8472",
"name": "Paul",
},
},
},
"result": "123",
}
`;
exports[`normalize passes over pre-normalized values 1`] = `
Object {
"entities": Object {
"articles": Object {
"123": Object {
"author": 1,
"id": "123",
"title": "normalizr is great!",
},
},
},
"result": "123",
}
`;
exports[`normalize uses the non-normalized input when getting the ID for an entity 1`] = `
Object {
"entities": Object {
"recommendations": Object {
"456": Object {
"user": "456",
},
},
"users": Object {
"456": Object {
"id": "456",
},
},
},
"result": "456",
}
`;
exports[`normalize uses the non-normalized input when getting the ID for an entity 2`] = `
Array [
Array [
Object {
"user": Object {
"id": "456",
},
},
Object {
"user": Object {
"id": "456",
},
},
null,
],
Array [
Object {
"user": Object {
"id": "456",
},
},
Object {
"user": Object {
"id": "456",
},
},
null,
],
]
`;
================================================
FILE: src/__tests__/index.test.js
================================================
// eslint-env jest
import { denormalize, normalize, schema } from '../';
describe('normalize', () => {
[42, null, undefined, '42', () => {}].forEach((input) => {
test(`cannot normalize input that == ${input}`, () => {
expect(() => normalize(input, new schema.Entity('test'))).toThrow();
});
});
test('cannot normalize without a schema', () => {
expect(() => normalize({})).toThrow();
});
test('cannot normalize with null input', () => {
const mySchema = new schema.Entity('tacos');
expect(() => normalize(null, mySchema)).toThrow(/null/);
});
test('normalizes entities', () => {
const mySchema = new schema.Entity('tacos');
expect(
normalize(
[
{ id: 1, type: 'foo' },
{ id: 2, type: 'bar' },
],
[mySchema]
)
).toMatchSnapshot();
});
test('normalizes entities with circular references', () => {
const user = new schema.Entity('users');
user.define({
friends: [user],
});
const input = { id: 123, friends: [] };
input.friends.push(input);
expect(normalize(input, user)).toMatchSnapshot();
});
test('normalizes nested entities', () => {
const user = new schema.Entity('users');
const comment = new schema.Entity('comments', {
user: user,
});
const article = new schema.Entity('articles', {
author: user,
comments: [comment],
});
const input = {
id: '123',
title: 'A Great Article',
author: {
id: '8472',
name: 'Paul',
},
body: 'This article is great.',
comments: [
{
id: 'comment-123-4738',
comment: 'I like it!',
user: {
id: '10293',
name: 'Jane',
},
},
],
};
expect(normalize(input, article)).toMatchSnapshot();
});
test('does not modify the original input', () => {
const user = new schema.Entity('users');
const article = new schema.Entity('articles', { author: user });
const input = Object.freeze({
id: '123',
title: 'A Great Article',
author: Object.freeze({
id: '8472',
name: 'Paul',
}),
});
expect(() => normalize(input, article)).not.toThrow();
});
test('ignores null values', () => {
const myEntity = new schema.Entity('myentities');
expect(normalize([null], [myEntity])).toMatchSnapshot();
expect(normalize([undefined], [myEntity])).toMatchSnapshot();
expect(normalize([false], [myEntity])).toMatchSnapshot();
});
test('can use fully custom entity classes', () => {
class MyEntity extends schema.Entity {
schema = {
children: [new schema.Entity('children')],
};
getId(entity, parent, key) {
return entity.uuid;
}
normalize(input, parent, key, visit, addEntity, visitedEntities) {
const entity = { ...input };
Object.keys(this.schema).forEach((key) => {
const schema = this.schema[key];
entity[key] = visit(input[key], input, key, schema, addEntity, visitedEntities);
});
addEntity(this, entity, parent, key);
return {
uuid: this.getId(entity),
schema: this.key,
};
}
}
const mySchema = new MyEntity('food');
expect(
normalize(
{
uuid: '1234',
name: 'tacos',
children: [{ id: 4, name: 'lettuce' }],
},
mySchema
)
).toMatchSnapshot();
});
test('uses the non-normalized input when getting the ID for an entity', () => {
const userEntity = new schema.Entity('users');
const idAttributeFn = jest.fn((nonNormalized, parent, key) => nonNormalized.user.id);
const recommendation = new schema.Entity(
'recommendations',
{ user: userEntity },
{
idAttribute: idAttributeFn,
}
);
expect(normalize({ user: { id: '456' } }, recommendation)).toMatchSnapshot();
expect(idAttributeFn.mock.calls).toMatchSnapshot();
expect(recommendation.idAttribute).toBe(idAttributeFn);
});
test('passes over pre-normalized values', () => {
const userEntity = new schema.Entity('users');
const articleEntity = new schema.Entity('articles', { author: userEntity });
expect(normalize({ id: '123', title: 'normalizr is great!', author: 1 }, articleEntity)).toMatchSnapshot();
});
test('can normalize object without proper object prototype inheritance', () => {
const test = { id: 1, elements: [] };
test.elements.push(
Object.assign(Object.create(null), {
id: 18,
name: 'test',
})
);
const testEntity = new schema.Entity('test', {
elements: [new schema.Entity('elements')],
});
expect(() => normalize(test, testEntity)).not.toThrow();
});
test('can normalize entity nested inside entity using property from parent', () => {
const linkablesSchema = new schema.Entity('linkables');
const mediaSchema = new schema.Entity('media');
const listsSchema = new schema.Entity('lists');
const schemaMap = {
media: mediaSchema,
lists: listsSchema,
};
linkablesSchema.define({
data: (parent) => schemaMap[parent.schema_type],
});
const input = {
id: 1,
module_type: 'article',
schema_type: 'media',
data: {
id: 2,
url: 'catimage.jpg',
},
};
expect(normalize(input, linkablesSchema)).toMatchSnapshot();
});
test('can normalize entity nested inside object using property from parent', () => {
const mediaSchema = new schema.Entity('media');
const listsSchema = new schema.Entity('lists');
const schemaMap = {
media: mediaSchema,
lists: listsSchema,
};
const linkablesSchema = {
data: (parent) => schemaMap[parent.schema_type],
};
const input = {
id: 1,
module_type: 'article',
schema_type: 'media',
data: {
id: 2,
url: 'catimage.jpg',
},
};
expect(normalize(input, linkablesSchema)).toMatchSnapshot();
});
});
describe('denormalize', () => {
test('cannot denormalize without a schema', () => {
expect(() => denormalize({})).toThrow();
});
test('returns the input if undefined', () => {
expect(denormalize(undefined, {}, {})).toBeUndefined();
});
test('denormalizes entities', () => {
const mySchema = new schema.Entity('tacos');
const entities = {
tacos: {
1: { id: 1, type: 'foo' },
2: { id: 2, type: 'bar' },
},
};
expect(denormalize([1, 2], [mySchema], entities)).toMatchSnapshot();
});
test('denormalizes nested entities', () => {
const user = new schema.Entity('users');
const comment = new schema.Entity('comments', {
user: user,
});
const article = new schema.Entity('articles', {
author: user,
comments: [comment],
});
const entities = {
articles: {
123: {
author: '8472',
body: 'This article is great.',
comments: ['comment-123-4738'],
id: '123',
title: 'A Great Article',
},
},
comments: {
'comment-123-4738': {
comment: 'I like it!',
id: 'comment-123-4738',
user: '10293',
},
},
users: {
10293: {
id: '10293',
name: 'Jane',
},
8472: {
id: '8472',
name: 'Paul',
},
},
};
expect(denormalize('123', article, entities)).toMatchSnapshot();
});
test('set to undefined if schema key is not in entities', () => {
const user = new schema.Entity('users');
const comment = new schema.Entity('comments', {
user: user,
});
const article = new schema.Entity('articles', {
author: user,
comments: [comment],
});
const entities = {
articles: {
123: {
id: '123',
author: '8472',
comments: ['1'],
},
},
comments: {
1: {
user: '123',
},
},
};
expect(denormalize('123', article, entities)).toMatchSnapshot();
});
test('does not modify the original entities', () => {
const user = new schema.Entity('users');
const article = new schema.Entity('articles', { author: user });
const entities = Object.freeze({
articles: Object.freeze({
123: Object.freeze({
id: '123',
title: 'A Great Article',
author: '8472',
}),
}),
users: Object.freeze({
8472: Object.freeze({
id: '8472',
name: 'Paul',
}),
}),
});
expect(() => denormalize('123', article, entities)).not.toThrow();
});
test('denormalizes with function as idAttribute', () => {
const normalizedData = {
entities: {
patrons: {
1: { id: '1', guest: null, name: 'Esther' },
2: { id: '2', guest: 'guest-2-1', name: 'Tom' },
},
guests: { 'guest-2-1': { guest_id: 1 } },
},
result: ['1', '2'],
};
const guestSchema = new schema.Entity(
'guests',
{},
{
idAttribute: (value, parent, key) => `${key}-${parent.id}-${value.guest_id}`,
}
);
const patronsSchema = new schema.Entity('patrons', {
guest: guestSchema,
});
expect(denormalize(normalizedData.result, [patronsSchema], normalizedData.entities)).toMatchSnapshot();
});
});
================================================
FILE: src/index.js
================================================
import * as ImmutableUtils from './schemas/ImmutableUtils';
import EntitySchema from './schemas/Entity';
import UnionSchema from './schemas/Union';
import ValuesSchema from './schemas/Values';
import ArraySchema, * as ArrayUtils from './schemas/Array';
import ObjectSchema, * as ObjectUtils from './schemas/Object';
const visit = (value, parent, key, schema, addEntity, visitedEntities) => {
if (typeof value !== 'object' || !value) {
return value;
}
if (typeof schema === 'object' && (!schema.normalize || typeof schema.normalize !== 'function')) {
const method = Array.isArray(schema) ? ArrayUtils.normalize : ObjectUtils.normalize;
return method(schema, value, parent, key, visit, addEntity, visitedEntities);
}
return schema.normalize(value, parent, key, visit, addEntity, visitedEntities);
};
const addEntities = (entities) => (schema, processedEntity, value, parent, key) => {
const schemaKey = schema.key;
const id = schema.getId(value, parent, key);
if (!(schemaKey in entities)) {
entities[schemaKey] = {};
}
const existingEntity = entities[schemaKey][id];
if (existingEntity) {
entities[schemaKey][id] = schema.merge(existingEntity, processedEntity);
} else {
entities[schemaKey][id] = processedEntity;
}
};
export const schema = {
Array: ArraySchema,
Entity: EntitySchema,
Object: ObjectSchema,
Union: UnionSchema,
Values: ValuesSchema,
};
export const normalize = (input, schema) => {
if (!input || typeof input !== 'object') {
throw new Error(
`Unexpected input given to normalize. Expected type to be "object", found "${
input === null ? 'null' : typeof input
}".`
);
}
const entities = {};
const addEntity = addEntities(entities);
const visitedEntities = {};
const result = visit(input, input, null, schema, addEntity, visitedEntities);
return { entities, result };
};
const unvisitEntity = (id, schema, unvisit, getEntity, cache) => {
let entity = getEntity(id, schema);
if (entity === undefined && schema instanceof EntitySchema) {
entity = schema.fallback(id, schema);
}
if (typeof entity !== 'object' || entity === null) {
return entity;
}
if (!cache[schema.key]) {
cache[schema.key] = {};
}
if (!cache[schema.key][id]) {
// Ensure we don't mutate it non-immutable objects
const entityCopy = ImmutableUtils.isImmutable(entity) ? entity : { ...entity };
// Need to set this first so that if it is referenced further within the
// denormalization the reference will already exist.
cache[schema.key][id] = entityCopy;
cache[schema.key][id] = schema.denormalize(entityCopy, unvisit);
}
return cache[schema.key][id];
};
const getUnvisit = (entities) => {
const cache = {};
const getEntity = getEntities(entities);
return function unvisit(input, schema) {
if (typeof schema === 'object' && (!schema.denormalize || typeof schema.denormalize !== 'function')) {
const method = Array.isArray(schema) ? ArrayUtils.denormalize : ObjectUtils.denormalize;
return method(schema, input, unvisit);
}
if (input === undefined || input === null) {
return input;
}
if (schema instanceof EntitySchema) {
return unvisitEntity(input, schema, unvisit, getEntity, cache);
}
return schema.denormalize(input, unvisit);
};
};
const getEntities = (entities) => {
const isImmutable = ImmutableUtils.isImmutable(entities);
return (entityOrId, schema) => {
const schemaKey = schema.key;
if (typeof entityOrId === 'object') {
return entityOrId;
}
if (isImmutable) {
return entities.getIn([schemaKey, entityOrId.toString()]);
}
return entities[schemaKey] && entities[schemaKey][entityOrId];
};
};
export const denormalize = (input, schema, entities) => {
if (typeof input !== 'undefined') {
return getUnvisit(entities)(input, schema);
}
};
================================================
FILE: src/schemas/Array.js
================================================
import PolymorphicSchema from './Polymorphic';
const validateSchema = (definition) => {
const isArray = Array.isArray(definition);
if (isArray && definition.length > 1) {
throw new Error(`Expected schema definition to be a single schema, but found ${definition.length}.`);
}
return definition[0];
};
const getValues = (input) => (Array.isArray(input) ? input : Object.keys(input).map((key) => input[key]));
export const normalize = (schema, input, parent, key, visit, addEntity, visitedEntities) => {
schema = validateSchema(schema);
const values = getValues(input);
// Special case: Arrays pass *their* parent on to their children, since there
// is not any special information that can be gathered from themselves directly
return values.map((value, index) => visit(value, parent, key, schema, addEntity, visitedEntities));
};
export const denormalize = (schema, input, unvisit) => {
schema = validateSchema(schema);
return input && input.map ? input.map((entityOrId) => unvisit(entityOrId, schema)) : input;
};
export default class ArraySchema extends PolymorphicSchema {
normalize(input, parent, key, visit, addEntity, visitedEntities) {
const values = getValues(input);
return values
.map((value, index) => this.normalizeValue(value, parent, key, visit, addEntity, visitedEntities))
.filter((value) => value !== undefined && value !== null);
}
denormalize(input, unvisit) {
return input && input.map ? input.map((value) => this.denormalizeValue(value, unvisit)) : input;
}
}
================================================
FILE: src/schemas/Entity.js
================================================
import * as ImmutableUtils from './ImmutableUtils';
const getDefaultGetId = (idAttribute) => (input) =>
ImmutableUtils.isImmutable(input) ? input.get(idAttribute) : input[idAttribute];
export default class EntitySchema {
constructor(key, definition = {}, options = {}) {
if (!key || typeof key !== 'string') {
throw new Error(`Expected a string key for Entity, but found ${key}.`);
}
const {
idAttribute = 'id',
mergeStrategy = (entityA, entityB) => {
return { ...entityA, ...entityB };
},
processStrategy = (input) => ({ ...input }),
fallbackStrategy = (key, schema) => undefined,
} = options;
this._key = key;
this._getId = typeof idAttribute === 'function' ? idAttribute : getDefaultGetId(idAttribute);
this._idAttribute = idAttribute;
this._mergeStrategy = mergeStrategy;
this._processStrategy = processStrategy;
this._fallbackStrategy = fallbackStrategy;
this.define(definition);
}
get key() {
return this._key;
}
get idAttribute() {
return this._idAttribute;
}
define(definition) {
this.schema = Object.keys(definition).reduce((entitySchema, key) => {
const schema = definition[key];
return { ...entitySchema, [key]: schema };
}, this.schema || {});
}
getId(input, parent, key) {
return this._getId(input, parent, key);
}
merge(entityA, entityB) {
return this._mergeStrategy(entityA, entityB);
}
fallback(id, schema) {
return this._fallbackStrategy(id, schema);
}
normalize(input, parent, key, visit, addEntity, visitedEntities) {
const id = this.getId(input, parent, key);
const entityType = this.key;
if (!(entityType in visitedEntities)) {
visitedEntities[entityType] = {};
}
if (!(id in visitedEntities[entityType])) {
visitedEntities[entityType][id] = [];
}
if (visitedEntities[entityType][id].some((entity) => entity === input)) {
return id;
}
visitedEntities[entityType][id].push(input);
const processedEntity = this._processStrategy(input, parent, key);
Object.keys(this.schema).forEach((key) => {
if (processedEntity.hasOwnProperty(key) && typeof processedEntity[key] === 'object') {
const schema = this.schema[key];
const resolvedSchema = typeof schema === 'function' ? schema(input) : schema;
processedEntity[key] = visit(
processedEntity[key],
processedEntity,
key,
resolvedSchema,
addEntity,
visitedEntities
);
}
});
addEntity(this, processedEntity, input, parent, key);
return id;
}
denormalize(entity, unvisit) {
if (ImmutableUtils.isImmutable(entity)) {
return ImmutableUtils.denormalizeImmutable(this.schema, entity, unvisit);
}
Object.keys(this.schema).forEach((key) => {
if (entity.hasOwnProperty(key)) {
const schema = this.schema[key];
entity[key] = unvisit(entity[key], schema);
}
});
return entity;
}
}
================================================
FILE: src/schemas/ImmutableUtils.js
================================================
/**
* Helpers to enable Immutable compatibility *without* bringing in
* the 'immutable' package as a dependency.
*/
/**
* Check if an object is immutable by checking if it has a key specific
* to the immutable library.
*
* @param {any} object
* @return {bool}
*/
export function isImmutable(object) {
return !!(
object &&
typeof object.hasOwnProperty === 'function' &&
(object.hasOwnProperty('__ownerID') || // Immutable.Map
(object._map && object._map.hasOwnProperty('__ownerID')))
); // Immutable.Record
}
/**
* Denormalize an immutable entity.
*
* @param {Schema} schema
* @param {Immutable.Map|Immutable.Record} input
* @param {function} unvisit
* @param {function} getDenormalizedEntity
* @return {Immutable.Map|Immutable.Record}
*/
export function denormalizeImmutable(schema, input, unvisit) {
return Object.keys(schema).reduce((object, key) => {
// Immutable maps cast keys to strings on write so we need to ensure
// we're accessing them using string keys.
const stringKey = `${key}`;
if (object.has(stringKey)) {
return object.set(stringKey, unvisit(object.get(stringKey), schema[stringKey]));
} else {
return object;
}
}, input);
}
================================================
FILE: src/schemas/Object.js
================================================
import * as ImmutableUtils from './ImmutableUtils';
export const normalize = (schema, input, parent, key, visit, addEntity, visitedEntities) => {
const object = { ...input };
Object.keys(schema).forEach((key) => {
const localSchema = schema[key];
const resolvedLocalSchema = typeof localSchema === 'function' ? localSchema(input) : localSchema;
const value = visit(input[key], input, key, resolvedLocalSchema, addEntity, visitedEntities);
if (value === undefined || value === null) {
delete object[key];
} else {
object[key] = value;
}
});
return object;
};
export const denormalize = (schema, input, unvisit) => {
if (ImmutableUtils.isImmutable(input)) {
return ImmutableUtils.denormalizeImmutable(schema, input, unvisit);
}
const object = { ...input };
Object.keys(schema).forEach((key) => {
if (object[key] != null) {
object[key] = unvisit(object[key], schema[key]);
}
});
return object;
};
export default class ObjectSchema {
constructor(definition) {
this.define(definition);
}
define(definition) {
this.schema = Object.keys(definition).reduce((entitySchema, key) => {
const schema = definition[key];
return { ...entitySchema, [key]: schema };
}, this.schema || {});
}
normalize(...args) {
return normalize(this.schema, ...args);
}
denormalize(...args) {
return denormalize(this.schema, ...args);
}
}
================================================
FILE: src/schemas/Polymorphic.js
================================================
import { isImmutable } from './ImmutableUtils';
export default class PolymorphicSchema {
constructor(definition, schemaAttribute) {
if (schemaAttribute) {
this._schemaAttribute = typeof schemaAttribute === 'string' ? (input) => input[schemaAttribute] : schemaAttribute;
}
this.define(definition);
}
get isSingleSchema() {
return !this._schemaAttribute;
}
define(definition) {
this.schema = definition;
}
getSchemaAttribute(input, parent, key) {
return !this.isSingleSchema && this._schemaAttribute(input, parent, key);
}
inferSchema(input, parent, key) {
if (this.isSingleSchema) {
return this.schema;
}
const attr = this.getSchemaAttribute(input, parent, key);
return this.schema[attr];
}
normalizeValue(value, parent, key, visit, addEntity, visitedEntities) {
const schema = this.inferSchema(value, parent, key);
if (!schema) {
return value;
}
const normalizedValue = visit(value, parent, key, schema, addEntity, visitedEntities);
return this.isSingleSchema || normalizedValue === undefined || normalizedValue === null
? normalizedValue
: { id: normalizedValue, schema: this.getSchemaAttribute(value, parent, key) };
}
denormalizeValue(value, unvisit) {
const schemaKey = isImmutable(value) ? value.get('schema') : value.schema;
if (!this.isSingleSchema && !schemaKey) {
return value;
}
const id = this.isSingleSchema ? undefined : isImmutable(value) ? value.get('id') : value.id;
const schema = this.isSingleSchema ? this.schema : this.schema[schemaKey];
return unvisit(id || value, schema);
}
}
================================================
FILE: src/schemas/Union.js
================================================
import PolymorphicSchema from './Polymorphic';
export default class UnionSchema extends PolymorphicSchema {
constructor(definition, schemaAttribute) {
if (!schemaAttribute) {
throw new Error('Expected option "schemaAttribute" not found on UnionSchema.');
}
super(definition, schemaAttribute);
}
normalize(input, parent, key, visit, addEntity, visitedEntities) {
return this.normalizeValue(input, parent, key, visit, addEntity, visitedEntities);
}
denormalize(input, unvisit) {
return this.denormalizeValue(input, unvisit);
}
}
================================================
FILE: src/schemas/Values.js
================================================
import PolymorphicSchema from './Polymorphic';
export default class ValuesSchema extends PolymorphicSchema {
normalize(input, parent, key, visit, addEntity, visitedEntities) {
return Object.keys(input).reduce((output, key, index) => {
const value = input[key];
return value !== undefined && value !== null
? {
...output,
[key]: this.normalizeValue(value, input, key, visit, addEntity, visitedEntities),
}
: output;
}, {});
}
denormalize(input, unvisit) {
return Object.keys(input).reduce((output, key) => {
const entityOrId = input[key];
return {
...output,
[key]: this.denormalizeValue(entityOrId, unvisit),
};
}, {});
}
}
================================================
FILE: src/schemas/__tests__/Array.test.js
================================================
// eslint-env jest
import { fromJS } from 'immutable';
import { denormalize, normalize, schema } from '../../';
describe(`${schema.Array.name} normalization`, () => {
describe('Object', () => {
test(`normalizes plain arrays as shorthand for ${schema.Array.name}`, () => {
const userSchema = new schema.Entity('user');
expect(normalize([{ id: 1 }, { id: 2 }], [userSchema])).toMatchSnapshot();
});
test('throws an error if created with more than one schema', () => {
const userSchema = new schema.Entity('users');
const catSchema = new schema.Entity('cats');
expect(() => normalize([{ id: 1 }], [catSchema, userSchema])).toThrow();
});
test('passes its parent to its children when normalizing', () => {
const processStrategy = (entity, parent, key) => {
return { ...entity, parentId: parent.id, parentKey: key };
};
const childEntity = new schema.Entity('children', {}, { processStrategy });
const parentEntity = new schema.Entity('parents', {
children: [childEntity],
});
expect(
normalize(
{
id: 1,
content: 'parent',
children: [{ id: 4, content: 'child' }],
},
parentEntity
)
).toMatchSnapshot();
});
test('normalizes Objects using their values', () => {
const userSchema = new schema.Entity('user');
expect(normalize({ foo: { id: 1 }, bar: { id: 2 } }, [userSchema])).toMatchSnapshot();
});
});
describe('Class', () => {
test('normalizes a single entity', () => {
const cats = new schema.Entity('cats');
const listSchema = new schema.Array(cats);
expect(normalize([{ id: 1 }, { id: 2 }], listSchema)).toMatchSnapshot();
});
test('normalizes multiple entities', () => {
const inferSchemaFn = jest.fn((input, parent, key) => input.type || 'dogs');
const catSchema = new schema.Entity('cats');
const peopleSchema = new schema.Entity('person');
const listSchema = new schema.Array(
{
cats: catSchema,
people: peopleSchema,
},
inferSchemaFn
);
expect(
normalize(
[
{ type: 'cats', id: '123' },
{ type: 'people', id: '123' },
{ id: '789', name: 'fido' },
{ type: 'cats', id: '456' },
],
listSchema
)
).toMatchSnapshot();
expect(inferSchemaFn.mock.calls).toMatchSnapshot();
});
test('normalizes Objects using their values', () => {
const userSchema = new schema.Entity('user');
const users = new schema.Array(userSchema);
expect(normalize({ foo: { id: 1 }, bar: { id: 2 } }, users)).toMatchSnapshot();
});
test('filters out undefined and null normalized values', () => {
const userSchema = new schema.Entity('user');
const users = new schema.Array(userSchema);
expect(normalize([undefined, { id: 123 }, null], users)).toMatchSnapshot();
});
});
});
describe(`${schema.Array.name} denormalization`, () => {
describe('Object', () => {
test('denormalizes a single entity', () => {
const cats = new schema.Entity('cats');
const entities = {
cats: {
1: { id: 1, name: 'Milo' },
2: { id: 2, name: 'Jake' },
},
};
expect(denormalize([1, 2], [cats], entities)).toMatchSnapshot();
expect(denormalize([1, 2], [cats], fromJS(entities))).toMatchSnapshot();
});
test('returns the input value if is not an array', () => {
const filling = new schema.Entity('fillings');
const taco = new schema.Entity('tacos', { fillings: [filling] });
const entities = {
tacos: {
123: {
id: '123',
fillings: null,
},
},
};
expect(denormalize('123', taco, entities)).toMatchSnapshot();
expect(denormalize('123', taco, fromJS(entities))).toMatchSnapshot();
});
});
describe('Class', () => {
test('denormalizes a single entity', () => {
const cats = new schema.Entity('cats');
const entities = {
cats: {
1: { id: 1, name: 'Milo' },
2: { id: 2, name: 'Jake' },
},
};
const catList = new schema.Array(cats);
expect(denormalize([1, 2], catList, entities)).toMatchSnapshot();
expect(denormalize([1, 2], catList, fromJS(entities))).toMatchSnapshot();
});
test('denormalizes multiple entities', () => {
const catSchema = new schema.Entity('cats');
const peopleSchema = new schema.Entity('person');
const listSchema = new schema.Array(
{
cats: catSchema,
dogs: {},
people: peopleSchema,
},
(input, parent, key) => input.type || 'dogs'
);
const entities = {
cats: {
123: {
id: '123',
type: 'cats',
},
456: {
id: '456',
type: 'cats',
},
},
person: {
123: {
id: '123',
type: 'people',
},
},
};
const input = [
{ id: '123', schema: 'cats' },
{ id: '123', schema: 'people' },
{ id: { id: '789' }, schema: 'dogs' },
{ id: '456', schema: 'cats' },
];
expect(denormalize(input, listSchema, entities)).toMatchSnapshot();
expect(denormalize(input, listSchema, fromJS(entities))).toMatchSnapshot();
});
test('returns the input value if is not an array', () => {
const filling = new schema.Entity('fillings');
const fillings = new schema.Array(filling);
const taco = new schema.Entity('tacos', { fillings });
const entities = {
tacos: {
123: {
id: '123',
fillings: {},
},
},
};
expect(denormalize('123', taco, entities)).toMatchSnapshot();
expect(denormalize('123', taco, fromJS(entities))).toMatchSnapshot();
});
test('does not assume mapping of schema to attribute values when schemaAttribute is not set', () => {
const cats = new schema.Entity('cats');
const catRecord = new schema.Object({
cat: cats,
});
const catList = new schema.Array(catRecord);
const input = [
{ cat: { id: 1 }, id: 5 },
{ cat: { id: 2 }, id: 6 },
];
const output = normalize(input, catList);
expect(output).toMatchSnapshot();
expect(denormalize(output.result, catList, output.entities)).toEqual(input);
});
});
});
================================================
FILE: src/schemas/__tests__/Entity.test.js
================================================
// eslint-env jest
import { denormalize, normalize, schema } from '../../';
import { fromJS, Record } from 'immutable';
const values = (obj) => Object.keys(obj).map((key) => obj[key]);
describe(`${schema.Entity.name} normalization`, () => {
test('normalizes an entity', () => {
const entity = new schema.Entity('item');
expect(normalize({ id: 1 }, entity)).toMatchSnapshot();
});
describe('key', () => {
test('must be created with a key name', () => {
expect(() => new schema.Entity()).toThrow();
});
test('key name must be a string', () => {
expect(() => new schema.Entity(42)).toThrow();
});
test('key getter should return key passed to constructor', () => {
const user = new schema.Entity('users');
expect(user.key).toEqual('users');
});
});
describe('idAttribute', () => {
test('can use a custom idAttribute string', () => {
const user = new schema.Entity('users', {}, { idAttribute: 'id_str' });
expect(normalize({ id_str: '134351', name: 'Kathy' }, user)).toMatchSnapshot();
});
test('can normalize entity IDs based on their object key', () => {
const user = new schema.Entity('users', {}, { idAttribute: (entity, parent, key) => key });
const inputSchema = new schema.Values({ users: user }, () => 'users');
expect(normalize({ 4: { name: 'taco' }, 56: { name: 'burrito' } }, inputSchema)).toMatchSnapshot();
});
test("can build the entity's ID from the parent object", () => {
const user = new schema.Entity(
'users',
{},
{
idAttribute: (entity, parent, key) => `${parent.name}-${key}-${entity.id}`,
}
);
const inputSchema = new schema.Object({ user });
expect(normalize({ name: 'tacos', user: { id: '4', name: 'Jimmy' } }, inputSchema)).toMatchSnapshot();
});
});
describe('mergeStrategy', () => {
test('defaults to plain merging', () => {
const mySchema = new schema.Entity('tacos');
expect(
normalize(
[
{ id: 1, name: 'foo' },
{ id: 1, name: 'bar', alias: 'bar' },
],
[mySchema]
)
).toMatchSnapshot();
});
test('can use a custom merging strategy', () => {
const mergeStrategy = (entityA, entityB) => {
return { ...entityA, ...entityB, name: entityA.name };
};
const mySchema = new schema.Entity('tacos', {}, { mergeStrategy });
expect(
normalize(
[
{ id: 1, name: 'foo' },
{ id: 1, name: 'bar', alias: 'bar' },
],
[mySchema]
)
).toMatchSnapshot();
});
});
describe('processStrategy', () => {
test('can use a custom processing strategy', () => {
const processStrategy = (entity) => {
return { ...entity, slug: `thing-${entity.id}` };
};
const mySchema = new schema.Entity('tacos', {}, { processStrategy });
expect(normalize({ id: 1, name: 'foo' }, mySchema)).toMatchSnapshot();
});
test('can use information from the parent in the process strategy', () => {
const processStrategy = (entity, parent, key) => {
return { ...entity, parentId: parent.id, parentKey: key };
};
const childEntity = new schema.Entity('children', {}, { processStrategy });
const parentEntity = new schema.Entity('parents', {
child: childEntity,
});
expect(
normalize(
{
id: 1,
content: 'parent',
child: { id: 4, content: 'child' },
},
parentEntity
)
).toMatchSnapshot();
});
test('is run before and passed to the schema normalization', () => {
const processStrategy = (input) => ({ ...values(input)[0], type: Object.keys(input)[0] });
const attachmentEntity = new schema.Entity('attachments');
// If not run before, this schema would require a parent object with key "message"
const myEntity = new schema.Entity(
'entries',
{
data: { attachment: attachmentEntity },
},
{ idAttribute: (input) => values(input)[0].id, processStrategy }
);
expect(normalize({ message: { id: '123', data: { attachment: { id: '456' } } } }, myEntity)).toMatchSnapshot();
});
});
});
describe(`${schema.Entity.name} denormalization`, () => {
test('denormalizes an entity', () => {
const mySchema = new schema.Entity('tacos');
const entities = {
tacos: {
1: { id: 1, type: 'foo' },
},
};
expect(denormalize(1, mySchema, entities)).toMatchSnapshot();
expect(denormalize(1, mySchema, fromJS(entities))).toMatchSnapshot();
});
test('denormalizes deep entities', () => {
const foodSchema = new schema.Entity('foods');
const menuSchema = new schema.Entity('menus', {
food: foodSchema,
});
const entities = {
menus: {
1: { id: 1, food: 1 },
2: { id: 2 },
},
foods: {
1: { id: 1 },
},
};
expect(denormalize(1, menuSchema, entities)).toMatchSnapshot();
expect(denormalize(1, menuSchema, fromJS(entities))).toMatchSnapshot();
expect(denormalize(2, menuSchema, entities)).toMatchSnapshot();
expect(denormalize(2, menuSchema, fromJS(entities))).toMatchSnapshot();
});
test('denormalizes to undefined for missing data', () => {
const foodSchema = new schema.Entity('foods');
const menuSchema = new schema.Entity('menus', {
food: foodSchema,
});
const entities = {
menus: {
1: { id: 1, food: 2 },
},
foods: {
1: { id: 1 },
},
};
expect(denormalize(1, menuSchema, entities)).toMatchSnapshot();
expect(denormalize(1, menuSchema, fromJS(entities))).toMatchSnapshot();
expect(denormalize(2, menuSchema, entities)).toMatchSnapshot();
expect(denormalize(2, menuSchema, fromJS(entities))).toMatchSnapshot();
});
test('denormalizes deep entities with records', () => {
const foodSchema = new schema.Entity('foods');
const menuSchema = new schema.Entity('menus', {
food: foodSchema,
});
const Food = new Record({ id: null });
const Menu = new Record({ id: null, food: null });
const entities = {
menus: {
1: new Menu({ id: 1, food: 1 }),
2: new Menu({ id: 2 }),
},
foods: {
1: new Food({ id: 1 }),
},
};
expect(denormalize(1, menuSchema, entities)).toMatchSnapshot();
expect(denormalize(1, menuSchema, fromJS(entities))).toMatchSnapshot();
expect(denormalize(2, menuSchema, entities)).toMatchSnapshot();
expect(denormalize(2, menuSchema, fromJS(entities))).toMatchSnapshot();
});
test('can denormalize already partially denormalized data', () => {
const foodSchema = new schema.Entity('foods');
const menuSchema = new schema.Entity('menus', {
food: foodSchema,
});
const entities = {
menus: {
1: { id: 1, food: { id: 1 } },
},
foods: {
1: { id: 1 },
},
};
expect(denormalize(1, menuSchema, entities)).toMatchSnapshot();
expect(denormalize(1, menuSchema, fromJS(entities))).toMatchSnapshot();
});
test('denormalizes recursive dependencies', () => {
const user = new schema.Entity('users');
const report = new schema.Entity('reports');
user.define({
reports: [report],
});
report.define({
draftedBy: user,
publishedBy: user,
});
const entities = {
reports: {
123: {
id: '123',
title: 'Weekly report',
draftedBy: '456',
publishedBy: '456',
},
},
users: {
456: {
id: '456',
role: 'manager',
reports: ['123'],
},
},
};
expect(denormalize('123', report, entities)).toMatchSnapshot();
expect(denormalize('123', report, fromJS(entities))).toMatchSnapshot();
expect(denormalize('456', user, entities)).toMatchSnapshot();
expect(denormalize('456', user, fromJS(entities))).toMatchSnapshot();
});
test('denormalizes entities with referential equality', () => {
const user = new schema.Entity('users');
const report = new schema.Entity('reports');
user.define({
reports: [report],
});
report.define({
draftedBy: user,
publishedBy: user,
});
const entities = {
reports: {
123: {
id: '123',
title: 'Weekly report',
draftedBy: '456',
publishedBy: '456',
},
},
users: {
456: {
id: '456',
role: 'manager',
reports: ['123'],
},
},
};
const denormalizedReport = denormalize('123', report, entities);
expect(denormalizedReport).toBe(denormalizedReport.draftedBy.reports[0]);
expect(denormalizedReport.publishedBy).toBe(denormalizedReport.draftedBy);
// NOTE: Given how immutable data works, referential equality can't be
// maintained with nested denormalization.
});
test('denormalizes with fallback strategy', () => {
const user = new schema.Entity(
'users',
{},
{
idAttribute: 'userId',
fallbackStrategy: (id, schema) => ({
[schema.idAttribute]: id,
name: 'John Doe',
}),
}
);
const report = new schema.Entity('reports', {
draftedBy: user,
publishedBy: user,
});
const entities = {
reports: {
123: {
id: '123',
title: 'Weekly report',
draftedBy: '456',
publishedBy: '456',
},
},
users: {},
};
const denormalizedReport = denormalize('123', report, entities);
expect(denormalizedReport.publishedBy).toBe(denormalizedReport.draftedBy);
expect(denormalizedReport.publishedBy.name).toBe('John Doe');
expect(denormalizedReport.publishedBy.userId).toBe('456');
//
});
});
================================================
FILE: src/schemas/__tests__/Object.test.js
================================================
// eslint-env jest
import { fromJS } from 'immutable';
import { denormalize, normalize, schema } from '../../';
describe(`${schema.Object.name} normalization`, () => {
test('normalizes an object', () => {
const userSchema = new schema.Entity('user');
const object = new schema.Object({
user: userSchema,
});
expect(normalize({ user: { id: 1 } }, object)).toMatchSnapshot();
});
test(`normalizes plain objects as shorthand for ${schema.Object.name}`, () => {
const userSchema = new schema.Entity('user');
expect(normalize({ user: { id: 1 } }, { user: userSchema })).toMatchSnapshot();
});
test('filters out undefined and null values', () => {
const userSchema = new schema.Entity('user');
const users = { foo: userSchema, bar: userSchema, baz: userSchema };
expect(normalize({ foo: {}, bar: { id: '1' } }, users)).toMatchSnapshot();
});
});
describe(`${schema.Object.name} denormalization`, () => {
test('denormalizes an object', () => {
const userSchema = new schema.Entity('user');
const object = new schema.Object({
user: userSchema,
});
const entities = {
user: {
1: { id: 1, name: 'Nacho' },
},
};
expect(denormalize({ user: 1 }, object, entities)).toMatchSnapshot();
expect(denormalize({ user: 1 }, object, fromJS(entities))).toMatchSnapshot();
expect(denormalize(fromJS({ user: 1 }), object, fromJS(entities))).toMatchSnapshot();
});
test('denormalizes plain object shorthand', () => {
const userSchema = new schema.Entity('user');
const entities = {
user: {
1: { id: 1, name: 'Jane' },
},
};
expect(denormalize({ user: 1 }, { user: userSchema, tacos: {} }, entities)).toMatchSnapshot();
expect(denormalize({ user: 1 }, { user: userSchema, tacos: {} }, fromJS(entities))).toMatchSnapshot();
expect(denormalize(fromJS({ user: 1 }), { user: userSchema, tacos: {} }, fromJS(entities))).toMatchSnapshot();
});
test('denormalizes an object that contains a property representing a an object with an id of zero', () => {
const userSchema = new schema.Entity('user');
const object = new schema.Object({
user: userSchema,
});
const entities = {
user: {
0: { id: 0, name: 'Chancho' },
},
};
expect(denormalize({ user: 0 }, object, entities)).toMatchSnapshot();
expect(denormalize({ user: 0 }, object, fromJS(entities))).toMatchSnapshot();
expect(denormalize(fromJS({ user: 0 }), object, fromJS(entities))).toMatchSnapshot();
});
});
================================================
FILE: src/schemas/__tests__/Union.test.js
================================================
// eslint-env jest
import { fromJS } from 'immutable';
import { denormalize, normalize, schema } from '../../';
describe(`${schema.Union.name} normalization`, () => {
test('throws if not given a schemaAttribute', () => {
expect(() => new schema.Union({})).toThrow();
});
test('normalizes an object using string schemaAttribute', () => {
const user = new schema.Entity('users');
const group = new schema.Entity('groups');
const union = new schema.Union(
{
users: user,
groups: group,
},
'type'
);
expect(normalize({ id: 1, type: 'users' }, union)).toMatchSnapshot();
expect(normalize({ id: 2, type: 'groups' }, union)).toMatchSnapshot();
});
test('normalizes an array of multiple entities using a function to infer the schemaAttribute', () => {
const user = new schema.Entity('users');
const group = new schema.Entity('groups');
const union = new schema.Union(
{
users: user,
groups: group,
},
(input) => {
return input.username ? 'users' : input.groupname ? 'groups' : null;
}
);
expect(normalize({ id: 1, username: 'Janey' }, union)).toMatchSnapshot();
expect(normalize({ id: 2, groupname: 'People' }, union)).toMatchSnapshot();
expect(normalize({ id: 3, notdefined: 'yep' }, union)).toMatchSnapshot();
});
});
describe(`${schema.Union.name} denormalization`, () => {
const user = new schema.Entity('users');
const group = new schema.Entity('groups');
const entities = {
users: {
1: { id: 1, username: 'Janey', type: 'users' },
},
groups: {
2: { id: 2, groupname: 'People', type: 'groups' },
},
};
test('denormalizes an object using string schemaAttribute', () => {
const union = new schema.Union(
{
users: user,
groups: group,
},
'type'
);
expect(denormalize({ id: 1, schema: 'users' }, union, entities)).toMatchSnapshot();
expect(denormalize(fromJS({ id: 1, schema: 'users' }), union, fromJS(entities))).toMatchSnapshot();
expect(denormalize({ id: 2, schema: 'groups' }, union, entities)).toMatchSnapshot();
expect(denormalize(fromJS({ id: 2, schema: 'groups' }), union, fromJS(entities))).toMatchSnapshot();
});
test('denormalizes an array of multiple entities using a function to infer the schemaAttribute', () => {
const union = new schema.Union(
{
users: user,
groups: group,
},
(input) => {
return input.username ? 'users' : 'groups';
}
);
expect(denormalize({ id: 1, schema: 'users' }, union, entities)).toMatchSnapshot();
expect(denormalize(fromJS({ id: 1, schema: 'users' }), union, fromJS(entities))).toMatchSnapshot();
expect(denormalize({ id: 2, schema: 'groups' }, union, entities)).toMatchSnapshot();
expect(denormalize(fromJS({ id: 2, schema: 'groups' }), union, fromJS(entities))).toMatchSnapshot();
});
test('returns the original value no schema is given', () => {
const union = new schema.Union(
{
users: user,
groups: group,
},
(input) => {
return input.username ? 'users' : 'groups';
}
);
expect(denormalize({ id: 1 }, union, entities)).toMatchSnapshot();
expect(denormalize(fromJS({ id: 1 }), union, fromJS(entities))).toMatchSnapshot();
});
});
================================================
FILE: src/schemas/__tests__/Values.test.js
================================================
// eslint-env jest
import { fromJS } from 'immutable';
import { denormalize, normalize, schema } from '../../';
describe(`${schema.Values.name} normalization`, () => {
test('normalizes the values of an object with the given schema', () => {
const cat = new schema.Entity('cats');
const dog = new schema.Entity('dogs');
const valuesSchema = new schema.Values(
{
dogs: dog,
cats: cat,
},
(entity, key) => entity.type
);
expect(
normalize(
{
fido: { id: 1, type: 'dogs' },
fluffy: { id: 1, type: 'cats' },
},
valuesSchema
)
).toMatchSnapshot();
});
test('can use a function to determine the schema when normalizing', () => {
const cat = new schema.Entity('cats');
const dog = new schema.Entity('dogs');
const valuesSchema = new schema.Values(
{
dogs: dog,
cats: cat,
},
(entity, key) => `${entity.type}s`
);
expect(
normalize(
{
fido: { id: 1, type: 'dog' },
fluffy: { id: 1, type: 'cat' },
jim: { id: 2, type: 'lizard' },
},
valuesSchema
)
).toMatchSnapshot();
});
test('filters out null and undefined values', () => {
const cat = new schema.Entity('cats');
const dog = new schema.Entity('dogs');
const valuesSchema = new schema.Values(
{
dogs: dog,
cats: cat,
},
(entity, key) => entity.type
);
expect(
normalize(
{
fido: undefined,
milo: null,
fluffy: { id: 1, type: 'cats' },
},
valuesSchema
)
).toMatchSnapshot();
});
});
describe(`${schema.Values.name} denormalization`, () => {
test('denormalizes the values of an object with the given schema', () => {
const cat = new schema.Entity('cats');
const dog = new schema.Entity('dogs');
const valuesSchema = new schema.Values(
{
dogs: dog,
cats: cat,
},
(entity, key) => entity.type
);
const entities = {
cats: { 1: { id: 1, type: 'cats' } },
dogs: { 1: { id: 1, type: 'dogs' } },
};
expect(
denormalize(
{
fido: { id: 1, schema: 'dogs' },
fluffy: { id: 1, schema: 'cats' },
},
valuesSchema,
entities
)
).toMatchSnapshot();
expect(
denormalize(
{
fido: { id: 1, schema: 'dogs' },
fluffy: { id: 1, schema: 'cats' },
},
valuesSchema,
fromJS(entities)
)
).toMatchSnapshot();
});
});
================================================
FILE: src/schemas/__tests__/__snapshots__/Array.test.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ArraySchema denormalization Class denormalizes a single entity 1`] = `
Array [
Object {
"id": 1,
"name": "Milo",
},
Object {
"id": 2,
"name": "Jake",
},
]
`;
exports[`ArraySchema denormalization Class denormalizes a single entity 2`] = `
Array [
Immutable.Map {
"id": 1,
"name": "Milo",
},
Immutable.Map {
"id": 2,
"name": "Jake",
},
]
`;
exports[`ArraySchema denormalization Class denormalizes multiple entities 1`] = `
Array [
Object {
"id": "123",
"type": "cats",
},
Object {
"id": "123",
"type": "people",
},
Object {
"id": "789",
},
Object {
"id": "456",
"type": "cats",
},
]
`;
exports[`ArraySchema denormalization Class denormalizes multiple entities 2`] = `
Array [
Immutable.Map {
"id": "123",
"type": "cats",
},
Immutable.Map {
"id": "123",
"type": "people",
},
Object {
"id": "789",
},
Immutable.Map {
"id": "456",
"type": "cats",
},
]
`;
exports[`ArraySchema denormalization Class does not assume mapping of schema to attribute values when schemaAttribute is not set 1`] = `
Object {
"entities": Object {
"cats": Object {
"1": Object {
"id": 1,
},
"2": Object {
"id": 2,
},
},
},
"result": Array [
Object {
"cat": 1,
"id": 5,
},
Object {
"cat": 2,
"id": 6,
},
],
}
`;
exports[`ArraySchema denormalization Class returns the input value if is not an array 1`] = `
Object {
"fillings": Object {},
"id": "123",
}
`;
exports[`ArraySchema denormalization Class returns the input value if is not an array 2`] = `
Immutable.Map {
"id": "123",
"fillings": Immutable.Map {},
}
`;
exports[`ArraySchema denormalization Object denormalizes a single entity 1`] = `
Array [
Object {
"id": 1,
"name": "Milo",
},
Object {
"id": 2,
"name": "Jake",
},
]
`;
exports[`ArraySchema denormalization Object denormalizes a single entity 2`] = `
Array [
Immutable.Map {
"id": 1,
"name": "Milo",
},
Immutable.Map {
"id": 2,
"name": "Jake",
},
]
`;
exports[`ArraySchema denormalization Object returns the input value if is not an array 1`] = `
Object {
"fillings": null,
"id": "123",
}
`;
exports[`ArraySchema denormalization Object returns the input value if is not an array 2`] = `
Immutable.Map {
"id": "123",
"fillings": null,
}
`;
exports[`ArraySchema normalization Class filters out undefined and null normalized values 1`] = `
Object {
"entities": Object {
"user": Object {
"123": Object {
"id": 123,
},
},
},
"result": Array [
123,
],
}
`;
exports[`ArraySchema normalization Class normalizes Objects using their values 1`] = `
Object {
"entities": Object {
"user": Object {
"1": Object {
"id": 1,
},
"2": Object {
"id": 2,
},
},
},
"result": Array [
1,
2,
],
}
`;
exports[`ArraySchema normalization Class normalizes a single entity 1`] = `
Object {
"entities": Object {
"cats": Object {
"1": Object {
"id": 1,
},
"2": Object {
"id": 2,
},
},
},
"result": Array [
1,
2,
],
}
`;
exports[`ArraySchema normalization Class normalizes multiple entities 1`] = `
Object {
"entities": Object {
"cats": Object {
"123": Object {
"id": "123",
"type": "cats",
},
"456": Object {
"id": "456",
"type": "cats",
},
},
"person": Object {
"123": Object {
"id": "123",
"type": "people",
},
},
},
"result": Array [
Object {
"id": "123",
"schema": "cats",
},
Object {
"id": "123",
"schema": "people",
},
Object {
"id": "789",
"name": "fido",
},
Object {
"id": "456",
"schema": "cats",
},
],
}
`;
exports[`ArraySchema normalization Class normalizes multiple entities 2`] = `
Array [
Array [
Object {
"id": "123",
"type": "cats",
},
Array [
Object {
"id": "123",
"type": "cats",
},
Object {
"id": "123",
"type": "people",
},
Object {
"id": "789",
"name": "fido",
},
Object {
"id": "456",
"type": "cats",
},
],
null,
],
Array [
Object {
"id": "123",
"type": "cats",
},
Array [
Object {
"id": "123",
"type": "cats",
},
Object {
"id": "123",
"type": "people",
},
Object {
"id": "789",
"name": "fido",
},
Object {
"id": "456",
"type": "cats",
},
],
null,
],
Array [
Object {
"id": "123",
"type": "people",
},
Array [
Object {
"id": "123",
"type": "cats",
},
Object {
"id": "123",
"type": "people",
},
Object {
"id": "789",
"name": "fido",
},
Object {
"id": "456",
"type": "cats",
},
],
null,
],
Array [
Object {
"id": "123",
"type": "people",
},
Array [
Object {
"id": "123",
"type": "cats",
},
Object {
"id": "123",
"type": "people",
},
Object {
"id": "789",
"name": "fido",
},
Object {
"id": "456",
"type": "cats",
},
],
null,
],
Array [
Object {
"id": "789",
"name": "fido",
},
Array [
Object {
"id": "123",
"type": "cats",
},
Object {
"id": "123",
"type": "people",
},
Object {
"id": "789",
"name": "fido",
},
Object {
"id": "456",
"type": "cats",
},
],
null,
],
Array [
Object {
"id": "456",
"type": "cats",
},
Array [
Object {
"id": "123",
"type": "cats",
},
Object {
"id": "123",
"type": "people",
},
Object {
"id": "789",
"name": "fido",
},
Object {
"id": "456",
"type": "cats",
},
],
null,
],
Array [
Object {
"id": "456",
"type": "cats",
},
Array [
Object {
"id": "123",
"type": "cats",
},
Object {
"id": "123",
"type": "people",
},
Object {
"id": "789",
"name": "fido",
},
Object {
"id": "456",
"type": "cats",
},
],
null,
],
]
`;
exports[`ArraySchema normalization Object normalizes Objects using their values 1`] = `
Object {
"entities": Object {
"user": Object {
"1": Object {
"id": 1,
},
"2": Object {
"id": 2,
},
},
},
"result": Array [
1,
2,
],
}
`;
exports[`ArraySchema normalization Object normalizes plain arrays as shorthand for ArraySchema 1`] = `
Object {
"entities": Object {
"user": Object {
"1": Object {
"id": 1,
},
"2": Object {
"id": 2,
},
},
},
"result": Array [
1,
2,
],
}
`;
exports[`ArraySchema normalization Object passes its parent to its children when normalizing 1`] = `
Object {
"entities": Object {
"children": Object {
"4": Object {
"content": "child",
"id": 4,
"parentId": 1,
"parentKey": "children",
},
},
"parents": Object {
"1": Object {
"children": Array [
4,
],
"content": "parent",
"id": 1,
},
},
},
"result": 1,
}
`;
================================================
FILE: src/schemas/__tests__/__snapshots__/Entity.test.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EntitySchema denormalization can denormalize already partially denormalized data 1`] = `
Object {
"food": Object {
"id": 1,
},
"id": 1,
}
`;
exports[`EntitySchema denormalization can denormalize already partially denormalized data 2`] = `
Immutable.Map {
"id": 1,
"food": Immutable.Map {
"id": 1,
},
}
`;
exports[`EntitySchema denormalization denormalizes an entity 1`] = `
Object {
"id": 1,
"type": "foo",
}
`;
exports[`EntitySchema denormalization denormalizes an entity 2`] = `
Immutable.Map {
"id": 1,
"type": "foo",
}
`;
exports[`EntitySchema denormalization denormalizes deep entities 1`] = `
Object {
"food": Object {
"id": 1,
},
"id": 1,
}
`;
exports[`EntitySchema denormalization denormalizes deep entities 2`] = `
Immutable.Map {
"id": 1,
"food": Immutable.Map {
"id": 1,
},
}
`;
exports[`EntitySchema denormalization denormalizes deep entities 3`] = `
Object {
"id": 2,
}
`;
exports[`EntitySchema denormalization denormalizes deep entities 4`] = `
Immutable.Map {
"id": 2,
}
`;
exports[`EntitySchema denormalization denormalizes deep entities with records 1`] = `
Immutable.Record {
"id": 1,
"food": Immutable.Record {
"id": 1,
},
}
`;
exports[`EntitySchema denormalization denormalizes deep entities with records 2`] = `
Immutable.Record {
"id": 1,
"food": Immutable.Record {
"id": 1,
},
}
`;
exports[`EntitySchema denormalization denormalizes deep entities with records 3`] = `
Immutable.Record {
"id": 2,
"food": null,
}
`;
exports[`EntitySchema denormalization denormalizes deep entities with records 4`] = `
Immutable.Record {
"id": 2,
"food": null,
}
`;
exports[`EntitySchema denormalization denormalizes recursive dependencies 1`] = `
Object {
"draftedBy": Object {
"id": "456",
"reports": Array [
[Circular],
],
"role": "manager",
},
"id": "123",
"publishedBy": Object {
"id": "456",
"reports": Array [
[Circular],
],
"role": "manager",
},
"title": "Weekly report",
}
`;
exports[`EntitySchema denormalization denormalizes recursive dependencies 2`] = `
Immutable.Map {
"id": "123",
"title": "Weekly report",
"draftedBy": Immutable.Map {
"id": "456",
"role": "manager",
"reports": Immutable.List [
Immutable.Map {
"id": "123",
"title": "Weekly report",
"draftedBy": "456",
"publishedBy": "456",
},
],
},
"publishedBy": Immutable.Map {
"id": "456",
"role": "manager",
"reports": Immutable.List [
Immutable.Map {
"id": "123",
"title": "Weekly report",
"draftedBy": "456",
"publishedBy": "456",
},
],
},
}
`;
exports[`EntitySchema denormalization denormalizes recursive dependencies 3`] = `
Object {
"id": "456",
"reports": Array [
Object {
"draftedBy": [Circular],
"id": "123",
"publishedBy": [Circular],
"title": "Weekly report",
},
],
"role": "manager",
}
`;
exports[`EntitySchema denormalization denormalizes recursive dependencies 4`] = `
Immutable.Map {
"id": "456",
"role": "manager",
"reports": Immutable.List [
Immutable.Map {
"id": "123",
"title": "Weekly report",
"draftedBy": Immutable.Map {
"id": "456",
"role": "manager",
"reports": Immutable.List [
"123",
],
},
"publishedBy": Immutable.Map {
"id": "456",
"role": "manager",
"reports": Immutable.List [
"123",
],
},
},
],
}
`;
exports[`EntitySchema denormalization denormalizes to undefined for missing data 1`] = `
Object {
"food": undefined,
"id": 1,
}
`;
exports[`EntitySchema denormalization denormalizes to undefined for missing data 2`] = `
Immutable.Map {
"id": 1,
"food": undefined,
}
`;
exports[`EntitySchema denormalization denormalizes to undefined for missing data 3`] = `undefined`;
exports[`EntitySchema denormalization denormalizes to undefined for missing data 4`] = `undefined`;
exports[`EntitySchema normalization idAttribute can build the entity's ID from the parent object 1`] = `
Object {
"entities": Object {
"users": Object {
"tacos-user-4": Object {
"id": "4",
"name": "Jimmy",
},
},
},
"result": Object {
"name": "tacos",
"user": "tacos-user-4",
},
}
`;
exports[`EntitySchema normalization idAttribute can normalize entity IDs based on their object key 1`] = `
Object {
"entities": Object {
"users": Object {
"4": Object {
"name": "taco",
},
"56": Object {
"name": "burrito",
},
},
},
"result": Object {
"4": Object {
"id": "4",
"schema": "users",
},
"56": Object {
"id": "56",
"schema": "users",
},
},
}
`;
exports[`EntitySchema normalization idAttribute can use a custom idAttribute string 1`] = `
Object {
"entities": Object {
"users": Object {
"134351": Object {
"id_str": "134351",
"name": "Kathy",
},
},
},
"result": "134351",
}
`;
exports[`EntitySchema normalization mergeStrategy can use a custom merging strategy 1`] = `
Object {
"entities": Object {
"tacos": Object {
"1": Object {
"alias": "bar",
"id": 1,
"name": "foo",
},
},
},
"result": Array [
1,
1,
],
}
`;
exports[`EntitySchema normalization mergeStrategy defaults to plain merging 1`] = `
Object {
"entities": Object {
"tacos": Object {
"1": Object {
"alias": "bar",
"id": 1,
"name": "bar",
},
},
},
"result": Array [
1,
1,
],
}
`;
exports[`EntitySchema normalization normalizes an entity 1`] = `
Object {
"entities": Object {
"item": Object {
"1": Object {
"id": 1,
},
},
},
"result": 1,
}
`;
exports[`EntitySchema normalization processStrategy can use a custom processing strategy 1`] = `
Object {
"entities": Object {
"tacos": Object {
"1": Object {
"id": 1,
"name": "foo",
"slug": "thing-1",
},
},
},
"result": 1,
}
`;
exports[`EntitySchema normalization processStrategy can use information from the parent in the process strategy 1`] = `
Object {
"entities": Object {
"children": Object {
"4": Object {
"content": "child",
"id": 4,
"parentId": 1,
"parentKey": "child",
},
},
"parents": Object {
"1": Object {
"child": 4,
"content": "parent",
"id": 1,
},
},
},
"result": 1,
}
`;
exports[`EntitySchema normalization processStrategy is run before and passed to the schema normalization 1`] = `
Object {
"entities": Object {
"attachments": Object {
"456": Object {
"id": "456",
},
},
"entries": Object {
"123": Object {
"data": Object {
"attachment": "456",
},
"id": "123",
"type": "message",
},
},
},
"result": "123",
}
`;
================================================
FILE: src/schemas/__tests__/__snapshots__/Object.test.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ObjectSchema denormalization denormalizes an object 1`] = `
Object {
"user": Object {
"id": 1,
"name": "Nacho",
},
}
`;
exports[`ObjectSchema denormalization denormalizes an object 2`] = `
Object {
"user": Immutable.Map {
"id": 1,
"name": "Nacho",
},
}
`;
exports[`ObjectSchema denormalization denormalizes an object 3`] = `
Immutable.Map {
"user": Immutable.Map {
"id": 1,
"name": "Nacho",
},
}
`;
exports[`ObjectSchema denormalization denormalizes an object that contains a property representing a an object with an id of zero 1`] = `
Object {
"user": Object {
"id": 0,
"name": "Chancho",
},
}
`;
exports[`ObjectSchema denormalization denormalizes an object that contains a property representing a an object with an id of zero 2`] = `
Object {
"user": Immutable.Map {
"id": 0,
"name": "Chancho",
},
}
`;
exports[`ObjectSchema denormalization denormalizes an object that contains a property representing a an object with an id of zero 3`] = `
Immutable.Map {
"user": Immutable.Map {
"id": 0,
"name": "Chancho",
},
}
`;
exports[`ObjectSchema denormalization denormalizes plain object shorthand 1`] = `
Object {
"user": Object {
"id": 1,
"name": "Jane",
},
}
`;
exports[`ObjectSchema denormalization denormalizes plain object shorthand 2`] = `
Object {
"user": Immutable.Map {
"id": 1,
"name": "Jane",
},
}
`;
exports[`ObjectSchema denormalization denormalizes plain object shorthand 3`] = `
Immutable.Map {
"user": Immutable.Map {
"id": 1,
"name": "Jane",
},
}
`;
exports[`ObjectSchema normali
gitextract_b0ejolxe/
├── .babelrc.js
├── .eslintignore
├── .eslintrc.js
├── .flowconfig
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug.md
│ │ ├── feature_request.md
│ │ └── support_question.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── lock.yml
│ └── workflows/
│ └── close-pull-request.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs/
│ ├── README.md
│ ├── api.md
│ ├── faqs.md
│ ├── introduction.md
│ ├── jsonapi.md
│ └── quickstart.md
├── examples/
│ ├── .eslintrc
│ ├── github/
│ │ ├── README.md
│ │ ├── index.js
│ │ ├── output.json
│ │ └── schema.js
│ ├── redux/
│ │ ├── .babelrc
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.js
│ │ ├── package.json
│ │ └── src/
│ │ ├── api/
│ │ │ ├── index.js
│ │ │ └── schema.js
│ │ └── redux/
│ │ ├── actions.js
│ │ ├── index.js
│ │ ├── modules/
│ │ │ ├── commits.js
│ │ │ ├── issues.js
│ │ │ ├── labels.js
│ │ │ ├── milestones.js
│ │ │ ├── pull-requests.js
│ │ │ ├── repos.js
│ │ │ └── users.js
│ │ ├── reducer.js
│ │ └── selectors.js
│ └── relationships/
│ ├── README.md
│ ├── index.js
│ ├── input.json
│ ├── output.json
│ └── schema.js
├── husky.config.js
├── index.d.ts
├── jest.config.js
├── lint-staged.config.js
├── package.json
├── prettier.config.js
├── rollup.config.js
├── src/
│ ├── __tests__/
│ │ ├── __snapshots__/
│ │ │ └── index.test.js.snap
│ │ └── index.test.js
│ ├── index.js
│ └── schemas/
│ ├── Array.js
│ ├── Entity.js
│ ├── ImmutableUtils.js
│ ├── Object.js
│ ├── Polymorphic.js
│ ├── Union.js
│ ├── Values.js
│ └── __tests__/
│ ├── Array.test.js
│ ├── Entity.test.js
│ ├── Object.test.js
│ ├── Union.test.js
│ ├── Values.test.js
│ └── __snapshots__/
│ ├── Array.test.js.snap
│ ├── Entity.test.js.snap
│ ├── Object.test.js.snap
│ ├── Union.test.js.snap
│ └── Values.test.js.snap
└── typescript-tests/
├── array.ts
├── array_schema.ts
├── entity.ts
├── github.ts
├── object.ts
├── relationships.ts
├── union.ts
└── values.ts
SYMBOL INDEX (73 symbols across 20 files)
FILE: examples/redux/index.js
constant REPO (line 6) | const REPO = 'paularmstrong/normalizr';
FILE: examples/redux/src/redux/actions.js
constant ADD_ENTITIES (line 8) | const ADD_ENTITIES = 'ADD_ENTITIES';
FILE: examples/redux/src/redux/modules/commits.js
constant STATE_KEY (line 6) | const STATE_KEY = 'commits';
function reducer (line 8) | function reducer(state = {}, action) {
FILE: examples/redux/src/redux/modules/issues.js
constant STATE_KEY (line 6) | const STATE_KEY = 'issues';
function reducer (line 8) | function reducer(state = {}, action) {
FILE: examples/redux/src/redux/modules/labels.js
constant STATE_KEY (line 6) | const STATE_KEY = 'labels';
function reducer (line 8) | function reducer(state = {}, action) {
FILE: examples/redux/src/redux/modules/milestones.js
constant STATE_KEY (line 6) | const STATE_KEY = 'milestones';
function reducer (line 8) | function reducer(state = {}, action) {
FILE: examples/redux/src/redux/modules/pull-requests.js
constant STATE_KEY (line 6) | const STATE_KEY = 'pullRequests';
function reducer (line 8) | function reducer(state = {}, action) {
FILE: examples/redux/src/redux/modules/repos.js
constant STATE_KEY (line 1) | const STATE_KEY = 'repo';
function reducer (line 3) | function reducer(state = {}, action) {
FILE: examples/redux/src/redux/modules/users.js
constant STATE_KEY (line 5) | const STATE_KEY = 'users';
function reducer (line 7) | function reducer(state = {}, action) {
FILE: index.d.ts
type StrategyFunction (line 2) | type StrategyFunction<T> = (value: any, parent: any, key: string) => T;
type SchemaFunction (line 3) | type SchemaFunction = (value: any, parent: any, key: string) => string;
type MergeFunction (line 4) | type MergeFunction = (entityA: any, entityB: any) => any;
type FallbackFunction (line 5) | type FallbackFunction<T> = (key: string, schema: schema.Entity<T>) => T;
class Array (line 7) | class Array<T = any> {
type EntityOptions (line 12) | interface EntityOptions<T = any> {
class Entity (line 19) | class Entity<T = any> {
class Object (line 27) | class Object<T = any> {
class Union (line 32) | class Union<T = any> {
class Values (line 37) | class Values<T = any> {
type Schema (line 43) | type Schema<T = any> =
type SchemaValueFunction (line 51) | type SchemaValueFunction<T> = (t: T) => Schema<T>;
type SchemaValue (line 52) | type SchemaValue<T> = Schema<T> | SchemaValueFunction<T>;
type SchemaObject (line 54) | interface SchemaObject<T> {
type SchemaArray (line 58) | interface SchemaArray<T> extends Array<Schema<T>> {}
type NormalizedSchema (line 60) | type NormalizedSchema<E, R> = { entities: E, result: R };
FILE: src/__tests__/index.test.js
class MyEntity (line 100) | class MyEntity extends schema.Entity {
method getId (line 105) | getId(entity, parent, key) {
method normalize (line 109) | normalize(input, parent, key, visit, addEntity, visitedEntities) {
FILE: src/schemas/Array.js
class ArraySchema (line 29) | class ArraySchema extends PolymorphicSchema {
method normalize (line 30) | normalize(input, parent, key, visit, addEntity, visitedEntities) {
method denormalize (line 38) | denormalize(input, unvisit) {
FILE: src/schemas/Entity.js
class EntitySchema (line 6) | class EntitySchema {
method constructor (line 7) | constructor(key, definition = {}, options = {}) {
method key (line 30) | get key() {
method idAttribute (line 34) | get idAttribute() {
method define (line 38) | define(definition) {
method getId (line 45) | getId(input, parent, key) {
method merge (line 49) | merge(entityA, entityB) {
method fallback (line 53) | fallback(id, schema) {
method normalize (line 57) | normalize(input, parent, key, visit, addEntity, visitedEntities) {
method denormalize (line 92) | denormalize(entity, unvisit) {
FILE: src/schemas/ImmutableUtils.js
function isImmutable (line 13) | function isImmutable(object) {
function denormalizeImmutable (line 31) | function denormalizeImmutable(schema, input, unvisit) {
FILE: src/schemas/Object.js
class ObjectSchema (line 32) | class ObjectSchema {
method constructor (line 33) | constructor(definition) {
method define (line 37) | define(definition) {
method normalize (line 44) | normalize(...args) {
method denormalize (line 48) | denormalize(...args) {
FILE: src/schemas/Polymorphic.js
class PolymorphicSchema (line 3) | class PolymorphicSchema {
method constructor (line 4) | constructor(definition, schemaAttribute) {
method isSingleSchema (line 11) | get isSingleSchema() {
method define (line 15) | define(definition) {
method getSchemaAttribute (line 19) | getSchemaAttribute(input, parent, key) {
method inferSchema (line 23) | inferSchema(input, parent, key) {
method normalizeValue (line 32) | normalizeValue(value, parent, key, visit, addEntity, visitedEntities) {
method denormalizeValue (line 43) | denormalizeValue(value, unvisit) {
FILE: src/schemas/Union.js
class UnionSchema (line 3) | class UnionSchema extends PolymorphicSchema {
method constructor (line 4) | constructor(definition, schemaAttribute) {
method normalize (line 11) | normalize(input, parent, key, visit, addEntity, visitedEntities) {
method denormalize (line 15) | denormalize(input, unvisit) {
FILE: src/schemas/Values.js
class ValuesSchema (line 3) | class ValuesSchema extends PolymorphicSchema {
method normalize (line 4) | normalize(input, parent, key, visit, addEntity, visitedEntities) {
method denormalize (line 16) | denormalize(input, unvisit) {
FILE: typescript-tests/entity.ts
type User (line 3) | type User = {
type Tweet (line 8) | type Tweet = {
FILE: typescript-tests/object.ts
type Response (line 3) | type Response = {
Condensed preview — 86 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (218K chars).
[
{
"path": ".babelrc.js",
"chars": 395,
"preview": "const { NODE_ENV, BABEL_ENV } = process.env;\n\nconst cjs = BABEL_ENV === 'cjs' || NODE_ENV === 'test';\n\nmodule.exports = "
},
{
"path": ".eslintignore",
"chars": 59,
"preview": "dist/*\nnode_modules/*\nexamples/*/node_modules/*\ncoverage/*\n"
},
{
"path": ".eslintrc.js",
"chars": 4213,
"preview": "module.exports = {\n // babel parser to support ES6/7 features\n parser: 'babel-eslint',\n parserOptions: {\n ecmaVers"
},
{
"path": ".flowconfig",
"chars": 0,
"preview": ""
},
{
"path": ".github/FUNDING.yml",
"chars": 74,
"preview": "# These are supported funding model platforms\n\ngithub: \n - paularmstrong\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug.md",
"chars": 827,
"preview": "---\nname: 🐛 Bug Report\nabout: If something isn't working as expected 🤔.\n---\n\n# Problem\n\nA short explanation of your prob"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 754,
"preview": "---\nname: 🚀 Feature Request\nabout: I have a suggestion (and I might like to implement it myself 😀)!\n---\n\n<!--\nWhen tryin"
},
{
"path": ".github/ISSUE_TEMPLATE/support_question.md",
"chars": 586,
"preview": "---\nname: 🤗 Support or Question\nabout: If you have a question 💬, please check for help on StackOverflow!\n---\n\nIssues on "
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 429,
"preview": "<!--\nThank you so much for contributing to open source and the Normalizr project!\n-->\n# Problem\n\nExplain the problem tha"
},
{
"path": ".github/lock.yml",
"chars": 610,
"preview": "# Configuration for lock-threads - https://github.com/dessant/lock-threads\n\n# Number of days of inactivity before a clos"
},
{
"path": ".github/workflows/close-pull-request.yml",
"chars": 329,
"preview": "name: Close Pull Request\n\non:\n pull_request_target:\n types: [opened]\n\njobs:\n run:\n runs-on: ubuntu-latest\n st"
},
{
"path": ".gitignore",
"chars": 78,
"preview": ".DS_Store\n\nnode_modules/*\ndist/*\n*.log\ncoverage/*\n.coveralls.yml\n.eslintcache\n"
},
{
"path": ".travis.yml",
"chars": 183,
"preview": "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"
},
{
"path": "CHANGELOG.md",
"chars": 2825,
"preview": "# v3.6.1\n\n- **Fixed** Add types for fallback strategy\n- **Chore** Upgraded development dependencies\n\n# v3.6.0\n\n- **Added"
},
{
"path": "CONTRIBUTING.md",
"chars": 1236,
"preview": "# Contributing\n\n## Issues\n\n1. Follow the [Issue Template](/.github/ISSUE_TEMPLATE.md) provided when opening a new Issue."
},
{
"path": "LICENSE",
"chars": 1094,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Dan Abramov, Paul Armstrong\n\nPermission is hereby granted, free of charge, to "
},
{
"path": "README.md",
"chars": 1507,
"preview": "# normalizr [](https:"
},
{
"path": "docs/README.md",
"chars": 3440,
"preview": "# normalizr [](https:"
},
{
"path": "docs/api.md",
"chars": 14974,
"preview": "# API\n\n- [normalize](#normalizedata-schema)\n- [denormalize](#denormalizeinput-schema-entities)\n- [schema](#schema)\n - ["
},
{
"path": "docs/faqs.md",
"chars": 278,
"preview": "# Frequently Asked Questions\n\nIf you are having trouble with Normalizr, try [StackOverflow](http://stackoverflow.com/que"
},
{
"path": "docs/introduction.md",
"chars": 2222,
"preview": "# Introduction\n\n## Motivation\n\nMany APIs, public or not, return JSON data that has deeply nested objects. Using data in "
},
{
"path": "docs/jsonapi.md",
"chars": 913,
"preview": "# 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 "
},
{
"path": "docs/quickstart.md",
"chars": 1317,
"preview": "# Quick Start\n\nConsider a typical blog post. The API response for a single post might look something like this:\n\n```json"
},
{
"path": "examples/.eslintrc",
"chars": 45,
"preview": "{\n \"rules\": {\n \"no-console\": \"off\"\n }\n}\n"
},
{
"path": "examples/github/README.md",
"chars": 575,
"preview": "# Normalizing GitHub Issues\n\nThis is a barebones example for node to illustrate how normalizing the GitHub Issues API en"
},
{
"path": "examples/github/index.js",
"chars": 797,
"preview": "import * as schema from './schema';\nimport fs from 'fs';\nimport https from 'https';\nimport { normalize } from '../../src"
},
{
"path": "examples/github/output.json",
"chars": 52166,
"preview": "{\n \"entities\": {\n \"users\": {\n \"14322\": {\n \"login\": \"whistlerbrk\",\n \"id\": 14322,\n \"avatar_u"
},
{
"path": "examples/github/schema.js",
"chars": 674,
"preview": "import { schema } from '../../src';\n\nexport const user = new schema.Entity('users');\n\nexport const label = new schema.En"
},
{
"path": "examples/redux/.babelrc",
"chars": 39,
"preview": "{\n \"presets\": [\"es2015\", \"stage-1\"]\n}\n"
},
{
"path": "examples/redux/.gitignore",
"chars": 21,
"preview": "node_modules/*\n*.log\n"
},
{
"path": "examples/redux/README.md",
"chars": 490,
"preview": "# Redux\n\nThis is a simple example of using Normalizr with Redux and Redux-Thunk. The command-line utility allows you to "
},
{
"path": "examples/redux/index.js",
"chars": 5643,
"preview": "import * as Action from './src/redux/actions';\nimport * as Selector from './src/redux/selectors';\nimport inquirer from '"
},
{
"path": "examples/redux/package.json",
"chars": 496,
"preview": "{\n \"name\": \"normalizr-redux-example\",\n \"version\": \"0.0.0\",\n \"description\": \"And example of using Normalizr with Redux"
},
{
"path": "examples/redux/src/api/index.js",
"chars": 131,
"preview": "import GitHubApi from 'github';\n\nexport default new GitHubApi({\n headers: {\n 'user-agent': 'Normalizr Redux Example'"
},
{
"path": "examples/redux/src/api/schema.js",
"chars": 815,
"preview": "import { schema } from '../../../../src';\n\nexport const user = new schema.Entity('users');\n\nexport const commit = new sc"
},
{
"path": "examples/redux/src/redux/actions.js",
"chars": 432,
"preview": "export { getCommits } from './modules/commits';\nexport { getIssues } from './modules/issues';\nexport { getLabels } from "
},
{
"path": "examples/redux/src/redux/index.js",
"chars": 284,
"preview": "import * as schema from '../api/schema';\nimport api from '../api';\nimport reducer from './reducer';\nimport thunk from 'r"
},
{
"path": "examples/redux/src/redux/modules/commits.js",
"chars": 1018,
"preview": "import * as Repo from './repos';\nimport { commit } from '../../api/schema';\nimport { ADD_ENTITIES, addEntities } from '."
},
{
"path": "examples/redux/src/redux/modules/issues.js",
"chars": 1013,
"preview": "import * as Repo from './repos';\nimport { issue } from '../../api/schema';\nimport { ADD_ENTITIES, addEntities } from '.."
},
{
"path": "examples/redux/src/redux/modules/labels.js",
"chars": 1012,
"preview": "import * as Repo from './repos';\nimport { label } from '../../api/schema';\nimport { ADD_ENTITIES, addEntities } from '.."
},
{
"path": "examples/redux/src/redux/modules/milestones.js",
"chars": 1040,
"preview": "import * as Repo from './repos';\nimport { milestone } from '../../api/schema';\nimport { ADD_ENTITIES, addEntities } from"
},
{
"path": "examples/redux/src/redux/modules/pull-requests.js",
"chars": 1051,
"preview": "import * as Repo from './repos';\nimport { pullRequest } from '../../api/schema';\nimport { ADD_ENTITIES, addEntities } fr"
},
{
"path": "examples/redux/src/redux/modules/repos.js",
"chars": 569,
"preview": "export const STATE_KEY = 'repo';\n\nexport default function reducer(state = {}, action) {\n switch (action.type) {\n cas"
},
{
"path": "examples/redux/src/redux/modules/users.js",
"chars": 639,
"preview": "import { ADD_ENTITIES } from '../actions';\nimport { denormalize } from '../../../../../src';\nimport { user } from '../.."
},
{
"path": "examples/redux/src/redux/reducer.js",
"chars": 877,
"preview": "import { combineReducers } from 'redux';\nimport commits, { STATE_KEY as COMMITS_STATE_KEY } from './modules/commits';\nim"
},
{
"path": "examples/redux/src/redux/selectors.js",
"chars": 353,
"preview": "export { selectHydrated as selectcommit } from './modules/commits';\nexport { selectHydrated as selectissue } from './mod"
},
{
"path": "examples/relationships/README.md",
"chars": 818,
"preview": "# Dealing with Relationships\n\nOccasionally, it is useful to have all one-to-one, one-to-many, and many-to-many relations"
},
{
"path": "examples/relationships/index.js",
"chars": 334,
"preview": "import fs from 'fs';\nimport input from './input.json';\nimport { normalize } from '../../src';\nimport path from 'path';\ni"
},
{
"path": "examples/relationships/input.json",
"chars": 1254,
"preview": "[\n {\n \"id\": \"1\",\n \"title\": \"My first post!\",\n \"author\": {\n \"id\": \"123\",\n \""
},
{
"path": "examples/relationships/output.json",
"chars": 1433,
"preview": "{\n \"entities\": {\n \"users\": {\n \"123\": {\n \"id\": \"123\",\n \"name\": \"Paul\",\n \"posts\": [\n "
},
{
"path": "examples/relationships/schema.js",
"chars": 974,
"preview": "import { schema } from '../../src';\n\nconst userProcessStrategy = (value, parent, key) => {\n switch (key) {\n case 'au"
},
{
"path": "husky.config.js",
"chars": 297,
"preview": "const runYarnLock = 'yarn install --frozen-lockfile';\n\nmodule.exports = {\n hooks: {\n 'post-checkout': `if [[ $HUSKY_"
},
{
"path": "index.d.ts",
"chars": 2106,
"preview": "declare namespace schema {\n export type StrategyFunction<T> = (value: any, parent: any, key: string) => T;\n export typ"
},
{
"path": "jest.config.js",
"chars": 66,
"preview": "module.exports = {\n testMatch: ['**/__tests__/**/*.test.js'],\n};\n"
},
{
"path": "lint-staged.config.js",
"chars": 193,
"preview": "module.exports = {\n '*.{md}': ['prettier --write', 'git add'],\n '*.{js,jsx,json}': ['yarn lint', 'prettier --write', '"
},
{
"path": "package.json",
"chars": 2431,
"preview": "{\n \"name\": \"normalizr\",\n \"version\": \"3.6.2\",\n \"description\": \"Normalizes and denormalizes JSON according to schema fo"
},
{
"path": "prettier.config.js",
"chars": 121,
"preview": "module.exports = {\n 'arrowParens': 'always',\n 'printWidth': 120,\n 'singleQuote': true,\n 'quoteProps': 'preserve',\n};"
},
{
"path": "rollup.config.js",
"chars": 807,
"preview": "import babel from 'rollup-plugin-babel';\nimport filesize from 'rollup-plugin-filesize';\nimport { name } from './package."
},
{
"path": "src/__tests__/__snapshots__/index.test.js.snap",
"chars": 4995,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`denormalize denormalizes entities 1`] = `\nArray [\n Object {\n \"i"
},
{
"path": "src/__tests__/index.test.js",
"chars": 9514,
"preview": "// eslint-env jest\nimport { denormalize, normalize, schema } from '../';\n\ndescribe('normalize', () => {\n [42, null, und"
},
{
"path": "src/index.js",
"chars": 3919,
"preview": "import * as ImmutableUtils from './schemas/ImmutableUtils';\nimport EntitySchema from './schemas/Entity';\nimport UnionSch"
},
{
"path": "src/schemas/Array.js",
"chars": 1547,
"preview": "import PolymorphicSchema from './Polymorphic';\n\nconst validateSchema = (definition) => {\n const isArray = Array.isArray"
},
{
"path": "src/schemas/Entity.js",
"chars": 3046,
"preview": "import * as ImmutableUtils from './ImmutableUtils';\n\nconst getDefaultGetId = (idAttribute) => (input) =>\n ImmutableUtil"
},
{
"path": "src/schemas/ImmutableUtils.js",
"chars": 1230,
"preview": "/**\n * Helpers to enable Immutable compatibility *without* bringing in\n * the 'immutable' package as a dependency.\n */\n\n"
},
{
"path": "src/schemas/Object.js",
"chars": 1435,
"preview": "import * as ImmutableUtils from './ImmutableUtils';\n\nexport const normalize = (schema, input, parent, key, visit, addEnt"
},
{
"path": "src/schemas/Polymorphic.js",
"chars": 1655,
"preview": "import { isImmutable } from './ImmutableUtils';\n\nexport default class PolymorphicSchema {\n constructor(definition, sche"
},
{
"path": "src/schemas/Union.js",
"chars": 568,
"preview": "import PolymorphicSchema from './Polymorphic';\n\nexport default class UnionSchema extends PolymorphicSchema {\n construct"
},
{
"path": "src/schemas/Values.js",
"chars": 746,
"preview": "import PolymorphicSchema from './Polymorphic';\n\nexport default class ValuesSchema extends PolymorphicSchema {\n normaliz"
},
{
"path": "src/schemas/__tests__/Array.test.js",
"chars": 6630,
"preview": "// eslint-env jest\nimport { fromJS } from 'immutable';\nimport { denormalize, normalize, schema } from '../../';\n\ndescrib"
},
{
"path": "src/schemas/__tests__/Entity.test.js",
"chars": 10020,
"preview": "// eslint-env jest\nimport { denormalize, normalize, schema } from '../../';\nimport { fromJS, Record } from 'immutable';\n"
},
{
"path": "src/schemas/__tests__/Object.test.js",
"chars": 2561,
"preview": "// eslint-env jest\nimport { fromJS } from 'immutable';\nimport { denormalize, normalize, schema } from '../../';\n\ndescrib"
},
{
"path": "src/schemas/__tests__/Union.test.js",
"chars": 3375,
"preview": "// eslint-env jest\nimport { fromJS } from 'immutable';\nimport { denormalize, normalize, schema } from '../../';\n\ndescrib"
},
{
"path": "src/schemas/__tests__/Values.test.js",
"chars": 2630,
"preview": "// eslint-env jest\nimport { fromJS } from 'immutable';\nimport { denormalize, normalize, schema } from '../../';\n\ndescrib"
},
{
"path": "src/schemas/__tests__/__snapshots__/Array.test.js.snap",
"chars": 7835,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`ArraySchema denormalization Class denormalizes a single entity 1`] "
},
{
"path": "src/schemas/__tests__/__snapshots__/Entity.test.js.snap",
"chars": 7123,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`EntitySchema denormalization can denormalize already partially deno"
},
{
"path": "src/schemas/__tests__/__snapshots__/Object.test.js.snap",
"chars": 2386,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`ObjectSchema denormalization denormalizes an object 1`] = `\nObject "
},
{
"path": "src/schemas/__tests__/__snapshots__/Union.test.js.snap",
"chars": 3307,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`UnionSchema denormalization denormalizes an array of multiple entit"
},
{
"path": "src/schemas/__tests__/__snapshots__/Values.test.js.snap",
"chars": 1918,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`ValuesSchema denormalization denormalizes the values of an object w"
},
{
"path": "typescript-tests/array.ts",
"chars": 494,
"preview": "import { denormalize, normalize, schema } from '../index'\n\nconst data = [{ id: '123', name: 'Jim' }, { id: '456', name: "
},
{
"path": "typescript-tests/array_schema.ts",
"chars": 504,
"preview": "import { denormalize, normalize, schema } from '../index'\n\nconst data = [{ id: 1, type: 'admin' }, { id: 2, type: 'user'"
},
{
"path": "typescript-tests/entity.ts",
"chars": 1022,
"preview": "import { denormalize, normalize, schema } from '../index'\n\ntype User = {\n id_str: string;\n name: string;\n};\n\ntype Twee"
},
{
"path": "typescript-tests/github.ts",
"chars": 761,
"preview": "import { normalize, schema } from '../index'\n\nconst user = new schema.Entity('users');\n\nconst label = new schema.Entity("
},
{
"path": "typescript-tests/object.ts",
"chars": 632,
"preview": "import { normalize, schema } from '../index'\n\ntype Response = {\n users: Array<{ id: string }>\n}\nconst data: Response = "
},
{
"path": "typescript-tests/relationships.ts",
"chars": 1105,
"preview": "import { normalize, schema } from '../index'\n\nconst userProcessStrategy = (value: any, parent: any, key: string) => {\n "
},
{
"path": "typescript-tests/union.ts",
"chars": 337,
"preview": "import { normalize, schema } from '../index'\n\nconst data = { owner: { id: 1, type: 'user' } };\n\nconst user = new schema."
},
{
"path": "typescript-tests/values.ts",
"chars": 253,
"preview": "import { normalize, schema } from '../index'\n\nconst data = { firstThing: { id: 1 }, secondThing: { id: 2 } };\n\nconst ite"
}
]
About this extraction
This page contains the full source code of the paularmstrong/normalizr GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 86 files (196.2 KB), approximately 55.7k tokens, and a symbol index with 73 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.