diff --git a/src/IMQRequestContext.ts b/src/IMQRequestContext.ts new file mode 100644 index 0000000..1c9d28e --- /dev/null +++ b/src/IMQRequestContext.ts @@ -0,0 +1,57 @@ +/*! + * IMQRequestContext implementation + * + * I'm Queue Software Project + * Copyright (C) 2025 imqueue.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * If you want to use this code in a closed source (commercial) project, you can + * purchase a proprietary commercial license. Please contact us at + * to get commercial licensing options. + */ +import { AsyncLocalStorage } from 'node:async_hooks'; +import { IMQMetadata } from './IMQMetadata'; +import { IMQRPCRequest } from './IMQRPCRequest'; + +const storage = new AsyncLocalStorage(); + +/** + * Runs the given function with `request` bound to the current async execution + * context, so any code reached from it (and its asynchronous continuations) can + * access the in-flight request's metadata via `currentMetadata()` without + * threading it through call signatures. + * + * The binding is scoped to the function: it is established for the duration of + * the call and automatically removed afterwards, which keeps concurrent + * requests isolated from one another. + * + * @param {IMQRPCRequest} request - the request to bind for this execution + * @param {() => T} fn - the work to run within the bound context + * @return {T} + */ +export function runWithRequest(request: IMQRPCRequest, fn: () => T): T { + return storage.run(request, fn); +} + +/** + * Returns the metadata of the in-flight IMQ request for the current async + * execution, if any. Returns `undefined` outside of a `runWithRequest()` scope. + * The transport carries metadata as an opaque bag; callers interpret its fields. + * + * @return {IMQMetadata | undefined} + */ +export function currentMetadata(): IMQMetadata | undefined { + return storage.getStore()?.metadata; +} diff --git a/src/IMQService.ts b/src/IMQService.ts index 4103f8a..11d6c81 100644 --- a/src/IMQService.ts +++ b/src/IMQService.ts @@ -47,6 +47,7 @@ import { import * as os from 'os'; import { ArgDescription } from './IMQRPCDescription'; import { IMQBeforeCall, IMQAfterCall } from './IMQRPCOptions'; +import { runWithRequest } from './IMQRequestContext'; import * as http from 'node:http'; const cluster: any = require('cluster'); @@ -170,6 +171,13 @@ export abstract class IMQService { private async handleRequest( request: IMQRPCRequest, id: string, + ): Promise { + return runWithRequest(request, () => this.processRequest(request, id)); + } + + private async processRequest( + request: IMQRPCRequest, + id: string, ): Promise { const logger = this.options.logger || console; const method = request.method; diff --git a/src/index.ts b/src/index.ts index 06de58b..a5f9f59 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,3 +33,4 @@ export * from './IMQLock'; export * from './IMQService'; export * from './IMQClient'; export * from './IMQMetadata'; +export * from './IMQRequestContext';