@simplus/facades-scopes

Add scopes to facades for better data handling

Usage no npm install needed!

<script type="module">
  import simplusFacadesScopes from 'https://cdn.skypack.dev/@simplus/facades-scopes';
</script>

README

Facade scopes

Facades scopes allow to control what parameters are created, updated and fetched from the database using facades.

Install

npm install @simplus/facades-scopes

How does it work ?

The library only provides a decorator called collectionScopes, the decorator adds a method to facadae classes.

the decorator receives a set of object associated to their names. I will show how the library works through a real example.

Let's imagine we have a database full of users, and these users have different types of roles. (one user is an admin, an other user is normal user, and the last one is an anonymous user).

we will create three different type of scopes: public (for everyone), user (for authenticated users) and admin (for admins)

this is how our Schema looks alik:

const schema = {
    _id : { dataType : String},
    name: { dataType : String},
    email: { dataType : String},
    blacklisted: { dataType : Boolean},
    password: { dataType : String},
}

let's start with a simple configuration:

const scopes = {
    public: ['_id', 'name'], // pulbic can only see _id and name
    user : ['_id', 'name', 'email'], // ...
    admin : ['_id', 'name', 'email', 'blacklisted'] // ...
}

We will now configure out Facade

@collectionScopes(scopes)
export class UserFacade extends SQLCollection<User> {
}


export userFacade = new UserFacade({...})

That's it, now the only thing to do is to fetch the right scope during a request

// Fetch list of users
app.get('/users/', (req, res, next) => {
    let scope = 'public'
    if(req.user) // user is logged in
        scope = 'user'
    if(req.user.role === 'admin')
        scope = 'admin'
    userFacade.scope(scope).find({}).then( ... )
})
// Fetch one user
app.get('/users/:id', (req, res, next) => {
    let scope = 'public'
    if(req.user) // user is logged in
        scope = 'user'
    if(req.user.role === 'admin')
        scope = 'admin'
    if(req.user.id === req.params.id)
        scope = null
    userFacade.scope(scope).findById(req.params.id).then( ... )
})

Let's now imagine we want to make a specific requeqt with a specific scope (let's imagine someone has a token to access all the information of an user)

app.get('/users/:id/special-request/:token', (req, res, next) => {
    if(!isValid(req.params.token))
        return next(new Error())
    let scope = parseToken(req.params.token) // for example ['email', 'password']
    userFacade.scope(scope).findById(req.params.id).then( ... )
})

The next item you might want to do in some cases is blacklist items instead of white list them, that is the second parameter of the scope function

app.get('/users/:id/everything-but-password', (req, res, next) => {
    userFacade.scope(null, ['password']).findById(req.params.id).then( ... )
})

What about creation and updates ?

it works the same way, only you limit the input instead of the output

app.put('/users/:id/change-password', (req, res, next) => {
    if(req.params.id !== req.user.id)
        return next(new Error())
    userFacade.scope(['password']).updateById(req.params.id, req.body).then( ... )
})

and it works the same for updates.

Things you should know

  • It works with deep nested objects too my.deep.nested.value
  • all functionalities are kept from the facade you inherit from and even if you add you own functionalities they will still work (but there will be no scope applied yet)