Repository: fastify/fastify (JavaScript)

Issue:
[Bug] Registering multiple named instances of this module fail.
### Prerequisites

- [X] I have written a descriptive issue title
- [X] I have searched existing issues to ensure the bug has not already been reported


### Fastify version

3.21.2

### Plugin version

3.6.0

### Node.js version

14.17.6

### Operating system

Linux

### Operating system version (i.e. 20.04, 11.3, 10)

ArchLinux (kernel 5.14.5-arch1-1)

### Description

Since https://github.com/fastify/fastify/pull/3317 was merged in Fastify it's impossible to register multiple named instances of the package.

This error is thrown at the start:
```
[...]/node_modules/fastify/lib/decorate.js:40
    throw new FST_ERR_DEC_ALREADY_PRESENT(name)
          ^
FastifyError [Error]: The decorator 'pg' has already been added! 
[...]
```
I am currently investigating the code to clearly identify the faulty part and submit a fix PR.
I suspect that this is due to https://github.com/fastify/fastify-postgres/pull/76 as the code rely on a mutable request.pg object (https://www.fastify.io/docs/latest/Decorators/#decoraterequestname-value-dependencies).

I think we should adopt the same request decoration strategy as the one used for `fastify-jwt` (https://github.com/fastify/fastify-jwt/pull/175).

WDYT can I go for a fix PR adopting this decoration strategy knowing that this will be a breaking change ?


### Steps to Reproduce

```js
'use strict';

const fastify = require('fastify');

const server = fastify();

server.register(require('fastify-postgres'), {
  connectionString: 'postgres://postgres:postgres@localhost/postgres',
  name: 'one'
})
server.register(require('fastify-postgres'), {
  connectionString: 'postgres://postgres:postgres@localhost/postgres',
  name: 'two'
})

server.listen(3000);
```

### Expected Behavior

It should start.


Discussion:
It should be #3317 breaks the function `fastify.hasRequestDecorator` and `fastify.hasReplyDecorator`

I will ship it to `fastify`.
I have tested if `fastify-jwt` crashes too when registering 2 named instances and it run just like it should.
I even have a fix here https://github.com/darkgl0w/fastify-postgres/tree/fix-decorator-bug for this package if you want to look at it before I submit a pull request.
> 
> 
> I have tested if `fastify-jwt` crashes too when registering 2 named instances and it run just like it should.
> I even have a fix here https://github.com/darkgl0w/fastify-postgres/tree/fix-decorator-bug for this package if you want to look at it before I submit a pull request.

As I mention, it is a bug from `fastify`. The fix should be inside the main repo.
@climba03003 do you understand why this is happening? Could you send a PR to fix? I can ship a release if the code is there.
ok, that's nice if you are already working on a fix ^^
Ping me if you need some help :)

PS: sorry I read your previous message too fast and missed the point of it :d
Fixed in v3.21.3.



Patch:
diff --git a/lib/decorate.js b/lib/decorate.js
index 41a79b608a6..1297d5b1327 100644
--- a/lib/decorate.js
+++ b/lib/decorate.js
@@ -75,10 +75,12 @@ function checkExistence (instance, name) {
 }
 
 function checkRequestExistence (name) {
+  if (name && this[kRequest].props.includes(name)) return true
   return checkExistence(this[kRequest].prototype, name)
 }
 
 function checkReplyExistence (name) {
+  if (name && this[kReply].props.includes(name)) return true
   return checkExistence(this[kReply].prototype, name)
 }
 


Test patch:
diff --git a/test/decorator.test.js b/test/decorator.test.js
index 6a613fffeb1..e8c10c92c06 100644
--- a/test/decorator.test.js
+++ b/test/decorator.test.js
@@ -17,6 +17,15 @@ test('server methods should exist', t => {
   t.ok(fastify.hasDecorator)
 })
 
+test('should check if the given decoration already exist when null', t => {
+  t.plan(1)
+  const fastify = Fastify()
+  fastify.decorate('null', null)
+  fastify.ready(() => {
+    t.ok(fastify.hasDecorator('null'))
+  })
+})
+
 test('server methods should be encapsulated via .register', t => {
   t.plan(2)
   const fastify = Fastify()
@@ -425,6 +434,15 @@ test('hasRequestDecorator', t => {
     t.ok(fastify.hasRequestDecorator(requestDecoratorName))
   })
 
+  t.test('should check if the given request decoration already exist when null', t => {
+    t.plan(2)
+    const fastify = Fastify()
+
+    t.notOk(fastify.hasRequestDecorator(requestDecoratorName))
+    fastify.decorateRequest(requestDecoratorName, null)
+    t.ok(fastify.hasRequestDecorator(requestDecoratorName))
+  })
+
   t.test('should be plugin encapsulable', t => {
     t.plan(4)
     const fastify = Fastify()
@@ -481,6 +499,15 @@ test('hasReplyDecorator', t => {
     t.ok(fastify.hasReplyDecorator(replyDecoratorName))
   })
 
+  t.test('should check if the given reply decoration already exist when null', t => {
+    t.plan(2)
+    const fastify = Fastify()
+
+    t.notOk(fastify.hasReplyDecorator(replyDecoratorName))
+    fastify.decorateReply(replyDecoratorName, null)
+    t.ok(fastify.hasReplyDecorator(replyDecoratorName))
+  })
+
   t.test('should be plugin encapsulable', t => {
     t.plan(4)
     const fastify = Fastify()
diff --git a/test/same-shape.test.js b/test/same-shape.test.js
index 5e49081e32c..3157c71eaf7 100644
--- a/test/same-shape.test.js
+++ b/test/same-shape.test.js
@@ -33,6 +33,36 @@ test('same shape on Request', async (t) => {
   await app.inject('/')
 })
 
+test('same shape on Request when object', async (t) => {
+  t.plan(1)
+
+  const app = fastify()
+
+  let request
+
+  app.decorateRequest('object', null)
+
+  app.addHook('preHandler', (req, reply, done) => {
+    if (request) {
+      req.object = {}
+    }
+    done()
+  })
+
+  app.get('/', (req, reply) => {
+    if (request) {
+      t.equal(%HaveSameMap(request, req), true)
+    }
+
+    request = req
+
+    return 'hello world'
+  })
+
+  await app.inject('/')
+  await app.inject('/')
+})
+
 test('same shape on Reply', async (t) => {
   t.plan(1)
 
@@ -62,3 +92,33 @@ test('same shape on Reply', async (t) => {
   await app.inject('/')
   await app.inject('/')
 })
+
+test('same shape on Reply when object', async (t) => {
+  t.plan(1)
+
+  const app = fastify()
+
+  let _reply
+
+  app.decorateReply('object', null)
+
+  app.addHook('preHandler', (req, reply, done) => {
+    if (_reply) {
+      reply.object = {}
+    }
+    done()
+  })
+
+  app.get('/', (req, reply) => {
+    if (_reply) {
+      t.equal(%HaveSameMap(_reply, reply), true)
+    }
+
+    _reply = reply
+
+    return 'hello world'
+  })
+
+  await app.inject('/')
+  await app.inject('/')
+})