diff --git a/docs/api.md b/docs/api.md index e08e22ab..795cc0c8 100644 --- a/docs/api.md +++ b/docs/api.md @@ -175,6 +175,9 @@ const normalizedData = normalize(data, myArray); - `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 @@ -253,6 +256,56 @@ normalize(data, [patronsSchema]); } ``` +#### `fallbackStrategy` Usage +```js +const users = [ + { id: '1', name: "Emily", requestState: 'SUCCEEDED' }, + { id: '2', name: "Douglas", requestState: 'SUCCEEDED' } +]; +const books = [ + {id: '1', name: "Book 1", author: 1 }, + {id: '2', name: "Book 2", author: 2 }, + {id: '3', name: "Book 3", author: 3 } +] + +const authorSchema = new schema.Entity('authors'); +const bookSchema = new schema.Entity('books', { + author: authorSchema +}, { + fallbackStrategy: (key, schema) => { + return { + [schema.idAttribute]: key, + name: 'Unknown', + requestState: 'NONE' + }; + } +}); + +``` + + +#### 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: `{ ... }`_ diff --git a/src/index.js b/src/index.js index 2c576b51..f4da7983 100644 --- a/src/index.js +++ b/src/index.js @@ -59,7 +59,12 @@ export const normalize = (input, schema) => { }; const unvisitEntity = (id, schema, unvisit, getEntity, cache) => { - const entity = getEntity(id, schema); + let entity = getEntity(id, schema); + + if (entity === undefined && schema instanceof EntitySchema) { + entity = schema.fallback(id, schema); + } + if (typeof entity !== 'object' || entity === null) { return entity; } diff --git a/src/schemas/Entity.js b/src/schemas/Entity.js index 218a2d41..fdf138b2 100644 --- a/src/schemas/Entity.js +++ b/src/schemas/Entity.js @@ -14,7 +14,8 @@ export default class EntitySchema { mergeStrategy = (entityA, entityB) => { return { ...entityA, ...entityB }; }, - processStrategy = (input) => ({ ...input }) + processStrategy = (input) => ({ ...input }), + fallbackStrategy = (key, schema) => undefined } = options; this._key = key; @@ -22,6 +23,7 @@ export default class EntitySchema { this._idAttribute = idAttribute; this._mergeStrategy = mergeStrategy; this._processStrategy = processStrategy; + this._fallbackStrategy = fallbackStrategy; this.define(definition); } @@ -48,6 +50,10 @@ export default class EntitySchema { 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; diff --git a/src/schemas/__tests__/Entity.test.js b/src/schemas/__tests__/Entity.test.js index b42da026..28c31d24 100644 --- a/src/schemas/__tests__/Entity.test.js +++ b/src/schemas/__tests__/Entity.test.js @@ -292,4 +292,41 @@ describe(`${schema.Entity.name} denormalization`, () => { // 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'); + // + }); });