npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

alpha-restful

v0.7.38

Published

O Alpha Restful é um framework para o desenvolvimento de aplicações web Rest backend em MongoDB, feito para Node JS. Esta ferramenta é executada em cima do Express JS e da ORM de banco de dados Mongoose.

Downloads

86

Readme

IMPORTANT WARNING

This version of Alpha Restful is just a prototype of what is to come. We strongly recommend NOT to use this package for production, as it is discontinued. In the future, a completely redesigned Alpha Restful will appear, with a totally new architecture and new functions, in addition to allowing the inclusion of plugins in which any specific behavior can be replaced and new functions can be added by the community.

Currently, the guide written here is in Portuguese as it is a BETA version that will be discontinued. But the new version of Alpha Restful will be completely in English.

Alpha Restful (Discontinued. A new Alpha Restful, completely redesigned, will arrive soon)

O Alpha Restful é um framework para o desenvolvimento de aplicações web Rest backend em MongoDB, feito para Node JS. Esta ferramenta é executada em cima do Express JS e da ORM de banco de dados Mongoose.

Atenção!

O Alpha Restful possui compatibilidade apenas com o NODE 8 ou superior!

O Alpha Restful está em versão Beta. Por causa disto, eventualmente algum erro poderá ocorrer. Caso você detecte algum erro, sinta-se livre para fazer uma publicação nas Issues do github, que eu tentarei resolver o mais rápido possível.

Guia

Aqui será apresentado um guia para você poder já sair programando!

Atenção

Este guia não engloba todas as funções implementadas pelo Alpha Restful. Em breve será disponibilizado uma documentação completa com todas as funções e opções que podem ser utilizadas!

Instalação

Preparando Ambiente

  • Instale o Node JS em seu computador clicando neste link.

  • Instale o banco de dados MongoDB em seu computador clicando neste link.

Criando Aplicação Node

Você pode criar uma nova aplicação Node através do seguinte comando:

npm init -y

Incluindo o Alpha Restful em seu Projeto

Execute o seguinte código no diretório de seu projeto

npm install alpha-restful --save

Preparando o Ambiente Express JS

O Alpha Restful executa em cima do Express JS. Para mais informações sobre o Express JS, acesse o link https://expressjs.com/.

Se você já trabalha com o Express JS você poderá pular esta seção.

A seguir será exibido um exemplo de código que prepara o ambiente do Express JS, que poderá ser colocado no arquivo principal (primeiro script a ser executado) de sua aplicação:

const express = require('express')
const path = require('path')
const cookieParser = require('cookie-parser')
const logger = require('morgan')

const app = express()

app.use(logger('dev'))
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.static(path.join(__dirname, 'public')))

Inicializando o seu Servidor

Para inicializar sua aplicação na porta 3001, com o banco de dados no localhost com o nome do banco db_test e com o nome da aplicação de aplicacao-teste, basta acrescentar no final de seu script principal o seguinte código:

const mongoose = require('mongoose') // ORM de banco de dados MongoDB
const { Connector, www, Restful, Entity } = require('alpha-restful') // Importa módulos do Alpha-Restful
const restful = new Restful('aplicacao-teste', {
  locale: 'en'
}) // Instância do Alpha Restful

// ...

process.env.PORT = 3001 // Porta do servidor
const connector = new Connector('mongodb://localhost/db_test', restful, app) // Conexão com Banco de Dados Mongo DB
www(connector, true) // Inicializia o servidor

O primeiro argumento do construtor de Restful é o nome da sua aplicação. Neste caso definiu-se o nome da aplicação de aplicacao-teste.

O segundo argumento deste construtor é um objeto opcional que contém várias opções. Em breve será disponibilizado uma explicação detalhada para cada opção. Umas dessas opções é a opção locale, que representa a linguagem na qual o banco de dados mongodb irá reconhecer. Este locale é utilizado para a chamada automática da função collation do mongodb para, por exemplo, possibilitar uma ordenação ignorando acentos e letras maiúsculas/minúsculas. Dependendo de sua versão do mongodb, um erro poderá ocorrer neste collation. Para corrigir este erro (caso ocorra), basta remover o uso do collation com a opção isLocale igual a false.

Caso o locale não seja definido, o padrão é o inglês (en). A seguir é apresentado uma tabela com todas as linguagens suportadas pelo locale.

Language | Locale --------------------------------- | ------ Afrikaans | af Albanian | sq Amharic | am Armenian | hy Arabic | ar Assamese | as Azeri | az Bengali | bn Belarusian | be Bengali | bn Bosnian | bs Bosnian (Cyrillic) | bs_Cyrl Bulgarian | bg Burmese | my Catalan | ca Cherokee | chr Chinese | zh Chinese (Traditional) | zh_Hant Croatian | hr Czech | cs Danish | da Dutch nl | nl Dzongkha | dz English | en English (United States) | en_US English (United States, Computer) | en_US_POSIX Esperanto | eo Estonian | et Ewe | ee Faroese | fo Filipino | fil Finnish | fi_FI French | fr French (Canada) | fr_Ca Galician | gl Georgian | ka German | de German (Austria) | de_AT Greek | rl Gujarati | gu Hausa | ha Hawaiian | haw Hebrew | he Hindi | hi Hungarian | hu Icelandic | is Igbo | ig Inari Sami | smn Indonesian | id Irish | gs Italian | it Japanese | ja Kalaallisut | kl Kannada | kn Kazakh | kk Khmer | km Konkani | kok Korean | ko Kyrgyz | ky Lakota | lk Lao | lo Latvian | lv Lingala | li Lithuanian | lt Lower Sorbian | dsb Luxembourgish | lb Macedonian | mk Malay | ms Malayalam | ml Maltese | mt Marathi | mr Mongolian | mn Nepali | ne Northern Sami | se Norwegian Bokmål | nb Norwegian Nynorsk | nn Oriya | or Oromo | om Pashto | ps Persian | fa Persian (Afghanistan) | fa_AF Polish | pl Portuguese | pt Punjabi | pa Romanian | ro Russian | ru Serbian | sr Serbian (Latin) | sr_Latn Sinhala | si Slovak | sk Slovenian | sl Spanish | es Swahili | sw Swedish | sv Tamil | ta Telugu | te Thai | th Tibetan | bo Tongan | to Turkish | tr Ukrainian | uk Upper Sorbian | hsb Urdu | ur Uyghur | ug Vietnamese | vi Walser | wae Welsh | cy Yiddish | yi Yoruba | yo Zulu | zu

O primeiro argumento do construtor da classe Connector é a URL do local onde o banco de dados mongodb está localizado. O segundo argumento é a instância da classe Restful, que representa a instância do framework Alpha Restful. O terceiro argumento é a instância do Express JS.

O primeiro argumento da função www é o connector do banco de dados MongoDB. Se o segundo argumento for true, o Alpha Restful irá gerar automaticamente rotas para tratamento de erros. O www é uma função que retorna uma promise, na qual o retorno da promise é um objeto contendo o atributo server (representando a chamada do método require('http').createServer(app)) e o atributo debug (usado para mostrar mensagens de debug).

