reqlocal 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 reqlocal contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,340 @@
1
+ <div align="center">
2
+
3
+ <br />
4
+
5
+ <img src="./docs/logo-mark.svg" alt="reqlocal" width="72" height="72" />
6
+
7
+ # reqlocal
8
+
9
+ ### Stop threading context. One middleware.
10
+
11
+ [![npm](https://img.shields.io/npm/v/reqlocal?style=flat-square&color=2dd4bf&label=npm)](https://www.npmjs.com/package/reqlocal)
12
+ [![downloads](https://img.shields.io/npm/dm/reqlocal?style=flat-square&color=34d399&label=downloads)](https://www.npmjs.com/package/reqlocal)
13
+ [![minzipped size](https://img.shields.io/bundlephobia/minzip/reqlocal?style=flat-square&color=059669&label=minzipped%20size)](https://bundlephobia.com/package/reqlocal)
14
+ [![license](https://img.shields.io/badge/license-MIT-34d399?style=flat-square)](./LICENSE)
15
+
16
+ <br />
17
+
18
+ Open-source **request-scoped context** for Node.js with **`AsyncLocalStorage`**: typed **`getCtx()`** anywhere in the request lifecycle, without passing bags of arguments through every helper.
19
+
20
+ **Zero runtime dependencies.** TypeScript-first. Works with **Express** and **Fastify**.
21
+
22
+ <br />
23
+
24
+ [Repo](https://github.com/Abdul-Moiz31/reqlocal) · [Install](#install) · [Why this exists](#why-i-built-this) · [API](#api-reference)
25
+
26
+ <br />
27
+
28
+ ---
29
+
30
+ </div>
31
+
32
+ ## Why I built this
33
+
34
+ This did not come from a toy example. It came from **adminIde-stack**, the big multi-tenant stack I work on: GraphQL, Auth0, billing, org and project context, subscriptions, and a `ServerContext` type that keeps growing because every package augments the same central context interface.
35
+
36
+ In that codebase, a single request is not just headers and a body. You end up with account id, org id, tenant id, permissions, parsed URI segments for the current page, sometimes a WebSocket connection with `connectionParams` instead of a normal `req`, and special paths like secret API tokens. Middleware like `addUserContext` has to branch across HTTP, WebSocket, and token flows, and downstream code still expects something that looks like a request so helpers such as `getPermissionsFromContext` can read `context.req`, `context.userContext`, and `context.user` together. Plugins do the same kind of thing, for example resolving tenant off `requestContext.contextValue.req?.tenant` before an operation runs.
37
+
38
+ So the "real time" problem was not only long parameter lists. It was that **everything interesting about the caller lived inside a fat GraphQL context and a stuffed `req`**, and serious logic could not run unless you threaded that whole picture through resolvers, middleware, and services. When subscriptions need a mock `req` so the same permission code can run, you feel how heavy that model is.
39
+
40
+ **reqlocal** is what I wanted for the other end of the spectrum: small HTTP services, edge functions on the Node runtime, internal APIs, and tests. You define a **narrow** request-scoped object once at the edge. After that you call **`getCtx()`** instead of dragging a god object through every layer. It uses the same primitive Node gives you for async-safe isolation: **`AsyncLocalStorage`**. No extra runtime dependencies, just **`reqlocal`** middleware (or the Fastify plugin) plus **`runWithCtx`** when you are not inside Express.
41
+
42
+ It does not replace a full Apollo `ServerContext` on its own. It is the distilled habit I wish I had everywhere: **establish scope once, read it anywhere in the async tree**, without inventing another global or another ten constructor arguments.
43
+
44
+ If you have lived in a stack like adminIde-stack, you already know why that matters.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ npm install reqlocal
50
+ ```
51
+
52
+ ```bash
53
+ pnpm add reqlocal
54
+ ```
55
+
56
+ ```bash
57
+ yarn add reqlocal
58
+ ```
59
+
60
+ For **`reqlocalPlugin`** (Fastify), add the optional peer:
61
+
62
+ ```bash
63
+ npm install reqlocal fastify
64
+ ```
65
+
66
+ ## Usage
67
+
68
+ ```ts
69
+ import express from 'express';
70
+ import { reqlocal, getCtx } from 'reqlocal';
71
+
72
+ const app = express();
73
+
74
+ app.use(
75
+ reqlocal({
76
+ userId: (req) => String(req.headers['x-user-id'] ?? ''),
77
+ }),
78
+ );
79
+
80
+ app.get('/me', (_req, res) => {
81
+ const { userId } = getCtx<{ userId: string }>();
82
+ res.json({ userId });
83
+ });
84
+ ```
85
+
86
+ Typed context for the whole request, including after `await`, without threading arguments through every helper.
87
+
88
+ ## Before and after
89
+
90
+ **Manual threading**
91
+
92
+ ```ts
93
+ async function loadProfile(userId: string, traceId: string) {
94
+ return db.profiles.find({ userId, traceId });
95
+ }
96
+
97
+ app.get('/me', async (req, res) => {
98
+ const userId = req.headers['x-user-id'] as string;
99
+ const traceId = req.headers['x-trace-id'] as string;
100
+ const profile = await loadProfile(userId, traceId);
101
+ res.json(profile);
102
+ });
103
+ ```
104
+
105
+ **reqlocal**
106
+
107
+ ```ts
108
+ async function loadProfile() {
109
+ const { userId, traceId } = getCtx<{ userId: string; traceId: string }>();
110
+ return db.profiles.find({ userId, traceId });
111
+ }
112
+
113
+ app.get('/me', async (_req, res) => {
114
+ const profile = await loadProfile();
115
+ res.json(profile);
116
+ });
117
+ ```
118
+
119
+ ## TypeScript
120
+
121
+ ```ts
122
+ import type { InferContext } from 'reqlocal';
123
+
124
+ const contextConfig = {
125
+ userId: (req) => String(req.headers['x-user-id'] ?? ''),
126
+ traceId: (req) => String(req.headers['x-trace-id'] ?? ''),
127
+ requestStartedAt: () => Date.now(),
128
+ } as const;
129
+
130
+ type AppContext = InferContext<typeof contextConfig>;
131
+
132
+ function audit(): AppContext {
133
+ return getCtx<AppContext>();
134
+ }
135
+ ```
136
+
137
+ ## Background jobs and tests
138
+
139
+ ```ts
140
+ import { runWithCtx, getCtx } from 'reqlocal';
141
+
142
+ await runWithCtx({ jobId: '42', source: 'nightly' }, async () => {
143
+ const ctx = getCtx<{ jobId: string; source: string }>();
144
+ await doWork(ctx.jobId);
145
+ });
146
+ ```
147
+
148
+ Nested **`runWithCtx`** calls stack correctly with HTTP-bound context.
149
+
150
+ ## Framework integrations
151
+
152
+ ### Express (middleware)
153
+
154
+ ```ts
155
+ import express from 'express';
156
+ import { reqlocal, getCtx } from 'reqlocal';
157
+
158
+ const app = express();
159
+
160
+ app.use(
161
+ reqlocal({
162
+ userId: (req) => String(req.headers['x-user-id'] ?? ''),
163
+ traceId: (req) => String(req.headers['x-trace-id'] ?? ''),
164
+ }),
165
+ );
166
+
167
+ app.get('/api/hello', (_req, res) => {
168
+ res.json(getCtx<{ userId: string; traceId: string }>());
169
+ });
170
+ ```
171
+
172
+ Mount **`reqlocal`** **before** any route or middleware that calls **`getCtx()`**.
173
+
174
+ ### Fastify (plugin)
175
+
176
+ ```ts
177
+ import Fastify from 'fastify';
178
+ import { reqlocalPlugin, getCtx } from 'reqlocal';
179
+
180
+ const app = Fastify();
181
+
182
+ await app.register(reqlocalPlugin, {
183
+ config: {
184
+ userId: (req) => String(req.headers['x-user-id'] ?? ''),
185
+ },
186
+ });
187
+
188
+ app.get('/api/me', async () => getCtx<{ userId: string }>());
189
+ ```
190
+
191
+ Config callbacks receive the Node **`IncomingMessage`** (`request.raw`).
192
+
193
+ ### Next.js (route handler, Node runtime)
194
+
195
+ Use the **Node.js** runtime (not Edge). Establish context with **`runWithCtx`** when you are not behind Express:
196
+
197
+ ```ts
198
+ import { NextResponse } from 'next/server';
199
+ import { runWithCtx, getCtx } from 'reqlocal';
200
+
201
+ export async function GET(request: Request) {
202
+ const userId = request.headers.get('x-user-id') ?? '';
203
+
204
+ return runWithCtx({ userId }, async () => {
205
+ const ctx = getCtx<{ userId: string }>();
206
+ return NextResponse.json({ ok: true, userId: ctx.userId });
207
+ });
208
+ }
209
+ ```
210
+
211
+ ## How it works
212
+
213
+ ```
214
+ HTTP request
215
+
216
+
217
+ ┌──────────────────┐ ┌────────────────────────────┐
218
+ │ reqlocal │────▶│ Build context object │
219
+ │ middleware │ │ (headers, auth, traceId…) │
220
+ └────────┬─────────┘ └────────────────────────────┘
221
+
222
+
223
+ ┌──────────────────┐ ┌────────────────────────────┐
224
+ │ AsyncLocalStorage │────▶│ One store per request │
225
+ │ .run(store, …) │ │ Survives await / microtasks │
226
+ └────────┬─────────┘ └────────────────────────────┘
227
+
228
+
229
+ ┌──────────────────┐ ┌────────────────────────────┐
230
+ │ Route + services │────▶│ getCtx() / getCtxOrNull() │
231
+ └──────────────────┘ └────────────────────────────┘
232
+ ```
233
+
234
+ Each concurrent request gets an isolated store. No `cls-hooked` and no extra npm runtime dependencies: only Node’s built-in ALS.
235
+
236
+ ## Comparison
237
+
238
+ | | reqlocal | Manual threading | Implicit globals |
239
+ | --- | :---: | :---: | :---: |
240
+ | **Typed context** | ✅ | ✅ (verbose) | ❌ |
241
+ | **Works after `await`** | ✅ | ✅ | ⚠️ error-prone |
242
+ | **Zero extra runtime deps** | ✅ | ✅ | ✅ |
243
+ | **Concurrent requests safe** | ✅ | ✅ | ❌ |
244
+ | **Express / Fastify** | ✅ | ✅ | ⚠️ ad hoc |
245
+ | **`fastify-plugin` wrapper** | ✅ | N/A | N/A |
246
+
247
+ ## API reference
248
+
249
+ ### `getCtx<T>(): T`
250
+
251
+ Returns the current context object. **Throws** if called outside an active store:
252
+
253
+ ```txt
254
+ [reqlocal] getCtx() called outside a request context. Make sure reqlocal middleware runs before your route handlers.
255
+ ```
256
+
257
+ ### `getCtxOrNull<T>(): T | null`
258
+
259
+ Same as **`getCtx`**, but returns **`null`** when no store is active.
260
+
261
+ ### `runWithCtx(ctx, fn): Promise`
262
+
263
+ Runs **`fn`** (must return a **`Promise`**) inside a new store. Use for workers, queues, and tests.
264
+
265
+ ### `reqlocal(config): Middleware`
266
+
267
+ Express / Connect middleware. **`config`** maps string keys to **`(req: IncomingMessage) => value`**.
268
+
269
+ ### `reqlocalPlugin`
270
+
271
+ Fastify plugin (via **`fastify-plugin`**). Register with **`{ config }`** in the same shape as Express.
272
+
273
+ ### Exported types
274
+
275
+ | Type | Description |
276
+ | --- | --- |
277
+ | `ContextConfig` | Keys → `(req: IncomingMessage) => unknown` |
278
+ | `InferContext<C>` | Inferred context shape from **`C`** |
279
+ | `ReqlocalMiddleware` | Connect-compatible middleware type |
280
+ | `ReqlocalFastifyOptions` | `{ config: ContextConfig }` |
281
+
282
+ ## Package exports
283
+
284
+ Dual **ESM** (`.mjs`) and **CommonJS** (`.js`) with shared **`.d.ts`**:
285
+
286
+ ```json
287
+ "exports": {
288
+ ".": {
289
+ "types": "./dist/index.d.ts",
290
+ "import": "./dist/index.mjs",
291
+ "require": "./dist/index.js"
292
+ }
293
+ }
294
+ ```
295
+
296
+ ## Requirements
297
+
298
+ - **Node.js ≥ 16.0.0** (`AsyncLocalStorage`)
299
+
300
+ ## Developing
301
+
302
+ ```bash
303
+ npm install
304
+ npm test
305
+ npm run build
306
+ ```
307
+
308
+ Tests: **Vitest** (`tests/context`, `concurrent`, `express`, `fastify`). Source: **`src/`** (tsup → **`dist/`**).
309
+
310
+ ## Publishing
311
+
312
+ 1. Set **`repository`**, **`author`**, and **`version`** in **`package.json`**
313
+ 2. **`npm test`** && **`npm run build`**
314
+ 3. **`npm publish`**
315
+
316
+ Badges may show “package not found” until the first npm publish.
317
+
318
+ ## Contributing
319
+
320
+ Issues and PRs welcome. Please run **`npm test`** before submitting.
321
+
322
+ ## Marketing site
323
+
324
+ If you use the optional Next.js app in a monorepo (`Website/`), run `cd Website && npm install && npm run dev` and open **http://localhost:3000/docs**. This repository’s README is self-contained for npm and GitHub.
325
+
326
+ ## License
327
+
328
+ MIT. Use it in any project, commercial or open-source. See **[LICENSE](./LICENSE)**.
329
+
330
+ ---
331
+
332
+ <div align="center">
333
+
334
+ <br />
335
+
336
+ **Built by** [@Abdul-Moiz31](https://github.com/Abdul-Moiz31)
337
+
338
+ <br />
339
+
340
+ </div>
@@ -0,0 +1,23 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { FastifyPluginAsync } from 'fastify';
3
+
4
+ declare function getCtx<T extends Record<string, unknown> = Record<string, unknown>>(): T;
5
+ declare function getCtxOrNull<T extends Record<string, unknown> = Record<string, unknown>>(): T | null;
6
+ declare function runWithCtx<TCtx extends Record<string, unknown>, TResult>(ctx: TCtx, fn: () => Promise<TResult>): Promise<TResult>;
7
+
8
+ type ContextConfig = {
9
+ readonly [K: string]: (req: IncomingMessage) => unknown;
10
+ };
11
+ type InferContext<C extends ContextConfig> = {
12
+ [K in keyof C]: C[K] extends (req: IncomingMessage) => infer R ? R : never;
13
+ };
14
+ type ReqlocalMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
15
+
16
+ declare function reqlocal<C extends ContextConfig>(config: C): (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
17
+
18
+ type ReqlocalFastifyOptions = {
19
+ config: ContextConfig;
20
+ };
21
+ declare const reqlocalPlugin: FastifyPluginAsync<ReqlocalFastifyOptions>;
22
+
23
+ export { type ContextConfig, type InferContext, type ReqlocalFastifyOptions, type ReqlocalMiddleware, getCtx, getCtxOrNull, reqlocal, reqlocalPlugin, runWithCtx };
@@ -0,0 +1,23 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { FastifyPluginAsync } from 'fastify';
3
+
4
+ declare function getCtx<T extends Record<string, unknown> = Record<string, unknown>>(): T;
5
+ declare function getCtxOrNull<T extends Record<string, unknown> = Record<string, unknown>>(): T | null;
6
+ declare function runWithCtx<TCtx extends Record<string, unknown>, TResult>(ctx: TCtx, fn: () => Promise<TResult>): Promise<TResult>;
7
+
8
+ type ContextConfig = {
9
+ readonly [K: string]: (req: IncomingMessage) => unknown;
10
+ };
11
+ type InferContext<C extends ContextConfig> = {
12
+ [K in keyof C]: C[K] extends (req: IncomingMessage) => infer R ? R : never;
13
+ };
14
+ type ReqlocalMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
15
+
16
+ declare function reqlocal<C extends ContextConfig>(config: C): (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
17
+
18
+ type ReqlocalFastifyOptions = {
19
+ config: ContextConfig;
20
+ };
21
+ declare const reqlocalPlugin: FastifyPluginAsync<ReqlocalFastifyOptions>;
22
+
23
+ export { type ContextConfig, type InferContext, type ReqlocalFastifyOptions, type ReqlocalMiddleware, getCtx, getCtxOrNull, reqlocal, reqlocalPlugin, runWithCtx };
package/dist/index.js ADDED
@@ -0,0 +1,177 @@
1
+ 'use strict';
2
+
3
+ var async_hooks = require('async_hooks');
4
+
5
+ var __create = Object.create;
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __commonJS = (cb, mod) => function __require() {
12
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
+ };
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
23
+ // If the importer is in node compatibility mode or this is not an ESM
24
+ // file that has been converted to a CommonJS file using a Babel-
25
+ // compatible transform (i.e. "__esModule" has not been set), then set
26
+ // "default" to the CommonJS "module.exports" for node compatibility.
27
+ __defProp(target, "default", { value: mod, enumerable: true }) ,
28
+ mod
29
+ ));
30
+
31
+ // node_modules/fastify-plugin/lib/getPluginName.js
32
+ var require_getPluginName = __commonJS({
33
+ "node_modules/fastify-plugin/lib/getPluginName.js"(exports$1, module) {
34
+ var fpStackTracePattern = /at\s{1}(?:.*\.)?plugin\s{1}.*\n\s*(.*)/;
35
+ var fileNamePattern = /(\w*(\.\w*)*)\..*/;
36
+ module.exports = function getPluginName(fn) {
37
+ if (fn.name.length > 0) return fn.name;
38
+ const stackTraceLimit = Error.stackTraceLimit;
39
+ Error.stackTraceLimit = 10;
40
+ try {
41
+ throw new Error("anonymous function");
42
+ } catch (e) {
43
+ Error.stackTraceLimit = stackTraceLimit;
44
+ return extractPluginName(e.stack);
45
+ }
46
+ };
47
+ function extractPluginName(stack) {
48
+ const m = stack.match(fpStackTracePattern);
49
+ return m ? m[1].split(/[/\\]/).slice(-1)[0].match(fileNamePattern)[1] : "anonymous";
50
+ }
51
+ module.exports.extractPluginName = extractPluginName;
52
+ }
53
+ });
54
+
55
+ // node_modules/fastify-plugin/lib/toCamelCase.js
56
+ var require_toCamelCase = __commonJS({
57
+ "node_modules/fastify-plugin/lib/toCamelCase.js"(exports$1, module) {
58
+ module.exports = function toCamelCase(name) {
59
+ if (name[0] === "@") {
60
+ name = name.slice(1).replace("/", "-");
61
+ }
62
+ const newName = name.replace(/-(.)/g, function(match, g1) {
63
+ return g1.toUpperCase();
64
+ });
65
+ return newName;
66
+ };
67
+ }
68
+ });
69
+
70
+ // node_modules/fastify-plugin/plugin.js
71
+ var require_plugin = __commonJS({
72
+ "node_modules/fastify-plugin/plugin.js"(exports$1, module) {
73
+ var getPluginName = require_getPluginName();
74
+ var toCamelCase = require_toCamelCase();
75
+ var count = 0;
76
+ function plugin(fn, options = {}) {
77
+ let autoName = false;
78
+ if (typeof fn.default !== "undefined") {
79
+ fn = fn.default;
80
+ }
81
+ if (typeof fn !== "function") {
82
+ throw new TypeError(
83
+ `fastify-plugin expects a function, instead got a '${typeof fn}'`
84
+ );
85
+ }
86
+ if (typeof options === "string") {
87
+ options = {
88
+ fastify: options
89
+ };
90
+ }
91
+ if (typeof options !== "object" || Array.isArray(options) || options === null) {
92
+ throw new TypeError("The options object should be an object");
93
+ }
94
+ if (options.fastify !== void 0 && typeof options.fastify !== "string") {
95
+ throw new TypeError(`fastify-plugin expects a version string, instead got '${typeof options.fastify}'`);
96
+ }
97
+ if (!options.name) {
98
+ autoName = true;
99
+ options.name = getPluginName(fn) + "-auto-" + count++;
100
+ }
101
+ fn[/* @__PURE__ */ Symbol.for("skip-override")] = options.encapsulate !== true;
102
+ fn[/* @__PURE__ */ Symbol.for("fastify.display-name")] = options.name;
103
+ fn[/* @__PURE__ */ Symbol.for("plugin-meta")] = options;
104
+ if (!fn.default) {
105
+ fn.default = fn;
106
+ }
107
+ const camelCase = toCamelCase(options.name);
108
+ if (!autoName && !fn[camelCase]) {
109
+ fn[camelCase] = fn;
110
+ }
111
+ return fn;
112
+ }
113
+ module.exports = plugin;
114
+ module.exports.default = plugin;
115
+ module.exports.fastifyPlugin = plugin;
116
+ }
117
+ });
118
+ var storage = new async_hooks.AsyncLocalStorage();
119
+ function runInStore(store, fn) {
120
+ return storage.run(store, fn);
121
+ }
122
+ function getStore() {
123
+ return storage.getStore();
124
+ }
125
+
126
+ // src/context.ts
127
+ var GET_CTX_ERROR = "[reqlocal] getCtx() called outside a request context. Make sure reqlocal middleware runs before your route handlers.";
128
+ function getCtx() {
129
+ const store = getStore();
130
+ if (store === void 0) {
131
+ throw new Error(GET_CTX_ERROR);
132
+ }
133
+ return store;
134
+ }
135
+ function getCtxOrNull() {
136
+ const store = getStore();
137
+ return store ?? null;
138
+ }
139
+ function runWithCtx(ctx, fn) {
140
+ return runInStore(ctx, () => fn());
141
+ }
142
+
143
+ // src/middleware.ts
144
+ function reqlocal(config) {
145
+ return (req, res, next) => {
146
+ const context = {};
147
+ for (const key of Object.keys(config)) {
148
+ context[key] = config[key](req);
149
+ }
150
+ runInStore(context, () => next());
151
+ };
152
+ }
153
+
154
+ // src/fastify.ts
155
+ var import_fastify_plugin = __toESM(require_plugin());
156
+ var reqlocalPluginImpl = async (fastify, opts) => {
157
+ const { config } = opts;
158
+ fastify.addHook("onRequest", (request, reply, done) => {
159
+ const context = {};
160
+ for (const key of Object.keys(config)) {
161
+ context[key] = config[key](request.raw);
162
+ }
163
+ runInStore(context, () => done());
164
+ });
165
+ };
166
+ var reqlocalPlugin = (0, import_fastify_plugin.default)(reqlocalPluginImpl, {
167
+ fastify: "4.x",
168
+ name: "reqlocal"
169
+ });
170
+
171
+ exports.getCtx = getCtx;
172
+ exports.getCtxOrNull = getCtxOrNull;
173
+ exports.reqlocal = reqlocal;
174
+ exports.reqlocalPlugin = reqlocalPlugin;
175
+ exports.runWithCtx = runWithCtx;
176
+ //# sourceMappingURL=index.js.map
177
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../node_modules/fastify-plugin/lib/getPluginName.js","../node_modules/fastify-plugin/lib/toCamelCase.js","../node_modules/fastify-plugin/plugin.js","../src/store.ts","../src/context.ts","../src/middleware.ts","../src/fastify.ts"],"names":["exports","AsyncLocalStorage","fp"],"mappings":"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","file":"index.js","sourcesContent":["'use strict'\n\nconst fpStackTracePattern = /at\\s{1}(?:.*\\.)?plugin\\s{1}.*\\n\\s*(.*)/\nconst fileNamePattern = /(\\w*(\\.\\w*)*)\\..*/\n\nmodule.exports = function getPluginName (fn) {\n if (fn.name.length > 0) return fn.name\n\n const stackTraceLimit = Error.stackTraceLimit\n Error.stackTraceLimit = 10\n try {\n throw new Error('anonymous function')\n } catch (e) {\n Error.stackTraceLimit = stackTraceLimit\n return extractPluginName(e.stack)\n }\n}\n\nfunction extractPluginName (stack) {\n const m = stack.match(fpStackTracePattern)\n\n // get last section of path and match for filename\n return m ? m[1].split(/[/\\\\]/).slice(-1)[0].match(fileNamePattern)[1] : 'anonymous'\n}\nmodule.exports.extractPluginName = extractPluginName\n","'use strict'\n\nmodule.exports = function toCamelCase (name) {\n if (name[0] === '@') {\n name = name.slice(1).replace('/', '-')\n }\n const newName = name.replace(/-(.)/g, function (match, g1) {\n return g1.toUpperCase()\n })\n return newName\n}\n","'use strict'\n\nconst getPluginName = require('./lib/getPluginName')\nconst toCamelCase = require('./lib/toCamelCase')\n\nlet count = 0\n\nfunction plugin (fn, options = {}) {\n let autoName = false\n\n if (typeof fn.default !== 'undefined') {\n // Support for 'export default' behaviour in transpiled ECMAScript module\n fn = fn.default\n }\n\n if (typeof fn !== 'function') {\n throw new TypeError(\n `fastify-plugin expects a function, instead got a '${typeof fn}'`\n )\n }\n\n if (typeof options === 'string') {\n options = {\n fastify: options\n }\n }\n\n if (\n typeof options !== 'object' ||\n Array.isArray(options) ||\n options === null\n ) {\n throw new TypeError('The options object should be an object')\n }\n\n if (options.fastify !== undefined && typeof options.fastify !== 'string') {\n throw new TypeError(`fastify-plugin expects a version string, instead got '${typeof options.fastify}'`)\n }\n\n if (!options.name) {\n autoName = true\n options.name = getPluginName(fn) + '-auto-' + count++\n }\n\n fn[Symbol.for('skip-override')] = options.encapsulate !== true\n fn[Symbol.for('fastify.display-name')] = options.name\n fn[Symbol.for('plugin-meta')] = options\n\n // Faux modules support\n if (!fn.default) {\n fn.default = fn\n }\n\n // TypeScript support for named imports\n // See https://github.com/fastify/fastify/issues/2404 for more details\n // The type definitions would have to be update to match this.\n const camelCase = toCamelCase(options.name)\n if (!autoName && !fn[camelCase]) {\n fn[camelCase] = fn\n }\n\n return fn\n}\n\nmodule.exports = plugin\nmodule.exports.default = plugin\nmodule.exports.fastifyPlugin = plugin\n","import { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst storage = new AsyncLocalStorage<Record<string, unknown>>();\n\nexport function runInStore<T>(store: Record<string, unknown>, fn: () => T): T {\n return storage.run(store, fn);\n}\n\nexport function getStore<\n T extends Record<string, unknown> = Record<string, unknown>,\n>(): T | undefined {\n return storage.getStore() as T | undefined;\n}\n","import { getStore, runInStore } from \"./store.js\";\n\nconst GET_CTX_ERROR =\n \"[reqlocal] getCtx() called outside a request context. Make sure reqlocal middleware runs before your route handlers.\";\n\nexport function getCtx<\n T extends Record<string, unknown> = Record<string, unknown>,\n>(): T {\n const store = getStore<T>();\n if (store === undefined) {\n throw new Error(GET_CTX_ERROR);\n }\n return store;\n}\n\nexport function getCtxOrNull<\n T extends Record<string, unknown> = Record<string, unknown>,\n>(): T | null {\n const store = getStore<T>();\n return store ?? null;\n}\n\nexport function runWithCtx<TCtx extends Record<string, unknown>, TResult>(\n ctx: TCtx,\n fn: () => Promise<TResult>,\n): Promise<TResult> {\n return runInStore(ctx, () => fn());\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { runInStore } from \"./store.js\";\nimport type { ContextConfig, InferContext } from \"./types.js\";\n\nexport function reqlocal<C extends ContextConfig>(\n config: C,\n): (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: unknown) => void,\n) => void {\n return (req, res, next) => {\n const context = {} as InferContext<C>;\n for (const key of Object.keys(config) as Array<keyof C>) {\n (context as Record<string, unknown>)[key as string] = config[key](req);\n }\n runInStore(context as Record<string, unknown>, () => next());\n };\n}\n","import type { FastifyPluginAsync } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport { runInStore } from \"./store.js\";\nimport type { ContextConfig } from \"./types.js\";\n\nexport type ReqlocalFastifyOptions = {\n config: ContextConfig;\n};\n\nconst reqlocalPluginImpl: FastifyPluginAsync<ReqlocalFastifyOptions> = async (\n fastify,\n opts,\n) => {\n const { config } = opts;\n fastify.addHook(\"onRequest\", (request, reply, done) => {\n const context: Record<string, unknown> = {};\n for (const key of Object.keys(config)) {\n context[key] = config[key](request.raw);\n }\n runInStore(context, () => done());\n });\n};\n\nexport const reqlocalPlugin = fp(reqlocalPluginImpl, {\n fastify: \"4.x\",\n name: \"reqlocal\",\n});\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,171 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJS = (cb, mod) => function __require() {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ __defProp(target, "default", { value: mod, enumerable: true }) ,
26
+ mod
27
+ ));
28
+
29
+ // node_modules/fastify-plugin/lib/getPluginName.js
30
+ var require_getPluginName = __commonJS({
31
+ "node_modules/fastify-plugin/lib/getPluginName.js"(exports$1, module) {
32
+ var fpStackTracePattern = /at\s{1}(?:.*\.)?plugin\s{1}.*\n\s*(.*)/;
33
+ var fileNamePattern = /(\w*(\.\w*)*)\..*/;
34
+ module.exports = function getPluginName(fn) {
35
+ if (fn.name.length > 0) return fn.name;
36
+ const stackTraceLimit = Error.stackTraceLimit;
37
+ Error.stackTraceLimit = 10;
38
+ try {
39
+ throw new Error("anonymous function");
40
+ } catch (e) {
41
+ Error.stackTraceLimit = stackTraceLimit;
42
+ return extractPluginName(e.stack);
43
+ }
44
+ };
45
+ function extractPluginName(stack) {
46
+ const m = stack.match(fpStackTracePattern);
47
+ return m ? m[1].split(/[/\\]/).slice(-1)[0].match(fileNamePattern)[1] : "anonymous";
48
+ }
49
+ module.exports.extractPluginName = extractPluginName;
50
+ }
51
+ });
52
+
53
+ // node_modules/fastify-plugin/lib/toCamelCase.js
54
+ var require_toCamelCase = __commonJS({
55
+ "node_modules/fastify-plugin/lib/toCamelCase.js"(exports$1, module) {
56
+ module.exports = function toCamelCase(name) {
57
+ if (name[0] === "@") {
58
+ name = name.slice(1).replace("/", "-");
59
+ }
60
+ const newName = name.replace(/-(.)/g, function(match, g1) {
61
+ return g1.toUpperCase();
62
+ });
63
+ return newName;
64
+ };
65
+ }
66
+ });
67
+
68
+ // node_modules/fastify-plugin/plugin.js
69
+ var require_plugin = __commonJS({
70
+ "node_modules/fastify-plugin/plugin.js"(exports$1, module) {
71
+ var getPluginName = require_getPluginName();
72
+ var toCamelCase = require_toCamelCase();
73
+ var count = 0;
74
+ function plugin(fn, options = {}) {
75
+ let autoName = false;
76
+ if (typeof fn.default !== "undefined") {
77
+ fn = fn.default;
78
+ }
79
+ if (typeof fn !== "function") {
80
+ throw new TypeError(
81
+ `fastify-plugin expects a function, instead got a '${typeof fn}'`
82
+ );
83
+ }
84
+ if (typeof options === "string") {
85
+ options = {
86
+ fastify: options
87
+ };
88
+ }
89
+ if (typeof options !== "object" || Array.isArray(options) || options === null) {
90
+ throw new TypeError("The options object should be an object");
91
+ }
92
+ if (options.fastify !== void 0 && typeof options.fastify !== "string") {
93
+ throw new TypeError(`fastify-plugin expects a version string, instead got '${typeof options.fastify}'`);
94
+ }
95
+ if (!options.name) {
96
+ autoName = true;
97
+ options.name = getPluginName(fn) + "-auto-" + count++;
98
+ }
99
+ fn[/* @__PURE__ */ Symbol.for("skip-override")] = options.encapsulate !== true;
100
+ fn[/* @__PURE__ */ Symbol.for("fastify.display-name")] = options.name;
101
+ fn[/* @__PURE__ */ Symbol.for("plugin-meta")] = options;
102
+ if (!fn.default) {
103
+ fn.default = fn;
104
+ }
105
+ const camelCase = toCamelCase(options.name);
106
+ if (!autoName && !fn[camelCase]) {
107
+ fn[camelCase] = fn;
108
+ }
109
+ return fn;
110
+ }
111
+ module.exports = plugin;
112
+ module.exports.default = plugin;
113
+ module.exports.fastifyPlugin = plugin;
114
+ }
115
+ });
116
+ var storage = new AsyncLocalStorage();
117
+ function runInStore(store, fn) {
118
+ return storage.run(store, fn);
119
+ }
120
+ function getStore() {
121
+ return storage.getStore();
122
+ }
123
+
124
+ // src/context.ts
125
+ var GET_CTX_ERROR = "[reqlocal] getCtx() called outside a request context. Make sure reqlocal middleware runs before your route handlers.";
126
+ function getCtx() {
127
+ const store = getStore();
128
+ if (store === void 0) {
129
+ throw new Error(GET_CTX_ERROR);
130
+ }
131
+ return store;
132
+ }
133
+ function getCtxOrNull() {
134
+ const store = getStore();
135
+ return store ?? null;
136
+ }
137
+ function runWithCtx(ctx, fn) {
138
+ return runInStore(ctx, () => fn());
139
+ }
140
+
141
+ // src/middleware.ts
142
+ function reqlocal(config) {
143
+ return (req, res, next) => {
144
+ const context = {};
145
+ for (const key of Object.keys(config)) {
146
+ context[key] = config[key](req);
147
+ }
148
+ runInStore(context, () => next());
149
+ };
150
+ }
151
+
152
+ // src/fastify.ts
153
+ var import_fastify_plugin = __toESM(require_plugin());
154
+ var reqlocalPluginImpl = async (fastify, opts) => {
155
+ const { config } = opts;
156
+ fastify.addHook("onRequest", (request, reply, done) => {
157
+ const context = {};
158
+ for (const key of Object.keys(config)) {
159
+ context[key] = config[key](request.raw);
160
+ }
161
+ runInStore(context, () => done());
162
+ });
163
+ };
164
+ var reqlocalPlugin = (0, import_fastify_plugin.default)(reqlocalPluginImpl, {
165
+ fastify: "4.x",
166
+ name: "reqlocal"
167
+ });
168
+
169
+ export { getCtx, getCtxOrNull, reqlocal, reqlocalPlugin, runWithCtx };
170
+ //# sourceMappingURL=index.mjs.map
171
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../node_modules/fastify-plugin/lib/getPluginName.js","../node_modules/fastify-plugin/lib/toCamelCase.js","../node_modules/fastify-plugin/plugin.js","../src/store.ts","../src/context.ts","../src/middleware.ts","../src/fastify.ts"],"names":["exports","fp"],"mappings":"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","file":"index.mjs","sourcesContent":["'use strict'\n\nconst fpStackTracePattern = /at\\s{1}(?:.*\\.)?plugin\\s{1}.*\\n\\s*(.*)/\nconst fileNamePattern = /(\\w*(\\.\\w*)*)\\..*/\n\nmodule.exports = function getPluginName (fn) {\n if (fn.name.length > 0) return fn.name\n\n const stackTraceLimit = Error.stackTraceLimit\n Error.stackTraceLimit = 10\n try {\n throw new Error('anonymous function')\n } catch (e) {\n Error.stackTraceLimit = stackTraceLimit\n return extractPluginName(e.stack)\n }\n}\n\nfunction extractPluginName (stack) {\n const m = stack.match(fpStackTracePattern)\n\n // get last section of path and match for filename\n return m ? m[1].split(/[/\\\\]/).slice(-1)[0].match(fileNamePattern)[1] : 'anonymous'\n}\nmodule.exports.extractPluginName = extractPluginName\n","'use strict'\n\nmodule.exports = function toCamelCase (name) {\n if (name[0] === '@') {\n name = name.slice(1).replace('/', '-')\n }\n const newName = name.replace(/-(.)/g, function (match, g1) {\n return g1.toUpperCase()\n })\n return newName\n}\n","'use strict'\n\nconst getPluginName = require('./lib/getPluginName')\nconst toCamelCase = require('./lib/toCamelCase')\n\nlet count = 0\n\nfunction plugin (fn, options = {}) {\n let autoName = false\n\n if (typeof fn.default !== 'undefined') {\n // Support for 'export default' behaviour in transpiled ECMAScript module\n fn = fn.default\n }\n\n if (typeof fn !== 'function') {\n throw new TypeError(\n `fastify-plugin expects a function, instead got a '${typeof fn}'`\n )\n }\n\n if (typeof options === 'string') {\n options = {\n fastify: options\n }\n }\n\n if (\n typeof options !== 'object' ||\n Array.isArray(options) ||\n options === null\n ) {\n throw new TypeError('The options object should be an object')\n }\n\n if (options.fastify !== undefined && typeof options.fastify !== 'string') {\n throw new TypeError(`fastify-plugin expects a version string, instead got '${typeof options.fastify}'`)\n }\n\n if (!options.name) {\n autoName = true\n options.name = getPluginName(fn) + '-auto-' + count++\n }\n\n fn[Symbol.for('skip-override')] = options.encapsulate !== true\n fn[Symbol.for('fastify.display-name')] = options.name\n fn[Symbol.for('plugin-meta')] = options\n\n // Faux modules support\n if (!fn.default) {\n fn.default = fn\n }\n\n // TypeScript support for named imports\n // See https://github.com/fastify/fastify/issues/2404 for more details\n // The type definitions would have to be update to match this.\n const camelCase = toCamelCase(options.name)\n if (!autoName && !fn[camelCase]) {\n fn[camelCase] = fn\n }\n\n return fn\n}\n\nmodule.exports = plugin\nmodule.exports.default = plugin\nmodule.exports.fastifyPlugin = plugin\n","import { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst storage = new AsyncLocalStorage<Record<string, unknown>>();\n\nexport function runInStore<T>(store: Record<string, unknown>, fn: () => T): T {\n return storage.run(store, fn);\n}\n\nexport function getStore<\n T extends Record<string, unknown> = Record<string, unknown>,\n>(): T | undefined {\n return storage.getStore() as T | undefined;\n}\n","import { getStore, runInStore } from \"./store.js\";\n\nconst GET_CTX_ERROR =\n \"[reqlocal] getCtx() called outside a request context. Make sure reqlocal middleware runs before your route handlers.\";\n\nexport function getCtx<\n T extends Record<string, unknown> = Record<string, unknown>,\n>(): T {\n const store = getStore<T>();\n if (store === undefined) {\n throw new Error(GET_CTX_ERROR);\n }\n return store;\n}\n\nexport function getCtxOrNull<\n T extends Record<string, unknown> = Record<string, unknown>,\n>(): T | null {\n const store = getStore<T>();\n return store ?? null;\n}\n\nexport function runWithCtx<TCtx extends Record<string, unknown>, TResult>(\n ctx: TCtx,\n fn: () => Promise<TResult>,\n): Promise<TResult> {\n return runInStore(ctx, () => fn());\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { runInStore } from \"./store.js\";\nimport type { ContextConfig, InferContext } from \"./types.js\";\n\nexport function reqlocal<C extends ContextConfig>(\n config: C,\n): (\n req: IncomingMessage,\n res: ServerResponse,\n next: (err?: unknown) => void,\n) => void {\n return (req, res, next) => {\n const context = {} as InferContext<C>;\n for (const key of Object.keys(config) as Array<keyof C>) {\n (context as Record<string, unknown>)[key as string] = config[key](req);\n }\n runInStore(context as Record<string, unknown>, () => next());\n };\n}\n","import type { FastifyPluginAsync } from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport { runInStore } from \"./store.js\";\nimport type { ContextConfig } from \"./types.js\";\n\nexport type ReqlocalFastifyOptions = {\n config: ContextConfig;\n};\n\nconst reqlocalPluginImpl: FastifyPluginAsync<ReqlocalFastifyOptions> = async (\n fastify,\n opts,\n) => {\n const { config } = opts;\n fastify.addHook(\"onRequest\", (request, reply, done) => {\n const context: Record<string, unknown> = {};\n for (const key of Object.keys(config)) {\n context[key] = config[key](request.raw);\n }\n runInStore(context, () => done());\n });\n};\n\nexport const reqlocalPlugin = fp(reqlocalPluginImpl, {\n fastify: \"4.x\",\n name: \"reqlocal\",\n});\n"]}
@@ -0,0 +1,18 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
2
+ <rect width="64" height="64" rx="16" fill="#0d1512"/>
3
+ <rect x="1" y="1" width="62" height="62" rx="15" stroke="url(#b)" stroke-opacity="0.35" stroke-width="2"/>
4
+ <path
5
+ d="M18 22h28v5H18v-5zm0 11h18v5H18v-5zm0 11h24v5H18v-5z"
6
+ fill="url(#g)"
7
+ />
8
+ <defs>
9
+ <linearGradient id="g" x1="18" y1="22" x2="46" y2="44" gradientUnits="userSpaceOnUse">
10
+ <stop stop-color="#2dd4bf"/>
11
+ <stop offset="1" stop-color="#34d399"/>
12
+ </linearGradient>
13
+ <linearGradient id="b" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse">
14
+ <stop stop-color="#2dd4bf"/>
15
+ <stop offset="1" stop-color="#34d399"/>
16
+ </linearGradient>
17
+ </defs>
18
+ </svg>
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "reqlocal",
3
+ "version": "0.1.0",
4
+ "description": "Request-scoped context for Node.js via AsyncLocalStorage — access typed context anywhere in the request lifecycle without parameter threading.",
5
+ "license": "MIT",
6
+ "author": "Abdul-Moiz31 (https://github.com/Abdul-Moiz31)",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Abdul-Moiz31/reqlocal.git"
10
+ },
11
+ "keywords": [
12
+ "async-local-storage",
13
+ "request-context",
14
+ "express",
15
+ "fastify",
16
+ "typescript"
17
+ ],
18
+ "type": "module",
19
+ "main": "./dist/index.js",
20
+ "module": "./dist/index.mjs",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.mjs",
26
+ "require": "./dist/index.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "docs/logo-mark.svg",
32
+ "README.md"
33
+ ],
34
+ "engines": {
35
+ "node": ">=16.0.0"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest"
41
+ },
42
+ "peerDependencies": {
43
+ "fastify": "^4.0.0"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "fastify": {
47
+ "optional": true
48
+ }
49
+ },
50
+ "devDependencies": {
51
+ "@types/express": "^4.17.21",
52
+ "@types/node": "^20.14.10",
53
+ "@types/supertest": "^6.0.2",
54
+ "express": "^4.19.2",
55
+ "fastify": "^4.28.1",
56
+ "fastify-plugin": "^4.5.1",
57
+ "supertest": "^7.0.0",
58
+ "tsup": "^8.1.0",
59
+ "typescript": "^5.5.3",
60
+ "vitest": "^2.0.3"
61
+ }
62
+ }