Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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": "^10.1.3",
"eslint": "^9.6.0",
"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
Loading