Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .prettierrc.json

This file was deleted.

2 changes: 0 additions & 2 deletions .taprc

This file was deleted.

8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![CI](https://github.com/fastify/fastify-request-context/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify-request-context/actions/workflows/ci.yml)
[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard)

Request-scoped storage support, based on [AsyncLocalStorage](https://nodejs.org/api/async_context.html#asynchronous-context-tracking).

Expand All @@ -13,14 +14,14 @@ nor will variables remain available once a request is completed.

Frequent use-cases are persisting request-aware logger instances and user authorization information.



## Install

```
npm i @fastify/request-context
```

### Compatibility

| Plugin version | Fastify version |
| ---------------|-----------------|
| `>=6.x` | `^5.x` |
Expand All @@ -29,7 +30,6 @@ npm i @fastify/request-context
| `^1.x` | `^2.x` |
| `^1.x` | `^1.x` |


Please note that if a Fastify version is out of support, then so are the corresponding versions of this plugin
in the table above.
See [Fastify's LTS policy](https://github.com/fastify/fastify/blob/main/docs/Reference/LTS.md) for more details.
Expand Down Expand Up @@ -133,7 +133,7 @@ return app.ready()
In TypeScript you are expected to augment the module to type your context:

```ts
import {requestContext} from '@fastify/request-context'
import { requestContext } from '@fastify/request-context'

declare module '@fastify/request-context' {
interface RequestContextData {
Expand Down
20 changes: 4 additions & 16 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
'use strict'

const globals = require('globals')
const js = require('@eslint/js')
const prettier = require('eslint-plugin-prettier/recommended')

module.exports = [
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
},
},
js.configs.recommended,
prettier,
]
module.exports = require('neostandard')({
ignores: require('neostandard').resolveIgnoresFromGitignore(),
ts: true,
})
6 changes: 3 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ const requestContext = {
},
}

function fastifyRequestContext(fastify, opts, next) {
function fastifyRequestContext (fastify, opts, next) {
fastify.decorate('requestContext', requestContext)
fastify.decorateRequest('requestContext', { getter: () => requestContext })
fastify.decorateRequest(asyncResourceSymbol, null)
const hook = opts.hook || 'onRequest'
const hasDefaultStoreValuesFactory = typeof opts.defaultStoreValues === 'function'

fastify.addHook(hook, function requestContextHook(req, _res, done) {
fastify.addHook(hook, function requestContextHook (req, _res, done) {
const defaultStoreValues = hasDefaultStoreValuesFactory
? opts.defaultStoreValues(req)
: opts.defaultStoreValues
Expand All @@ -51,7 +51,7 @@ function fastifyRequestContext(fastify, opts, next) {
// in a different async context, as req/res may emit events in a different context.
// Related to https://github.com/nodejs/node/issues/34430 and https://github.com/nodejs/node/issues/33723
if (hook === 'onRequest' || hook === 'preParsing') {
fastify.addHook('preValidation', function requestContextPreValidationHook(req, _res, done) {
fastify.addHook('preValidation', function requestContextPreValidationHook (req, _res, done) {
const asyncResource = req[asyncResourceSymbol]
asyncResource.runInAsyncScope(done, req.raw)
})
Expand Down
10 changes: 4 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,18 @@
"test": "npm run test:unit && npm run test:typescript",
"test:unit": "c8 --100 node --test",
"test:typescript": "tsd",
"lint": "eslint \"test/**/*.js\" \"test-tap/**/*.js\" index.js",
"prettier": "prettier --write \"{lib,test,test-tap}/**/*.js\" index.js \"types/**/*.ts\""
"lint": "eslint",
"lint:fix": "eslint --fix"
},
"dependencies": {
"fastify-plugin": "^5.0.0"
},
"devDependencies": {
"@types/node": "^25.0.3",
"c8": "^11.0.0",
"eslint": "^10.0.2",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint": "^9.39.0",
"fastify": "^5.0.0",
"prettier": "^3.2.5",
"neostandard": "^0.12.0",
"superagent": "^10.0.0",
"tsd": "^0.33.0"
},
Expand Down
10 changes: 5 additions & 5 deletions test/internal/appInitializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
const fastify = require('fastify')
const { fastifyRequestContext } = require('../..')

function initAppGet(endpoint) {
function initAppGet (endpoint) {
const app = fastify({ logger: true })
app.register(fastifyRequestContext)

app.get('/', endpoint)
return app
}

function initAppPost(endpoint) {
function initAppPost (endpoint) {
const app = fastify({ logger: true })
app.register(fastifyRequestContext)

Expand All @@ -20,7 +20,7 @@ function initAppPost(endpoint) {
return app
}

function initAppPostWithPrevalidation(endpoint) {
function initAppPostWithPrevalidation (endpoint) {
const app = fastify({ logger: true })
app.register(fastifyRequestContext, { hook: 'preValidation' })

Expand All @@ -40,7 +40,7 @@ function initAppPostWithPrevalidation(endpoint) {
return app
}

function initAppPostWithAllPlugins(endpoint, requestHook) {
function initAppPostWithAllPlugins (endpoint, requestHook) {
const app = fastify({ logger: true })
app.register(fastifyRequestContext, { hook: requestHook })

Expand Down Expand Up @@ -85,7 +85,7 @@ function initAppPostWithAllPlugins(endpoint, requestHook) {
return app
}

function initAppGetWithDefaultStoreValues(endpoint, defaultStoreValues) {
function initAppGetWithDefaultStoreValues (endpoint, defaultStoreValues) {
const app = fastify({ logger: true })
app.register(fastifyRequestContext, {
defaultStoreValues,
Expand Down
10 changes: 5 additions & 5 deletions test/internal/testService.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,29 @@ const { requestContext } = require('../..')

// Test class to check if nested calls with promises work correctly with async local storage
class TestService {
constructor(fastify) {
constructor (fastify) {
this.appRequestContext = fastify.requestContext
}

processRequest(requestId) {
processRequest (requestId) {
return this.fetchData().then(() => {
const testValueFromApp = this.appRequestContext.get('testKey')
const testValueFromLib = requestContext.get('testKey')
if (testValueFromApp !== `testValue${requestId}`) {
throw new Error(
`Wrong value retrieved from app context for request ${requestId}: ${testValueFromApp}`,
`Wrong value retrieved from app context for request ${requestId}: ${testValueFromApp}`
)
}

if (testValueFromLib !== `testValue${requestId}`) {
throw new Error(
`Wrong value retrieved from lib context for request ${requestId}: ${testValueFromLib}`,
`Wrong value retrieved from lib context for request ${requestId}: ${testValueFromLib}`
)
}
})
}

fetchData() {
fetchData () {
return new Promise((resolve) => {
setTimeout(resolve, 10)
})
Expand Down
12 changes: 6 additions & 6 deletions test/internal/watcherService.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ const { executionAsyncId, createHook, AsyncResource } = require('node:async_hook
const { EventEmitter } = require('node:events')

class CustomResource extends AsyncResource {
constructor(type, traceId) {
constructor (type, traceId) {
super(type)

this.traceId = traceId
}
}

class AsyncWatcher extends EventEmitter {
setupInitHook() {
setupInitHook () {
// init is called during object construction. The resource may not have
// completed construction when this callback runs, therefore all fields of the
// resource referenced by "asyncId" may not have been populated.
Expand All @@ -28,7 +28,7 @@ class AsyncWatcher extends EventEmitter {
return this
}

setupDestroyHook() {
setupDestroyHook () {
// Destroy is called when an AsyncWrap instance is destroyed.
this.destroy = (asyncId) => {
this.emit('DESTROY', {
Expand All @@ -39,7 +39,7 @@ class AsyncWatcher extends EventEmitter {
return this
}

start() {
start () {
createHook({
init: this.init.bind(this),
destroy: this.destroy.bind(this),
Expand All @@ -50,7 +50,7 @@ class AsyncWatcher extends EventEmitter {
}

class AsyncHookContainer {
constructor(types) {
constructor (types) {
const checkedTypes = types

const idMap = new Map()
Expand Down Expand Up @@ -86,7 +86,7 @@ class AsyncHookContainer {
this.watcher = watcher
}

getStore(asyncId) {
getStore (asyncId) {
let resource = this.resourceMap.get(asyncId)

if (resource != null) {
Expand Down
5 changes: 3 additions & 2 deletions test/requestContextPlugin.e2e.spec.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable promise/param-names */
'use strict'

const request = require('superagent')
Expand Down Expand Up @@ -26,7 +27,7 @@ describe('requestContextPlugin E2E', () => {
const route = (req) => {
const requestId = req.requestContext.get('testKey')

function prepareReply() {
function prepareReply () {
return testService.processRequest(requestId.replace('testValue', '')).then(() => {
const storedValue = req.requestContext.get('testKey')
return Promise.resolve({ storedValue })
Expand Down Expand Up @@ -104,7 +105,7 @@ describe('requestContextPlugin E2E', () => {

const requestId = `testValue${preHandlerValue}`

function prepareReply() {
function prepareReply () {
return testService.processRequest(requestId.replace('testValue', '')).then(() => {
const storedValue = req.requestContext.get('preValidation')
return Promise.resolve({ storedValue: `testValue${storedValue}` })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable promise/param-names */
'use strict'

const fastify = require('fastify')
Expand Down Expand Up @@ -29,7 +30,7 @@ test('correctly preserves values set in prevalidation phase within single POST r
const route = (req) => {
const requestId = req.requestContext.get('testKey')

function prepareReply() {
function prepareReply () {
return testService.processRequest(requestId.replace('testValue', '')).then(() => {
const storedValue = req.requestContext.get('testKey')
return Promise.resolve({ storedValue })
Expand Down Expand Up @@ -107,7 +108,7 @@ test('correctly preserves values set in multiple phases within single POST reque

const requestId = `testValue${preHandlerValue}`

function prepareReply() {
function prepareReply () {
return testService.processRequest(requestId.replace('testValue', '')).then(() => {
const storedValue = req.requestContext.get('preValidation')
return Promise.resolve({ storedValue: `testValue${storedValue}` })
Expand Down
9 changes: 5 additions & 4 deletions test/requestContextPlugin.spec.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable promise/param-names */
'use strict'

const {
Expand All @@ -24,7 +25,7 @@ describe('requestContextPlugin', () => {
const promiseRequest2 = new Promise((resolveRequest2Promise) => {
const promiseRequest1 = new Promise((resolveRequest1Promise) => {
const route = (req, reply) => {
function prepareReply() {
function prepareReply () {
return testService.processRequest(requestId).then(() => {
const storedValue = req.requestContext.get('testKey')
reply.status(200).send({
Expand Down Expand Up @@ -99,7 +100,7 @@ describe('requestContextPlugin', () => {
const promiseRequest2 = new Promise((resolveRequest2Promise) => {
const promiseRequest1 = new Promise((resolveRequest1Promise) => {
const route = (req, reply) => {
function prepareReply() {
function prepareReply () {
return testService.processRequest(requestId).then(() => {
const storedValue = req.requestContext.get('testKey')
reply.status(200).send({
Expand Down Expand Up @@ -176,7 +177,7 @@ describe('requestContextPlugin', () => {
const route = (req, reply) => {
const requestId = req.requestContext.get('testKey')

function prepareReply() {
function prepareReply () {
return testService.processRequest(requestId.replace('testValue', '')).then(() => {
const storedValue = req.requestContext.get('testKey')
reply.status(200).send({
Expand Down Expand Up @@ -289,7 +290,7 @@ describe('requestContextPlugin', () => {
const promiseRequest2 = new Promise((resolveRequest2Promise) => {
const promiseRequest1 = new Promise((resolveRequest1Promise) => {
const route = (req, reply) => {
function prepareReply() {
function prepareReply () {
return testService.processRequest(requestId).then(() => {
const storedValue = req.requestContext.get('testKey')
reply.status(204).header('storedvalue', storedValue).send()
Expand Down
11 changes: 3 additions & 8 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"compilerOptions": {
"outDir": "dist",
"module": "commonjs",
"target": "es2015",
"target": "es2022",
"sourceMap": true,
"declaration": true,
"declarationMap": false,
"types": ["node", "jest"],
"types": ["node"],
"strict": true,
"moduleResolution": "node",
"noUnusedLocals": false,
Expand All @@ -17,15 +17,10 @@
"noImplicitThis": true,
"strictNullChecks": true,
"importHelpers": true,
"baseUrl": ".",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"exactOptionalPropertyTypes": true
},
"exclude": [
"node_modules",
"test",
"dist"
]
"exclude": ["node_modules", "test", "dist"]
}
2 changes: 1 addition & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ declare namespace fastifyRequestContext {
export { fastifyRequestContext as default }
}

declare function fastifyRequestContext(
declare function fastifyRequestContext (
...params: Parameters<FastifyRequestContext>
): ReturnType<FastifyRequestContext>
export = fastifyRequestContext
1 change: 1 addition & 0 deletions types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ expectType<AsyncLocalStorage<RequestContext>>(asyncLocalStorage)
const getHandler: RouteHandlerMethod = function (request, _reply) {
expectType<RequestContext>(request.requestContext)
}
expectType<RouteHandlerMethod>(getHandler)

expectType<string | undefined>(requestContext.get('a'))
expectType<FastifyBaseLogger | undefined>(requestContext.get('log'))
Expand Down