Toda a implementação de sua aplicação deverá ocorrer antes da chamada do método www. Aqui serão mostrados códigos como se estivessem dentro de um mesmo arquivo, mas o programador poderá se sentir a vontade de modularizar seu código em vários arquivos seguindo a estrutura que achar melhor.

Para executar a sua aplicação, basta executar o comando:

node <arquivo-principal>.js

Para executar a sua aplicação no linux / Mac OS no modo debug execute:

DEBUG=<nome-da-aplicação>:* node <arquivo-principal>.js

Para executar a sua aplicação no windows no modo debug execute:

set DEBUG=<nome-da-aplicação>:* & node <arquivo-principal>.js

Modelando suas Entidades

Digamos que você deseje criar a entidade Pessoa com o atributo nome e idade, disponível na URI /pessoas. Para fazer isto bastaria fazer o seguinte código:

const Pessoa = new Entity({
    name: 'Pessoa', // Nome da sua entidade
    resource: '/pessoas', // URI utilizada para acessar os recursos REST desta entidade. Também é o nome da coleção de documentos utilizada para esta entidade (sem a barra)
    descriptor: { // Objeto que descreve os atributos da entidade
        name: String, // Uma pessoa possui um nome que é do tipo String
        idade: Number // Uma pessoa possui uma idade que é do tipo Number
    }
})

restful.add(Pessoa) // Adiciona ao Restful a entidade Pessoa

O descriptor descreve como a entidade Pessoa está modelada. A sintaxe do descriptor segue as especificações presentes nos schemas do Mongoose.

Integração com o Mongoose

O Alpha Restful é completamente integrado com o Mongoose. Todas as funcionalidade disponibilizadas pelo Mongoose poderão ser utilizadas dentro de seu projeto Alpha Restful.

Para acessar o schema da entidade para uso do Mongoose, basta digitar Pessoa.schema. Para acessar o model da entidade para uso do Mongoose, basta digitar Pessoa.model.

Criando CRUD de Entidade

Para que o Alpha Restful crie as rotas dos métodos http de CRUD, basta informar na entidade quais métodos http devem ser gerados automaticamente:

const Pessoa = new Entity({
    name: 'Pessoa', // Nome da sua entidade
    resource: '/pessoas', // URI utilizada para acessar os recursos REST desta entidade. Também é o nome da coleção de documentos utilizada para esta entidade (sem a barra)
    descriptor: { // Objeto que descreve os atributos da entidade
        name: String, // Uma pessoa possui um nome que é do tipo String
        idade: Number // Uma pessoa possui uma idade que é do tipo Number
    },
    methods: ['get', 'post', 'put', 'delete', 'patch'] // Métodos http dos CRUDS
})

Para que o Alpha Restful gere automaticamente todas as rotas de CRUD definidas em todas as entidade, é necessário executar o seguinte código após a definição das entidades:

restful.applyRouters(app)

Relacionamento entre Entidades

O Mongoose possui uma funcionalidade chamada de populate. Esta funcionalidade permite um certo relacionamento entre entidades.

Porém o Alpha Restful disponibiliza seu próprio método de relacionamento entre entidades, disponibilizando diversas funções a mais.

Para que as funcionalidades a seguir descritas funcionem, é necessário que o relacionamento entre entidades seja realizado por meio do Alpha Restful.

Relacionamento no Dono da Relação (Onde Serão Armazenados os ids)

Digamos que você crie uma entidade chamada de Casa, e deseje fazer um relacionamento de muitos para muitos entre Casa e Pessoa. Neste caso, bastaria implementar o modelo da entidade Casa e definir o relacionamento em algum atributo da entidade:

const Casa = new Entity({
    name: 'Casa',
    resource: 'casas',
    descriptor: {
        endereco: {
            numero: String,
            rua: String,
            cidade: String,
            estado: String,
            pais: String
        },
        /*
        Uma casa possui várias pessoas. No documento da
        entidade Casa, armazena-se uma lista de objetos,
        na qual pelo menos o id da entidade Pessoa deve
        ser armazenada
        */
        pessoas: [{
            id: mongoose.Schema.Types.ObjectId // Id da entidade pessoa
        }]
    },
    /*
    A opção sync é responsável por descrever o comportamento
    de cada atributo da entidade. Nesta opção, torna-se
    possível sincronizar um atributo da entidade com
    instâncias de outras entidades
    */
    sync: {
        pessoas: 'Pessoa'
    },
    methods: ['get', 'post', 'put', 'delete', 'patch']
})

Na entidade Casa, definiu-se o atributo pessoas. Neste atributo será armazenado uma lista de objetos contendo atributos de cada pessoa relacionada. O id deve ser obrigatoriamente armazenado, mas outros atributos da entidade podem ser armazenados, assim como atributos presentes no relacionamento entre Casa e Pessoa. Caso a entidade Casa possua apenas uma pessoa, bastaria remover os colchetes ( [ ] ) que envolvem a definição do objeto armazenado pelo atributo.

Através da opção sync, sincronizou-se o atributo pessoas com a entidade Pessoa. Através do sync, diversas opções estão disponíveis para o atributo sincronizado. Neste exemplo, apenas um relacionamento simples está definido. Para relacionamentos simples (relacionamentos sem outras opções), basta colocar no lado direito uma String contendo o nome da entidade relacionada.

Relacionamento na Entidade Relacionada

Mas, e se desejarmos obter na entidade Pessoa a lista de casas relacionadas a ela? Nós não podemos repetir o procedimento anterior na entidade Pessoa, pois o Alpha Restful interpretaria isso como outro relacionamento. Nós poderíamos criar um novo atributo em Pessoa para armazenar manualmente todos os ids das casas que se relacionam com a pessoa armazenada, porém isto deixaria o código da sua aplicação complexa e suscetível a erros humanos.

Pensando nisto, o Alpha Restful dispõe de uma opção na qual você informa que existe um atributo virtual, representando o relacionamento feito por outra entidade. Este atributo não é armazenado em seu banco de dados, porém o Alpha Restful irá considerar este atributo como se ele estivesse declarado na entidade. Tal atributo virtual representa o relacionamento na entidade relacionada, sem que seja necessário adicionar nenhum dado no documento do MongoDB.

Para realizar este procedimento, a fim de obter em pessoa as casas a ela relacionada, bastaria adicionar no sync um atributo com a opção syncronized:

const Pessoa = new Entity({
    name: 'Pessoa',
    resource: '/pessoas',
    descriptor: {
        name: String,
        idade: Number
    },
    sync: {
        /*
        Como este relacionamento exige a passagem de
        novas opções, ao invés da String contendo o
        nome da entidade, coloca-se um objeto, na
        qual o atributo name é o nome da entidade
        relacionada.
        */
        casas: {
            name: 'Casa',

            /*
            A opção syncronized descreve
            o nome do atributo que realiza o
            relacionamento com esta entidade.
            */
            syncronized: ['pessoas']
        }
    },
    methods: ['get', 'post', 'put', 'delete', 'patch']
})

