@data-client/normalizr
Version:
Normalizes and denormalizes JSON according to schema for Redux and Flux applications
116 lines (106 loc) • 3.31 kB
text/typescript
import { addEntities } from './addEntities.js';
import { getVisit } from './getVisit.js';
import type { Schema, NormalizedIndex } from '../interface.js';
import { createGetEntity } from '../memo/MemoCache.js';
import type {
NormalizeMeta,
NormalizeNullable,
NormalizedSchema,
StoreData,
} from '../types.js';
export const normalize = <
S extends Schema = Schema,
E extends Record<string, Record<string, any> | undefined> = Record<
string,
Record<string, any>
>,
R = NormalizeNullable<S>,
>(
schema: S | undefined,
input: any,
args: readonly any[] = [],
{ entities, indexes, entityMeta }: StoreData<E> = emptyStore,
meta: NormalizeMeta = { fetchedAt: 0, date: Date.now(), expiresAt: Infinity },
): NormalizedSchema<E, R> => {
// no schema means we don't process at all
if (schema === undefined || schema === null)
return {
result: input,
entities,
indexes,
entityMeta,
};
const schemaType = expectedSchemaType(schema);
if (
input === null ||
(typeof input !== schemaType &&
// we will allow a Delete schema to be a string or object
!(
(schema as any).key !== undefined &&
(schema as any).pk === undefined &&
typeof input === 'string'
))
) {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
const parseWorks = (input: string) => {
try {
return typeof JSON.parse(input) !== 'string';
} catch (e) {
return false;
}
};
if (typeof input === 'string' && parseWorks(input)) {
throw new Error(`Normalizing a string, but this does match schema.
Parsing this input string as JSON worked. This likely indicates fetch function did not parse
the JSON. By default, this only happens if "content-type" header includes "json".
See https://dataclient.io/rest/api/RestEndpoint#parseResponse for more information
Schema: ${JSON.stringify(schema, undefined, 2)}
Input: "${input}"`);
} else {
throw new Error(
`Unexpected input given to normalize. Expected type to be "${schemaType}", found "${
input === null ? 'null' : typeof input
}".
Schema: ${JSON.stringify(schema, undefined, 2)}
Input: "${input}"`,
);
}
} else {
throw new Error(
`Unexpected input given to normalize. Expected type to be "${schemaType}", found "${
input === null ? 'null' : typeof input
}".`,
);
}
}
const newEntities = new Map<string, Map<string, any>>();
const newIndexes = new Map<string, Map<string, any>>();
const ret: NormalizedSchema<E, R> = {
result: '' as any,
entities: { ...entities },
indexes: { ...indexes },
entityMeta: { ...entityMeta },
};
const addEntity = addEntities(
newEntities,
newIndexes,
ret.entities,
ret.indexes,
ret.entityMeta,
meta,
);
const visit = getVisit(addEntity, createGetEntity(entities));
ret.result = visit(schema, input, input, undefined, args);
return ret;
};
function expectedSchemaType(schema: Schema) {
return ['object', 'function'].includes(typeof schema) ? 'object' : (
typeof schema
);
}
const emptyStore: StoreData<any> = {
entities: {},
indexes: {},
entityMeta: {},
};