0

Several days that I am stuck on something so basic as adding remote models using feathersjs. Starting to feel really stupid to be stuck on something so basic. :-/

How to create a tag then add it a post: Tags.addPost({[...]})

How to access sequelize models?

https://sequelize.org/master/manual/assocs.html#special-methods-mixins-added-to-instances

  const tags = sequelizeClient.define(
    'tags',
    {
      title: {
        type: DataTypes.STRING,
      },
    }
  const tags = sequelizeClient.define(
    'posts',
    {
      title: {
        type: DataTypes.STRING,
      },
    }

// [...]

  (posts as any).associate = function (models: any) {
    // Define associations here
    // See http://docs.sequelizejs.com/en/latest/docs/associations/

    const { tags, posts } = models;

    posts.belongsTo(tags);
    tags.hasMany(posts);
  };
  app.services.tags
    .create({
      title: 'myTag',
    })
    .then(myTag => {
      // has anybody found how to do something similar to
      // myTag.addPost({ title: 'myPost'}) ?
    })

Antony Gibbs
  • 1,321
  • 14
  • 24

1 Answers1

0

This is part of the solution I found.

Based on this Using feathers-client and sequelize many-to-many relation So using hooks this is how I include remote models on demand.

Note: still looking to find how to Tag.addPost({...})

src/shared/hooks/include-remote-models.ts

import { Hook, HookContext } from '@feathersjs/feathers';
import { mergeDeep, castToBoolean } from '../shared/utils';
import logger from '../logger';
import { merge } from 'lodash';

const hydrate = require('feathers-sequelize/hooks/hydrate');

const castToBoolean = (val: any): Boolean => {
  return ['number', 'bigint'].includes(typeof val) ? val != 0 : [true, 'true', 't', '1'].includes(val);
}

// inspired by https://stackoverflow.com/questions/48602085/using-feathers-client-and-sequelize-many-to-many-relation

export default (options = {}): any => {
  return async function (this: any, context: HookContext) {
    switch (context.type) {
      case 'before':
        if (context.params && context.params.query) {
          if ('$include' in context.params.query) {
            const { $include, ...query } = context.params.query;

            // Update the query to not include `$include`
            context.params.query = query;

            context.params.sequelize = context.params.sequelize || {};

            let include: any = [];

            // see https://jsperf.com/array-coerce
            (Array.isArray($include) ? $include : [$include]).forEach((name: string) => {
              if (name in context.service.Model.associations) {
                include.push({
                  association: context.service.Model.associations[name],
                  as: name,
                });
              } else {
                logger.warn(
                  `Requested association '${name}' of model '%s' doesn't exist. Available associations are: %O`,
                  context.service.Model.name,
                  context.service.Model.associations
                );
              }
            });

            if (include) {
              merge(context.params.sequelize, {
                include,
                raw: false,
              });
            }
          }

          if ('$scope' in context.params.query) {
            const { $scope, ...query } = context.params.query;
            // Update the query to not include `$scope`
            context.params.query = query;

            context.params.sequelize = context.params.sequelize || {};

            context.params.sequelize.scope = $scope;

            Object.assign(context.params.sequelize, {
              raw: false,
            });
          }

          if ('$raw' in context.params.query) {
            const { $raw, ...query } = context.params.query;

            // Update the query to not include `$raw`
            context.params.query = query;

            context.params.sequelize = context.params.sequelize || {};
            Object.assign(context.params.sequelize, { raw: castToBoolean($raw) });
          }
        }

        break;

      case 'after':
        // I still don't understand why we have to do this as I didn't see any difference using it (yet)
        // they say so here:
        // https://github.com/feathersjs-ecosystem/feathers-sequelize#working-with-sequelize-model-instances
        if (
          context.params &&
          context.params.sequelize &&
          context.params.sequelize.include &&
          !context.params.sequelize.raw
        ) {
          hydrate({
            include: context.params.sequelize.include,
          }).call(this, context);
        }

        break;
    }

    // return Promise.resolve(context);
    return context;
  };
};

Edit: improved code and added $scope

src/app.hooks.ts

import { includeRemoteModels } from './hooks/include-remote-models';
// Application hooks that run for every service
// Don't remove this comment. It's needed to format import lines nicely.

export default {
  before: {
    all: [includeRemoteModels()],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
  },
  after: {
    all: [includeRemoteModels()],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
  },

  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
  },
};

Antony Gibbs
  • 1,321
  • 14
  • 24