No sync da entidade Pessoa, defini-se que uma pessoa possui um relacionamento com Casa, porém o atributo casas não será armazenado no banco (por isso este atributo não está presente no descriptor), porém o Alpha Restful irá considerar a existência de tal atributo em pesquisas no banco de dados. A opção syncronized no sync de Pessoa contém o nome do atributo na entidade Casa que se relaciona com Pessoa. Se houverem várias casas, envolve-se em colchetes ( [ ] ) o nome do atributo. Caso haja sempre apenas uma Casa, remove-se os colchetes no nome do atributo em syncronized.

Por padrão o Alpha Restful irá buscar todos os ids das casas relacionadas com esta pessoa e colocar no atributo casas automaticamente em tempo de execução. Para que este atributo não seja buscado ao realizar uma busca por pessoa, basta adicionar uma opção de jsonIgnore que será explicado mais a frente. Mesmo que seja adicionada esta opção, você ainda poderá buscar pessoas filtradas por este atributo.

Garantia Automática de Consistência dos Dados

Neste exemplo na qual estamos abordando, caso uma pessoa seja removida, automaticamente serão removidos os atributos da pessoa nas instâncias de entidades que se relacionam com Pessoa. Desta forma não haverá ids de entidades que já não existem mais no banco de dados. Se por algum motivo você desejar que este comportamento seja desativado em algum atributo, basta adicionar a opção ignoreVerifyRelationship com o valor true no sync do atributo da entidade desejada.

Atributos de Relacionamento

O Alpha Restful permite que sejam definidos atributos de relacionamentos. Estes atributos de relacionamento podem ser atributos presentes dentro da entidade relacionada, como também podem ser atributos presentes apenas dentro do relacionamento.

Como exemplo, imagine a seguinte situação: imagine que no relacionamento entre Pessoa e Casa existe um aluguel. Vamos imaginar que por algum motivo o aluguel precisa ser modelado como uma entidade separada. Neste caso, pode-se representar esta situação com o seguinte código:

const Aluguel = new Entity({
    name: 'Aluguel',
    resource: 'alugueis',
    descriptor: {
        valor: Number,
        dataInicio: Date,
        dataFim: Date
    },
    methods: ['get', 'post', 'put', 'delete', 'patch']
})

restful.add(Aluguel)

const Casa = new Entity({
    name: 'Casa',
    resource: 'casas',
    descriptor: {
        endereco: {
            numero: String,
            rua: String,
            cidade: String,
            estado: String,
            pais: String
        },
        pessoas: [{
            id: mongoose.Schema.Types.ObjectId,

            /*
            No relacionamento entre Pessoa e Casa existe
            um atributo de relacionamento chamado aluguel
            que por sua vez se relaciona com a entidade
            Aluguel
            */
            aluguel: {
                id: mongoose.Schema.Types.ObjectId
            }
        }]
    },
    sync: {
        pessoas: {
            name: 'Pessoa',

            /*
            Dentro de cada pessoa existe um atributo de
            relacionamento chamado aluguel que se
            relaciona com a entidade Aluguel
            */
            sync: {
                aluguel: 'Aluguel'
            }
        }
    },
    methods: ['get', 'post', 'put', 'delete', 'patch']
})

Atributos de relacionamento são atributos normais, na qual podem se relacionar com outras entidades e podem receber opções como qualquer outra opção.

Json Ignore

Caso você deseje que determinado atributo não seja adicionado por padrão no json de busca da entidade, basta colocar a opção jsonIgnore no sync do atributo na qual deseja-se que seja omitido.

const Pessoa = new Entity({
    name: 'Pessoa',
    resource: '/pessoas',
    descriptor: {
        name: String,
        idade: Number
    },
    sync: {
        idade: {

            /*
            Ao buscar uma pessoa, o atributo idade não
            existirá
            */
            jsonIgnore: true
        }
        casas: {
            name: 'Casa',
            syncronized: ['pessoas'],

            /*
            Ao buscar uma pessoa, o atributo casas não
            existirá
            */
            jsonIgnore: true
        }
    },
    methods: ['get', 'post', 'put', 'delete', 'patch']
})

Neste exemplo, os atributos idade e casas não serão incluídos no json de busca da entidade Pessoa.

Atenção!!

Por questões de desempenho, somente atributos diretos são garantidos de maneira incondicional a serem ignorados pelo jsonIgnore, ou seja, na entidade Casa, os atributos dentro de endereco não serão ignorados de maneira individual. Os atributos dentro de endereco serão ignorados se o atributo endereco for ignorado. Caso você deseje que um sub-atributo possa ser ignorado de maneira garantida e individual, basta adicionar a opção ignoreFieldsRecursive como false nas opções da entidade.

O jsonIgnore é aplicado na função de preenchimento de entidades explicada posteriormente.

Rotas Personalizadas com Funções Assincronas

Se você deseja criar uma rota personalizada usando funções assincronas, você pode utilizar o método restful.execAsync.

Como exemplo, vamos criar a estrutura de uma rota http get com a URI /rota-personalizada:

app.get('/rota-personalizada',
    restful.execAsync(async function (req, res, next) {
        // ...
        // Executando alguma coisa
        // ...
    })
)

Para mais informações sobre a criação de rotas personalizadas com o Express JS acesse https://expressjs.com/en/starter/basic-routing.html.

Atenção

Independente da função da rota ser assincrona ou sincrona, para passar a execução para a rota seguinte é necessário chamar o método next(). Se a rota retornar o resultado para o cliente, o método next() não deverá ser chamado. Caso ocorra algum erro, pode-se colocar o objeto do erro na chamada da função next da seguinte forma: next(<objeto do erro>). Mais informações podem ser obtidas na documentação de roteamento do Express JS.

Preenchimento Automático de Entidade Relacionada

Se você desejar que por padrão uma entidade relacionada tenha seus atributos buscados e colocados no atributo do relacionamento, basta adicionar a opção fill no sync do atributo da entidade desejada:

const Casa = new Entity({
    name: 'Casa',
    resource: 'casas',
    descriptor: {
        endereco: {
            numero: String,
            rua: String,
            cidade: String,
            estado: String,
            pais: String
        },
        pessoas: [{
            id: mongoose.Schema.Types.ObjectId,
            aluguel: { id: mongoose.Schema.Types.ObjectId }
        }]
    },
    sync: {
        pessoas: {
            name: 'Pessoa',

            /*
            Caso fill seja true, o atributo pessoas
            receberá os valores contidos na entidade
            relacionada
            */
            fill: true,

            sync: { aluguel: 'Aluguel' }
        }
    },
    methods: ['get', 'post', 'put', 'delete', 'patch']
})

Neste caso, ao buscar uma Casa, serão jogados no atributo pessoas todos os atributos existentes dentro de pessoa. Este procedimento é recursivo, ou seja, se Pessoa possuir atributos com fill igual a true, estes atributos de pessoa também serão preenchidos.

Preenchimento Automático em Sub-Atributos

