firebase/emberfire

View on GitHub
addon/serializers/realtime-database.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import DS from 'ember-data';

import { database } from 'firebase/app';

export default class RealtimeDatabaseSerializer extends DS.JSONSerializer {

  // @ts-ignore TODO update the types to support
  applyTransforms: (a: DS.Model, b: any) => void;

  normalizeSingleResponse(store: DS.Store, primaryModelClass: DS.Model, payload: database.DataSnapshot, _id: string | number, _requestType: string) {
    if (!payload.exists) { throw  new DS.NotFoundError(); }
    let normalized = normalize(store, primaryModelClass, payload);
    this.applyTransforms(primaryModelClass, normalized.data.attributes);
    return normalized;
  }

  normalizeArrayResponse(store: DS.Store, primaryModelClass: DS.Model, payload: database.DataSnapshot, _id: string | number, _requestType: string) {
    const normalizedPayload: any[] = []
    payload.forEach(snapshot => {
      let normalized = normalize(store, primaryModelClass, snapshot);
      this.applyTransforms(primaryModelClass, normalized.data.attributes);
      normalizedPayload.push(normalized);
    });
    const included = new Array().concat(...normalizedPayload.map(({included}) => included));
    const meta = { query: (payload as any).query || payload.ref };
    const data = normalizedPayload.map(({data}) => data);
    return { data, included, meta };
  }

  normalizeCreateRecordResponse(_store: DS.Store, _primaryModelClass: DS.Model, payload: any, id: string | number, _requestType: string) {
    return { data: { id: id || payload.ref.key, attributes: payload.data }};
  }

}

declare module 'ember-data' {
  interface SerializerRegistry {
    'realtime-database': RealtimeDatabaseSerializer;
  }
}

export const normalize = (store: DS.Store, modelClass: DS.Model, snapshot: database.DataSnapshot) => {
  const id = snapshot.key;
  const type = (<any>modelClass).modelName;
  const attributes = { ...snapshot.val(), _ref: snapshot.ref };
  const { relationships, included } = normalizeRelationships(store, modelClass, attributes);
  const data = { id, type, attributes, relationships };
  return { data, included };
}

const normalizeRelationships = (store: DS.Store, modelClass: DS.Model, attributes: any) => {
  const relationships: {[field:string]: any} = {};
  const included: any[] = [];
  modelClass.eachRelationship((key: string, relationship: any) => {
    const attribute = attributes[key];
    delete attributes[key];
    relationships[key] = normalizeRealtionship(relationship)(store, attribute, relationship, included);
  }, null);
  return {relationships, included};
}

const normalizeRealtionship = (relationship: any) => {
  if (relationship.kind === 'belongsTo') {
    return normalizeBelongsTo;
  } else if (relationship.options.embedded) {
    return normalizeEmbedded;
  } else {
    return normalizeHasMany;
  }
}

const normalizeBelongsTo = (_store: DS.Store, attribute: any, relationship: any, _included: any[]) => {
  if (attribute) {
    return { data: { id: attribute, type: relationship.type } };
  } else {
    return { };
  }
}

const normalizeEmbedded = (store: DS.Store, attribute: any, relationship: any, included: any[]) => {
  if (attribute) {
    Object.keys(attribute).forEach(key => {
      const val = attribute[key];
      const snapshot = { key, val: () => val } as database.DataSnapshot;
      const model = store.modelFor(relationship.type as never);
      const { data, included: includes } = normalize(store, model, snapshot);
      included.push(data);
      includes.forEach(record => included.push(record));
    });
    const data = included
      .filter(record => record.type == relationship.type)
      .map(record => ({ id: record.id, type: record.type }));
    return { links: { related: 'emberfire' }, data };
  } else {
    return { };
  }
}

const normalizeHasMany = (_store: DS.Store, _attribute: any, _relationship: any, _included: any[]) => 
  ({ links: { related: 'emberfire' } })