Por questões de performance, um sub-atributo somente pode ser preenchido se o atributo pai tiver a opção fill ou subFill igual a true. Desta forma, se quisermos preencher (em nosso exemplo) somente o atributo aluguel, precisaríamos implementar algo como:

const Casa = new Entity({
    // ...
    sync: {
        pessoas: {
            name: 'Pessoa',

            /*
            Indica que um sub-atributo poderá ser
            preenchido
            */
            subFill: true,

            sync: {
                aluguel: {
                    name: 'Aluguel',

                    /*
                    Preenche o atributo aluguel com os
                    valores presentes na entidade Aluguel
                    */
                    fill: true
                }
            }
        }
    },
    // ...
})

Se além do aluguel quisermos preencher o atributo pessoas, bastaria adicionar a opção fill igual a true, ou substituir a opção subFill pelo atributo fill no sync de pessoas:

const Casa = new Entity({
    // ...
    sync: {
        pessoas: {
            name: 'Pessoa',

            /*
            Também indica que um sub-atributo poderá ser
            preenchido, porém também preenche o atributo
            pessoas
            */
            fill: true,

            sync: {
                aluguel: {
                    name: 'Aluguel',

                    /*
                    Preenche o atributo aluguel com os
                    valores presentes na entidade Aluguel
                    */
                    fill: true
                }
            }
        }
    },
    // ...
})

Se a opção fill é igual a true, o atributo sincronizado irá ser preenchido, mas se além desta opção também está presente a opção subFill igual a false, então o Alpha Restful não irá preencher os sub-atributos dos níveis abaixo.

Uma alternativa ao fill é a opção fillRec. Tal opção contém um número que indica quantos níveis abaixo serão preenchidos com a opção fill igual a true. Se fillRec for um número negativo, o Alpha Restful irá tentar expandir todos os níveis abaixo em todos os sub-atributos da entidade e das sub-entidades. A opção fill possui maior prioridade que a opção fillRec. Se fillRec for negativo, a recursão somente terminará se algum fill abaixo for igual a false ou se não houver mais atributos abaixo para ser preenchidos.

Observação

Da mesma forma, por padrão, sub-atributos somente poderão ser ignorados pelo jsonIgnore se o atributo pai tiver a opção fill ou subFill. Caso você deseje que sub-atributos possam ser ignorados pelo jsonIgnore, independente das opções fill e subFill, basta adicionar a opção ignoreFieldsRecursive como false nas opções da entidade.

Se você desejar que por padrão sub-atributos de sub-entidades também possam ser ignorados pelo jsonIgnore de maneira individual, independente das opções fill e subFill, além de adicionar a opção ignoreFieldsRecursive como false, torna-se necessário também adicionar a opção ignoreFieldsRecursiveSubEntity como false nas opções da entidade.

Evitando Preenchimento Circular

Digamos que desejemos preencher o atributo casas na entidade Pessoa. Para fazer isto poderíamos escrever:

const Pessoa = new Entity({
    name: 'Pessoa',
    resource: '/pessoas',
    descriptor: {
        name: String,
        idade: Number
    },
    sync: {
        casas: {
            name: 'Casa',
            syncronized: ['pessoas'], // Atributo usado pela Casa para se relacionar com Pessoa
            fill: true // Preenche o atributo casas com os valores da casa relacionada
        }
    },
    methods: ['get', 'post', 'put', 'delete', 'patch']
})

Digamos também que desejemos preencher o atributo pessoas na entidade Casa. Neste caso poderíamos também fazer o seguinte:

const Casa = new Entity({
    // ...
    sync: {
        pessoas: {
            name: 'Pessoa',
            fill: true, // Preenche o atributo pessoas com os valores da pessoa relacionada

            sync: {
                aluguel: { name: 'Aluguel' }
            }
        }
    },
    // ...
})

Se executarmos uma pesquisa por Pessoa ou por Casa, nós iremos notar um problema: o sistema irá entrar em recursão infinita, pois ao buscar uma Pessoa, o atributo casas será preenchido pelos atributos de Casa. Por sua vez, nos atributos de Casa são preenchidos os atributos de Pessoa, assim o procedimento segue, gerando um erro de preenchimento circular.

Para evitar que este erro ocorra, existem duas opções que podem ser utilizadas: jsonIgnoreProperties e ignoreFillProperties.

O jsonIgnoreProperties contém uma lista de nomes (ou apenas uma String com o nome desejado) de atributos que não serão incluídos dentro do json depois da recursão na qual esta opção está inserida.

O ignoreFillProperties contém uma lista de nomes (ou apenas uma String com o nome desejado ) de atributos que não serão preenchidos depois da recursão na qual esta opção está inserida.

const Pessoa = new Entity({
    // ...
    sync: {
        casas: {
            name: 'Casa',
            syncronized: ['pessoas'],
            fill: true,

            /*
            Todos os atributos abaixo desta recursão com
            o nome 'pessoas' não serão preenchidos
            */
            ignoreFillProperties: ['pessoas']
        }
    },
    // ...
})

const Casa = new Entity({
    // ...
    sync: {
        pessoas: {
            name: 'Pessoa',
            fill: true,

            /*
            Todos os atributos abaixo desta recursão com
            o nome 'casas' não serão incluídos dentro do
            json
            */
            ignoreJsonProperties: ['casas'],

            sync: {
                aluguel: { name: 'Aluguel' }
            }
        }
    },
    // ...
})

No código exemplo apresentado, ao buscar uma Pessoa, o atributo pessoas na entidade Casa não será preenchido. Ao buscar uma Casa, o atributo casas na entidade Pessoa não será incluído no json.

Opção de Dependência em Relacionamento

Digamos que uma pessoa não possa ser removida se houver um relacionamento desta pessoa com uma Casa. Neste caso basta informar que o relacionamento de Pessoa com Casa é um relacionamento de dependência. Para isto basta informar a opção required com valor true:

const Casa = new Entity({
    // ...
    sync: {
        pessoas: {
            name: 'Pessoa',
            fill: true,
            ignoreJsonProperties: ['casas'],

            /*
            As pessoas armazenadas no atributo 'pessoas'
            não poderão ser removidas enquando estiverem
            dentro deste relacionamento
            */
            required: true,

            sync: {
                aluguel: { name: 'Aluguel' }
            }
        }
    },
    // ...
})

Opção de Remoção em Cascata

Digamos que ao remover uma Casa, todas as pessoas relacionadas com esta Casa devam ser removida também de maneira automática. Neste caso basta colocar a opção deleteCascade igual a true.

const Pessoa = new Entity({
    // ...
    sync: {
        casas: {
            name: 'Casa',
            syncronized: ['pessoas'],
            fill: true,
            ignoreFillProperties: ['pessoas'],

            /*
            Se uma casa relacionada for removida, a
            pessoa relacionada será removida também
            */
            deleteCascade: true
        }
    },
    // ...
})

Rota Padrão de Busca

Caso seja habilitada a criação de CRUD, habilitando a geração de método http get, automaticamente é gerada uma rota de busca na qual buscas complexas podem ser realizadas alterando apenas os parâmetros da rota.

Como exemplo de busca, imagine que deseja-se buscar todas as casas, na qual existe pelo menos uma pessoa que mora em alguma Casa, que nesta Casa existe uma pessoa que possui idade maior ou igual a 18 anos. Para se realizar esta pesquisa bastaria fazer uma requisição http get com a seguinte url:

/casas?pessoas.casas.pessoas.idade__$gte=18

Nesta rota de busca pode-se realizar pesquisas em atributos e sub-atributos da entidade e de sub-entidades relacionadas.

Você também pode adicionar várias condições. Neste caso, você poderia deixar esta busca ainda mais específica, exigindo que todas as casas buscadas precisa-se estar na rua "Castelo". Neste caso bastaria realizar a seguinte requisição http get:

/casas?pessoas.casas.pessoas.idade__$gte=18&endereco.rua=Castelo

Segue uma tabela com todas as opções de busca nesta rota de pesquisa gerada automaticamente:

Filtro | Condição | Exemplo | Descrição ------------------- | ----------- | ----------------------------- | -------------------------- Igual | __$eq | /pessoas?nome=Emanuel ou /pessoas?nome__$eq=Emanuel | Busca todas as pessoas com o nome igual a Emanuel Diferente | __$ne | /pessoas?nome__$ne=Emanuel | Busca todas as pessoas na qual o nome é diferente de Emanuel Maior que | __$gt | /pessoas?idade__$gt=18 | Busca todas as pessoas com idade maior que 18 Maior ou Igual | __$gte | /pessoas?idade__$gte=18 | Busca todas as pessoas com idade maior ou igual a 18 Menor que | __$lt | /pessoas?idade__$lt=18 | Busca todas as pessoas com idade menor que 18 Menor ou Igual | __$lte | /pessoas?idade__$lte=18 | Busca todas as pessoas com idade menor ou igual a 18 Está Em | __$in | /pessoas?idade__$in=18,19 | Busca todas as pessoas com idade igual a 18 ou 19 anos Não Está Em | __$nin | /pessoas?idade__$nin=18,19 | Busca todas as pessoas na qual a idade não é 18 nem 19 anos Expressão Regular | __regex | /pessoas?nome__regex=/^Em/i | Busca todas as pessoas na qual o nome começa com "Em" (Case Insensitive) Negação da Expressão Regular | __$not_regex | /pessoas?nome__$not_regex=/^Em/i | Busca todas as pessoas na qual o nome não começa com "Em" (Case Insensitive) Limite | limit | /pessoas?limit=10 | Busca as pessoas com um limite de 10 pessoas Pular | skip | /pessoas?skip=20 | Busca todas as pessoas pulando as 20 primeiras pessoas da pesquisa Ordenar Crescente | order | /pessoas?order=name | Busca todas as pessoas ordenando de maneira crescente por nome Ordenar Decrescente | order | /pessoas?order=-name | Busca todas as pessoas ordenando de maneira decrescente por nome Quantidade | selectCount | /pessoas?selectCount=true | Busca a quantidade de pessoas registradas no banco de dados Selecionar | select | /pessoas?select=name,idade | Busca todas as pessoas selecionando o nome e a idade

Método de Busca

Digamos que você deseje realizar uma busca em uma rota personalizada, utilizando um poder ainda maior do que as opções disponíveis anteriormente. Neste caso, basta você chamar o método restful.query(<condições>, <Entidade>, <opções>).

Por exemplo: digamos que você deseje realizar uma pesquisa de todas as casas, na qual existe pelo menos uma pessoa que mora em alguma Casa, que nesta Casa existe uma pessoa que possui idade maior ou igual a 18 anos. Para realizar esta pesquisa basta realizar a seguinte chamada de método:

let casas = await restful.query({
    'pessoas.casas.pessoas.idade': {
        $gte: 18
    }
}, Casa)

O primeiro argumento do método de busca contém as especificações do objeto de busca usado pelo Mongoose, com o diferencial de poder utilizar atributos de sub-entidades de sub-entidades, como se elas estivessem dentro do mesmo documento.

O terceiro argumento é opcional e é um objeto com várias opções para a pesquisa. As opções deste Objeto são:

Opção | Valor Padrão | Descrição ----------------- | :----------: | ----------- select | null | Atributos a serem buscados skip | null | Quantidade de elementos a serem pulados limit | null | Quantidade máxima de elementos da busca sort | null | Atributo a ser ordenado internalSearch | true | Se for false retorna um objeto de busca que pode ser utilizado como primeiro argumento do método de busca do Mongoose (Entidade.model.find). Se for true retorna-se o resultado da busca selectCount | false | Se for true retorna a quantidade de elementos da busca. Se for false retorna os elementos da busca. isCopyEntity | false | Se for false não realiza a cópia das entidades buscadas. Neste caso, os atributos das entidades retornadas são imutáveis. Se for true as entidades retornadas são uma cópia das originais. Neste caso pode-se alterar os valores de seus atributos. As cópias das entidades não possuem os métodos utilizados pelo mongoose. findOne | false | Se for true, apenas uma entidade é buscada e retornada. descriptor | null | Objeto que descreve a modelagem da entidade. Caso seja null, o descriptor utilizado é o descriptor definido na modelagem da entidade.

Atenção!

O método restful.query utiliza as definições presentes do descriptor das entidades para mapear as possibilizades de busca, inclusive com os atributos presentes nas sub-entidades. Por causa deste comportamento, todos os atributos devem ser mapeados no descriptor para serem localizados pelo restful.query, porém existem situações na qual um atributo é do tipo Object ou Array e seus sub-atributos são dinâmicos e impossíveis de serem mapeados. Neste caso, no objeto sync deste atributo deve ser habilitado a opção dynamicData igual a true. Com esta opção habilitada, o método restful.query não irá procurar sub-atributos na entidade relacionada pelo atributo dinâmico.

Observação

O método restful.query irá internamente chamar o método de busca do Mongoose (Entidade.model.find), portanto a sintaxe presente no objeto de busca do Mongoose é preservada pelo método restful.query, com a diferença de que através do método restful.query, os atributos das entidades relacionadas são enxergadas pelo método de busca, como se eles já estivessem dentro do documento principal.

Para mais informações sobre o método de busca do mongose Acesse.

Atenção!

Ao realizar uma pesquisa no método restful.query, os atributos com a opção jsonIgnore igual a true não serão ignorados. Para que estes atributos sejam ignorados é necessário que o método de preenchimento (explicado logo a seguir) seja chamado.

Método de Preenchimento

Caso você deseja preencher os atributos com relacionamentos, basta chamar o método Entidade.fill(<dados>, restful, <opções>).

Por exemplo, vamos imaginar que você deseje que uma Pessoa tenha o atributo nome ignorado no json e que o atributo casas seja preenchido independente do valor contido no sync da modelagem, mas que isto tenha efeito em apenas uma rota especifica. Para isto bastaria fazer a seguinte chamada de código:

pessoas = await Pessoa.fill(pessoas, restful, {
    sync: {
        nome: { jsonIgnore: true },
        casas: { fill: true }
    }
})

O primeiro argumento do método fill é uma pessoa ou uma lista de pessoas que terão seus atributos expandidos. O segundo argumento é a instância do Alpha Restful. O último argumento é um objeto opcional contendo opções para o preenchimento.

Neste exemplo, apenas para esta chamada, você está sobrescrevendo o comportamento padrão do sync. Dentre as opções do ultimo argumento, você pode passar a opção syncs, opção esta que é um objeto na qual a chave do objeto é o nome da entidade e o valor é o sync da entidade que substituirá o sync padrão definido na modelagem. Como exemplo disto nós temos:

pessoas = await Pessoa.fill(pessoas, restful, {
    sync: {
        nome: { jsonIgnore: true },
        casas: { fill: true }
    },
    syncs: {
        Casa: {
            pessoas: { jsonIgnore: true }
        }
    }
})

Neste exemplo, independente do que foi definido na modelagem, apenas para esta chamada, as pessoas terão seu nome removidos do json, o atributo casas será preenchido, mas o atributo pessoas dentro da entidade Casa não será incluído dentro do atributo casas em Pessoa.

As opção passadas no objeto do último argumento são:

Opção | Valor Padrão | Descrição -------------------- | :----------: | ---------- sync | null | Objeto sync a ser usado ao invés do sync definido na modelagem. Se for null usa-se o sync da modelagem syncs | {} | Objeto cuja a chave é o nome da entidade e o valor é o sync a ser usado ao invés do sync definido na modelagem de tal entidade. A entidade que não estiver dentro desta opção terá o sync definido em sua modelagem usado. ignoreFillProperties | [] | Lista de propriedade que não serão preenchidas em qualquer nível. jsonIgnoreProperties | [] | Lista de propriedade que não serão incluídas em qualquer nível.

Atenção!

A opção sync do último argumento do método Entidade.fill apenas é aplicada ao primeiro nível, ou seja, no exemplo apresentado, se houver alguma sub-entidade que se relaciona com Pessoa, o sync de Pessoa utilizado será o objeto sync definido na modelagem da entidade.

Por causa disso, no exemplo apresentado, o atributo pessoas em Casa, se não estivesse sendo ignorado, utilizaria o objeto sync definido na modelagem da entidade. Para sobrescrever o sync em todas as chamadas e sub-chamadas de todas as sub-entidades de sub-entidades, seria necessário sobrescrever também na opção syncs:

pessoas = await Pessoa.fill(pessoas, restful, {
    sync: {
        nome: { jsonIgnore: true },
        casas: { fill: true }
    },
    syncs: {
        Casa: {
            pessoas: { jsonIgnore: true }
        },
        Pessoa: {
            nome: { jsonIgnore: true },
            casas: { fill: true }
        }
    }
})

Observação

Por padrão, ao preencher um atributo com a entidade relacionada, todos os valores deste atributo serão preenchidos e estarão dentro do json, mas pode-se ordenar e paginar os resultandos dentro do preenchimento, utilizando as opções limit (quantidade máxima de elementos), skip (quantidade de elementos a serem pulados) e sort (atributo a ser ordenado de maneira crescente ou decrescente (com o hífen no início)).

A opção sort irá ordenar os elementos de dentro do atributo pelo atributo passado nesta opção. Por padrão, apenas atributos armazenados dentro do documento principal poderão ser utilizados pela ordenação. Caso seja necessário ordenar por algum atributo presente na entidade relacionada, é necessário habilitar como true a opção ignoreSubAttr, que irá excluir do json os atributos de relacionamento presentes dentro do documento principal.

Caso a opção ignoreSubAttr seja true, pode-se utilizar a opção find dentro das opções de preenchimento. A opção find permite que seja definido uma especificação de pesquisa, para que apenas as subentidades que se encaixar nestas condições sejam incluídas dentro do json. O valor de find pode ser um objeto literal de busca (objeto a ser utilizado no primeiro argumento do método restful.query), assim como pode ser também uma função que retorna este objeto literal. Para este último caso, o primeiro argumento da função é a instância da entidade que terá seus atributos preenchidos e o segundo argumento da função é um array com todos os valores presentes no documento principal dentro do atributo a ser preenchido.

Também é possível selecionar quais atributos diretos (primeiro nível) estarão contidos dentro do json com a opção select, que pode ser um array com todos os atributos a serem adicionados no json, mas pode ser também uma string, separando os atributos a serem selecionados com espaço.

Forma Alternativa Para Integrar Preenchimento em Rotas Personalizadas

O método de preenchimento Entidade.fill pode ser chamado dentro de uma rota personalizada para preencher os atributos com os valores contidos nas entidades relacionadas por eles. Esse método pode ser chamado explicitamente, mas também pode ser chamado de maneira alternativa como uma opção ao método restful.execAsync:

app.get('/rota-personalizada',
    restful.execAsync(async function (req, res, next) {
        // ...
        // Código da rota personalizada
        // ...

        res._content_ = casas
        next()

    }, Casa.afterGetFill(restful), 200)
)

Sync Dinâmico

Especificamente para a função de preenchimento, pode ser definido um objeto sync dinâmico! Para fazer isto, basta ao invés de passar um objeto, passar uma função, que recebe a entidade na qual se deseja preencher como argumento e retorna o sync a ser utilizado. Isto se torna muito poderoso para os relacionamentos virtuais que serão vistos posteriormente!

Como exemplo disto, imaginemos que somente as pessoas que não tiverem seu nome iniciados com "Emanuel" devem ter seus nomes ignorados no json de resposta. Para fazer isto basta digitar o seguinte código:

pessoas = await Pessoa.fill(pessoas, restful, {
    sync: pessoa => ({
        nome: { jsonIgnore: !pessoa.nome.startsWith('Emanuel') },
        casas: { fill: true }
    }),
    syncs: {
        Casa: {
            pessoas: { jsonIgnore: true }
        }
    }
})

Opções de Remoção de Entidades

Caso você deseje excluir uma entidade manualmente via linha de código e ainda manter o comportamento definido nos objetos sync para as opções required, deleteCascade e garantir que não existirá ids em relacionamentos apontando para entidades que já não existem mais, basta chamar o método restful.deleteSync(<id>, <nome da entidade>, <syncronized>).

Como exemplo, o código que garantirá este comportamento na remoção de uma pessoa seria:

await restful.deleteSync(pessoa._id, 'Pessoa', Pessoa.syncronized)

O primeiro argumento é o id da pessoa a ser removida. O segundo argumento é uma String contendo o nome da entidade e o terceiro argumento é um objeto gerado automaticamente pelo Alpha Restful, contendo todos os relacionamentos que outras entidades possuem com Pessoa.

Este método NÃO irá remover a pessoa. Este método irá manter o comportamento padrão que o Alpha Restful aplica em uma entidade antes dela ser removida.

Após a chamada deste método, pode-se remover a entidade usando as ferramentas do Mongoose ou usando as ferramentas disponibilizadas pelo MongoDB.

Como exemplo de um método disponibilizado para remoção de entidades no Mongoose temos:

await pessoa.remove()

Manipulação da Entidade Pelo Moongose e pelo MongoDB

Caso você dejese salvar, buscar, editar ou remover uma entidade, pode-se utilizar os métodos disponíveis pelo Mongoose ou pelo MongoDB. O schema da entidade descrita pela documentação do Mongoose pode ser obtido através da opção Entidade.schema. O model da entidade descrita pela documentação do Moongose pode ser obtido através da opção Entidade.model. Caso você busque uma entidade pelo método restful.query e deseje ter acesso aos métodos do objeto disponibilizados pelo Mongoose, basta desabilitar a opçao isCopyEntity, assim como é explicado na seção Método de Busca deste documento.

Para mais informações sobre os métodos disponibilizados pelo Mongoose para manipulação de entidades acesse a documentação pelo link https://mongoosejs.com/docs/guides.html.

Relacionamento Virtual

Um relacionamento virtual é um relacionamento que existe entre uma entidade com outra entidade por intermédio de uma definição de pesquisa estática.

Digamos que toda vez que buscarmos uma pessoa, gostaríamos de obter dentro da pessoa um atributo contendo a quantidade de casas registradas no sistema. Para isto, podemos fazer um relacionamento virtual.

const Pessoa = new Entity({
    name: 'Pessoa',
    resource: '/pessoas',
    descriptor: {
        name: String,
        idade: Number
    },
    sync: {
        casas: {
            name: 'Casa',
            syncronized: ['pessoas'],
            fill: true,
            ignoreFillProperties: ['pessoas']
        },

        /*
        Definindo atributo que se relaciona com
        Casa atraves da especificação de uma
        pesquisa
        */
        quantidadeCasas: {
            name: 'Casa',
            virtual: true,
            find: {},
            selectCount: true
        }
    },
    methods: ['get', 'post', 'put', 'delete', 'patch']
})

Neste exemplo, toda vez que uma pessoa for buscada, o atributo quantidadeCasas será preenchido com a quantidade de casas registrado dentro do sistema. Se a opção find for um objeto vazio ou se não for definido, será buscado todos os elementos registrados na entidade relacionada.

A opção find contém um objeto de pesquisa que será utilizada no primeiro argumento da função restful.query. Além desta opção, estão disponíveis as mesmas opções presentes no objeto do último argumento da função restful.query.

Relacionamento Virtual Dinâmico

Digamos que desejamos criar uma entidade chamada de Familia, que contém um atributo chamado de sobrenome.

const Familia = new Entity({
    name: 'familia',
    resource: 'familias',
    descriptor: {
        sobrenome: String
    },
    methods: ['get', 'post', 'put', 'delete', 'patch']
})

restful.add(Familia)

Digamos ainda que ao fazer uma pesquisa por esta entidade, desejamos que seja retornado uma lista de todas as pessoas na qual seu nome termine com este sobrenome.

Para fazermos isto podemos utilizar o relacionamento virtual dinâmico, ou seja, haverá um relacionamento lógico, na qual uma entidade estará relacionada com outras entidades por intermédio de uma especificação de busca.

Atualmente, o relacionamento virtual dinâmico não está disponível na modelagem da entidade. Porém ele está disponível na chamada do método de preenchimento. Desta forma, podemos criar uma rota personalizada que chama o método de preenchimento e define nesta chamada o relacionamento virtual dinâmico:

familias = await Familia.fill(familias, restful, {
    sync: familia => ({
        pessoas: {
            name: 'Pessoa',
            virtual: true,
            find: {
                'nome': new RegExp(`${familia.sobrenome}$`)
            }
        }
    })
})

O Código anterior exemplifica o uso do relacionamento virtual dinâmico. O método Familia.fill irá preencher o atributo pessoas com a entidade relacionada Pessoa. Porém, apenas as pessoas na qual o nome termina com o sobrenome da família em questão, serão usados para preencher o atributo pessoas. Para realizar a verificação de se o nome da pessoa termina com o sobrenome da família, utilizou-se uma expressão regular.

Ao final deste código, cada família terá uma lista com todas as pessoas na qual o seu nome termina com o sobrenome da família em questão. Este código, ao ser colocado em uma rota personalizada, ou em uma projeção (explicada mais a frente) ou em um handler (explicado mais a frente), garantirá que as pessoas sejam separas por famílias, baseada em uma especificação lógica de busca, sem que haja a necessidade (a não ser por questões de performance) de armazenar estes dados no banco de dados.

Projeção

Digamos que em determinadas situações, uma pesquisa qualquer deva ser retornada de uma maneira diferente, seja removendo atributos, seja adicionando outros atributos, seja modificando completamente o resultado da pesquisa. Isto pode ser feito através do uso de projeções.

As projeções podem ser definidas como array, objeto ou função.

Projeção Definida como Array

Digamos que em determinadas situações, independente da rota utilizada, uma pessoa somente deverá retornar os atributos nome e idade. Neste caso podemos criar uma projeção, por exemplo com o nome de projecao-base.

const Pessoa = new Entity({
    //...
    projections: {
        'projecao-base': ['nome', 'idade']
    }
})

Para chamar esta projeção em uma requisição http basta adicionar o atributo projection=projecao-base em alguma rota já existente de busca.

Por exemplo, se quisermos chamar esta projeção para a rota de busca de todas as pessoas, nós podemos fazer a seguinte requisição:

/pessoas?projection=projecao-base

Projeção Definida como Objeto

Ainda continuando nossos exemplos de projeção, se quisermos que nossa projeção projecao-base retorne APENAS pessoas contendo um (pode ter mais de um) atributo, sendo este atributo uma concatenação do nome com a idade da pessoa, basta fazer o seguinte:

const Pessoa = new Entity({
    //...
    projections: {
        'projecao-base': {
            nomeComIdade: pessoa => `${pessoa.nome} ${pessoa.idade}`
        }
    }
})

Projeção Definida Como Função

E se quisermos que nossa projeção projecao-base retorne, além do atributo nomeComIdade, os outros atributos também? Neste caso basta que nossa projeção seja uma função (sincrona ou assincrona) que adiciona o atributo nomeComIdade:

const Pessoa = new Entity({
    //...
    projections: {
        'projecao-base': async function (pessoa, resolve, reject) {
            try {
              pessoa.nomeComIdade=`${pessoa.nome} ${pessoa.idade}`
              resolve(pessoa)
            } catch (err) {
              reject(err)
            }
        }
    }
})

Independente de nossa projeção ser assincrona ou sincrona, o método resolve deve ser chamado, sendo o argumento o objeto que será retornado pela rota. Em caso de erro, pode-se chamar o método reject, passando como argumento o objeto do erro.

Projeção Padrão

E se quisermos que nossa projeção projecao-base seja executada automaticamente sem precisarmos passar projection=projecao-base em nossas requisições http? Para isto basta definir na entidade a opção projectionDefault:

const Pessoa = new Entity({
    //...
    projections: {
        'projecao-base': async function (pessoa, resolve) {
            pessoa.nomeComIdade=`${pessoa.nome} ${pessoa.idade}`
            resolve(pessoa)
        }
    },
    projectionDefault: 'projecao-base'
})

Integrando as Projeções com Nossas Rotas Personalizadas

E se quisermos que nossas rotas personalizadas também usem nossas projeções? Neste caso basta adicionar dois novos argumentos no utilitário restful.execAsync e adicionar aquilo que seria enviado no atributo res._content_:

app.get('/rota-personalizada',
    restful.execAsync(async function (req, res, next) {
        // ...
        // Código da rota personalizada
        // ...

        res._content_ = pessoas
        next()

    }, Pessoa.afterGetProjections(restful), 200)
)
Observação

Para que o código anterior funcione, é necessário que no código de sua rota personalizada não seja enviado nenhum dado como resposta. Aquilo que seria enviado pela rota como resposta deve ser adicionado na variavel res._content_, que o Alpha Restful se encarregará de enviar seu conteúdo.

No último argumento do restful.execAsync encontra-se o status code de resposta do http. Caso você deseje, ao invés de colocar no ultimo argumento o status code, você pode passar outra função que se encarregará de enviar o conteúdo presente no res._content_.

Aplicando Projeções em um Objeto Qualquer

Se você deseja aplicar uma projeção de uma entidade em um objeto qualquer, basta você chamar o método <Entidade>.applyProjections(<content>, <projectionName>, restful). Isto é muito útil para aplicar uma projeção em um atributo estando dentro da implementação de outra projeção.

Como exemplo disto, vamos criar uma projeção da entidade Casa, na qual tal projeção simplesmente aplica a projeção projecao-base de Pessoa no atributo pessoas:

const Casa = new Entity({
    // ...
    projections: {
        'projecao-casa': async function (casa, resolve) {

            /*
            Dentro de uma projeção é altamente recomendável
            obter a entidade desejada por meio do atributo
            restful.entities
            */
            const Pessoa = restful.entities.Pessoa

            casa.pessoas = await Pessoa.applyProjections(
                casa.pessoas, 'projecao-base', restful
            )

            resolve(casa)
        }
    }
})

Handlers

Por fim, vamos falar sobre os handlers! Um handler basicamente é uma função que será executada em algum momento específico. Por padrão os handlers são executados antes ou depois de cada rota CRUD http padrão gerada pelo Alpha Restful. Mas você pode integrar os handlers as suas rotas personalizadas.

Aqui está a lista de handlers disponíveis:

Handler | Descrição -------------- | ------------- beforeQuery (req, res, next) | Executado antes de aplicar uma rota get afterQuery (entity, req, res, next) | Executado após a aplicação de uma rota get beforeCreate (entity, req, res, next) | Executado antes da aplicação de uma rota post afterCreate (entity, req, res, next) | Executado após a aplicação de uma rota post beforeRemove (entity, req, res, next) | Executado antes da aplicação de uma rota delete afterRemove (entity, req, res, next) | Executado após a aplicação de uma rota delete beforeEdit (entity, req, res, next) | Executado antes da aplicação de uma rota put ou patch afterEdit (entity, req, res, next) | Executado após a aplicação de uma rota put ou patch

Apenas o handler beforeCreate não recebe o conteúdo da entidade (ou entidades) como primeiro argumento. Nos demais handlers, o primeiro argumento é o conteúdo que está sendo manipulado. Este conteúdo também pode ser acessado pelo res._content_.

Atenção

Independente do handler ser uma função assincrona ou sincrona, o método next() deverá ser chamado ao final da execução do handler. Caso algum erro ocorra, pode-se chamar o método next passando como argumento o objeto do erro.

Implementando um handler

Para implementar um handler basta adicionar um método na entidade com o nome do handler desejado. Como exemplo vamos implementar o handler afterQuery que apenas irá printar a lista de pessoas antes de retornar na requisição.

Pessoa.afterQuery = async function (pessoas, req, res, next) {
    console.log(pessoas)
    next()
}

Caso ocorra algum erro dentro do handler, basta passar o objeto do erro na função next.

Integrando os Handlers a suas Rotas Personalizadas

No utilitário restful.execAsync, você deverá chamar antes ou depois de sua(s) rota(s) personalizada(s) os handlers desejados, sempre lembrando que o Alpha Restful irá colocar como primeiro argumento de todos os handlers (com exceção do handler beforeCreate) o conteúdo da variável res._content_.

Para obter um handler, basta chamar a função Entidade.getRouteHandler(<nome-do-handler>). O argumento da função getRouteHandler é o nome do handler.

Para exemplificar esta integração, definiremos uma rota personalizada que utilizará os handlers beforeEdit e afterEdit.

app.put('/edit-pessoa',
    restful.execAsync(
        async function (req, res, next) {
            // ... faz alguma coisa
            // ... busca a pessoa
            // ... faz outra coisa

            res._content_ = pessoa
            next()
        },
        Pessoa.getRouteHandler('beforeEdit'),
        async function (req, res, next) {
            // ... faz alguma coisa
            // ... edita a pessoa
            // ... faz outra coisa
            next()
        },
        Pessoa.getRouteHandler('afterEdit'),
        200
    )
)

Neste exemplo nós chamados o handler beforeEdit e o handler afterEdit, mas poderíamos chamar somente um deles ou nenhum deles. O último argumento é o status code http que será retornado caso tudo dê certo.

Por fim, se quisessemos integrar nossa rota personalizada ao beforeEdit, ao afterEdit e as nossas projeções explicadas anteriormente, poderíamos fazer o seguinte:

app.put('/edit-pessoa',
    restful.execAsync(
        async function (req, res, next) {
            // ... faz alguma coisa
            // ... busca a pessoa
            // ... faz outra coisa

            res._content_ = pessoa
            next()
        },
        Pessoa.getRouteHandler('beforeEdit'),
        async function (req, res, next) {
            // ... faz alguma coisa
            // ... edita a pessoa
            // ... faz outra coisa
            next()
        },
        Pessoa.afterGetProjections(restful),
        Pessoa.getRouteHandler('afterEdit'),
        200
    )
)

Veja que aqui optamos por aplicar as projeções antes de nosso handler afterEdit, mas tranquilamente poderíamos alterar a ordem na qual essa chamadas são realizadas.

Algumas Informações Importantes

O guia aqui presente engloga a grande maioria das funcionalidades implementadas, porém ainda existem alguns detalhes não especificados aqui. Em breve atualizarei este guia colocando nele mais informações.

Sinta-se a vontade de testar as funcionalidades aqui apresentadas e em caso de algum erro você poderá relatar aqui nas Issues que eu tentarei resolver o mais rápido possível.

Este software ainda não está 100% pronto. Existem alguns detalhes importantes a serem tratados e testes mais severos a serem realizados.

Sinta-se livre para sugerir qualquer mudança no framework ou para realizar qualquer sugestão de atualização de seu código fonte.