{"_id":"@ahsan_raza_syed/weave","name":"@ahsan_raza_syed/weave","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@ahsan_raza_syed/weave","version":"0.1.0","description":"Context that just works. Logs that actually tell the story.","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./adapters":{"types":"./dist/adapters/index.d.ts","import":"./dist/adapters/index.js","require":"./dist/adapters/index.cjs"}},"sideEffects":false,"scripts":{"build":"tsup src/index.ts src/adapters/index.ts --format esm,cjs --dts","trace-story":"node scripts/trace-story.mjs","dev":"tsup src/index.ts --format esm,cjs --dts --watch","test":"vitest run","lint":"tsc --noEmit","prepublishOnly":"npm run build"},"keywords":["async-context","logging","tracing","typescript","observability","abortsignal","timeout","edge-runtime"],"license":"MIT","homepage":"https://www.npmjs.com/package/@ahsan_raza_syed/weave","bugs":{"url":"https://github.com/ahsan3219/weave/issues"},"repository":{"type":"git","url":"git+https://github.com/ahsan3219/weave.git"},"publishConfig":{"access":"public"},"engines":{"node":">=18"},"devDependencies":{"@types/node":"^22.13.10","tsup":"^8.4.0","typescript":"^5.8.2","vitest":"^3.0.8"},"_id":"@ahsan_raza_syed/weave@0.1.0","gitHead":"e942ec5eaf203a66ded04ca9c6d5e6f01d486157","_nodeVersion":"22.19.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-EJyBmzp4DHrRbubLqWKVVstpJ9+uhngZpc7A3mMSQAumZz6bCM1FHffhCE9PWja6Ql5G5uvBAd+K0wmEkO3k+Q==","shasum":"03373c7acf6d27eaa28c19f054897a0ecae06353","tarball":"https://registry.npmjs.org/@ahsan_raza_syed/weave/-/weave-0.1.0.tgz","fileCount":13,"unpackedSize":67239,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICgPCHQtZTm7+gCU5eYEv+7ohl3u6ZbSONxC6g5I/+lkAiEA5GB/iTVdsGQyKozGXbstBtpK+D13P00ijLPHrC/MLq4="}]},"_npmUser":{"name":"ahsan_raza_syed","email":"ahsanrazasyedahsan@gmail.com"},"directories":{},"maintainers":[{"name":"ahsan_raza_syed","email":"ahsanrazasyedahsan@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/weave_0.1.0_1773281902587_0.54466825991159"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-12T02:18:22.528Z","0.1.0":"2026-03-12T02:18:22.724Z","modified":"2026-03-12T02:18:22.895Z"},"maintainers":[{"name":"ahsan_raza_syed","email":"ahsanrazasyedahsan@gmail.com"}],"description":"Context that just works. Logs that actually tell the story.","homepage":"https://www.npmjs.com/package/@ahsan_raza_syed/weave","keywords":["async-context","logging","tracing","typescript","observability","abortsignal","timeout","edge-runtime"],"repository":{"type":"git","url":"git+https://github.com/ahsan3219/weave.git"},"bugs":{"url":"https://github.com/ahsan3219/weave/issues"},"license":"MIT","readme":"# weave\r\n\r\n**Context that just works. Logs that actually tell the story. Tracing without ceremony.**\r\n\r\n`weave` is a tiny, zero-dependency, TypeScript-first async context + structured logging + lightweight tracing library for Node.js, Bun, Deno, browsers, and edge runtimes.\r\n\r\n[![npm version](https://img.shields.io/npm/v/@ahsan_raza_syed/weave.svg)](https://www.npmjs.com/package/@ahsan_raza_syed/weave)\r\n[![npm downloads](https://img.shields.io/npm/dm/@ahsan_raza_syed/weave.svg)](https://www.npmjs.com/package/@ahsan_raza_syed/weave)\r\n\r\n## Why weave?\r\n\r\nEvery backend developer hits the same problems:\r\n\r\n1. **\"Which log belongs to which request?\"** — Logs are individual lines. Reconstructing the story of a single request means grepping by some ID you hopefully remembered to pass everywhere.\r\n2. **\"Context vanishes in callbacks\"** — You set `requestId` at the top, then `setTimeout`, `Promise.then`, or `fetch` loses it.\r\n3. **\"The request just hung. No error. No timeout.\"** — Nothing aborted it because nobody wired up `AbortSignal` or a timeout.\r\n\r\n`weave` solves all three with a single `create` → `run` → done workflow, automatic global patching so context survives timers/promises/fetch, and built-in timeouts and cancellation.\r\n\r\n## Install\r\n\r\n```bash\r\nnpm i @ahsan_raza_syed/weave\r\n```\r\n\r\n## Quick start\r\n\r\n```ts\r\nimport { weave } from '@ahsan_raza_syed/weave';\r\n\r\nconst ctx = weave.create({\r\n  requestId: crypto.randomUUID(),\r\n  userId: 'u_123',\r\n  traceId: weave.autoTraceId()\r\n});\r\n\r\nawait weave.runScoped(ctx, async () => {\r\n  ctx.log.info('Request started');\r\n\r\n  const span = ctx.startSpan('db.query');\r\n  await db.users.findById('u_123');\r\n  span.end({ rows: 1 });\r\n\r\n  setTimeout(() => {\r\n    // context is still here — weave patches timers automatically\r\n    ctx.log.success('Async callback', { requestId: weave.current?.values.requestId });\r\n  }, 10);\r\n}, { timeoutMs: 30_000 }); // request aborts after 30s\r\n```\r\n\r\n## Core concepts\r\n\r\n### Context\r\n\r\nA context is a bag of key-value pairs (`requestId`, `traceId`, `userId`, ...) plus a logger, an `AbortSignal`, and lifecycle hooks.\r\n\r\n```ts\r\nconst ctx = weave.create({ requestId: 'r1', traceId: weave.autoTraceId() });\r\n\r\n// Child context — inherits parent values, adds new ones, linked signal\r\nconst child = ctx.child({ userId: 'u_42' });\r\nchild.values.requestId; // 'r1' — inherited\r\nchild.values.userId;    // 'u_42' — added\r\n```\r\n\r\n### Running in context\r\n\r\n```ts\r\n// Synchronous or async — context is available via weave.current inside fn\r\nweave.run(ctx, () => {\r\n  weave.current?.values.requestId; // 'r1'\r\n});\r\n\r\n// Scoped — runs cleanups after fn resolves/rejects + optional timeout\r\nawait weave.runScoped(ctx, handler, { timeoutMs: 30_000 });\r\n```\r\n\r\n### Spans (lightweight tracing)\r\n\r\n```ts\r\nconst span = ctx.startSpan('http.fetch', { url: '/api/data' });\r\nawait fetch('/api/data');\r\nspan.end({ status: 200 }); // logs duration, attributes, spanId, parentId, traceId\r\n\r\n// Nested spans get automatic parent linkage\r\nconst parent = ctx.startSpan('handler');\r\nconst child = parent.context.startSpan('db.query');\r\nchild.parentId === parent.id; // true\r\n```\r\n\r\n### Structured logging\r\n\r\nEvery log call includes context values automatically:\r\n\r\n```ts\r\nctx.log.debug('cache miss');\r\nctx.log.info('user loaded', { userId: 'u_42' });\r\nctx.log.warn('rate limit near', { remaining: 5 });\r\nctx.log.error('payment failed', { code: 'CARD_DECLINED' });\r\nctx.log.success('order placed', { orderId: 'ord_1' });\r\n```\r\n\r\nIn development, output is colorized. In production (`NODE_ENV=production`), output is one JSON line per call:\r\n\r\n```json\r\n{\"level\":\"info\",\"message\":\"user loaded\",\"timestamp\":\"2025-03-12T00:00:00.000Z\",\"requestId\":\"r1\",\"traceId\":\"t1\",\"userId\":\"u_42\"}\r\n```\r\n\r\n### Secret redaction\r\n\r\nKeys like `password`, `token`, `authorization`, `apiKey`, `secret` are automatically redacted. Token patterns (`sk_*`, `ghp_*`) and JWTs are detected in values:\r\n\r\n```ts\r\nctx.log.info('auth', { token: 'sk_live_abc123' });\r\n// logs: { token: '[REDACTED]' }\r\n\r\nctx.log.info('jwt', { value: 'eyJhbGci.eyJzdWIi.sig' });\r\n// logs: { value: '[REDACTED_JWT]' }\r\n```\r\n\r\n### Timeout and cancellation\r\n\r\n```ts\r\n// Timeout a specific task\r\nconst data = await weave.withTimeout('api-call', 5000, async (signal) => {\r\n  return fetch('/api/slow', { signal });\r\n});\r\n\r\n// Cancel the current context tree\r\nweave.run(ctx, () => {\r\n  weave.cancel('user navigated away');\r\n  ctx.signal.aborted; // true — all child contexts are also aborted\r\n});\r\n```\r\n\r\n### Guard (error boundary)\r\n\r\n```ts\r\nconst safeFn = weave.run(ctx, () =>\r\n  weave.guard('payment', async () => {\r\n    await chargeCard();\r\n  })\r\n);\r\n\r\nawait safeFn(); // on error: logs with context, then rethrows\r\n```\r\n\r\n### Snapshot and restore (cross-boundary)\r\n\r\n```ts\r\n// Serialize context for worker/queue/process boundary\r\nconst snap = ctx.snapshot(); // { id, values }\r\nconst json = JSON.stringify(snap);\r\n\r\n// On the other side:\r\nconst restored = weave.fromSnapshot(JSON.parse(json));\r\nrestored.values.requestId; // preserved\r\n```\r\n\r\n### Trace ID on response\r\n\r\nSo clients or support can reference a specific request:\r\n\r\n```ts\r\nweave.run(ctx, () => {\r\n  weave.setTraceIdOnResponse(res); // Node res.setHeader or Web Response.headers\r\n});\r\n```\r\n\r\n## Framework adapters\r\n\r\nOne-line middleware. No peer dependencies — compatible with minimal request/response shapes.\r\n\r\n### Express\r\n\r\n```ts\r\nimport express from 'express';\r\nimport { weave } from '@ahsan_raza_syed/weave';\r\nimport { weaveExpress } from '@ahsan_raza_syed/weave/adapters';\r\n\r\nconst app = express();\r\napp.use(weaveExpress({ timeoutMs: 30_000 }));\r\n\r\napp.get('/api/data', (req, res) => {\r\n  weave.current?.log.info('handling request');\r\n  res.json({ ok: true });\r\n});\r\n```\r\n\r\n### Fastify\r\n\r\n```ts\r\nimport Fastify from 'fastify';\r\nimport { weave } from '@ahsan_raza_syed/weave';\r\nimport { weaveFastify } from '@ahsan_raza_syed/weave/adapters';\r\n\r\nconst fastify = Fastify();\r\nawait fastify.register(weaveFastify());\r\n\r\nfastify.get('/api/data', async (request, reply) => {\r\n  const ctx = (request as any).weaveContext;\r\n  return weave.run(ctx, () => {\r\n    ctx.log.info('handling request');\r\n    return { ok: true };\r\n  });\r\n});\r\n```\r\n\r\n### Hono\r\n\r\n```ts\r\nimport { Hono } from 'hono';\r\nimport { weave } from '@ahsan_raza_syed/weave';\r\nimport { weaveHono } from '@ahsan_raza_syed/weave/adapters';\r\n\r\nconst app = new Hono();\r\napp.use('*', weaveHono({ timeoutMs: 30_000 }));\r\n\r\napp.get('/api/data', (c) => {\r\n  weave.current?.log.info('handling request');\r\n  return c.json({ ok: true });\r\n});\r\n```\r\n\r\n### Next.js (App Router)\r\n\r\n```ts\r\nimport { weave } from '@ahsan_raza_syed/weave';\r\nimport { withWeaveNext } from '@ahsan_raza_syed/weave/adapters';\r\n\r\nexport const GET = withWeaveNext(async (request) => {\r\n  weave.current?.log.info('handling request');\r\n  return Response.json({ ok: true });\r\n}, { timeoutMs: 10_000 });\r\n```\r\n\r\nAll adapters read an incoming `x-weave-trace-id` header (configurable via `headerName`) and set it on the response.\r\n\r\n## Trace story script\r\n\r\nFilter JSON logs by `traceId` and print in timestamp order:\r\n\r\n```bash\r\ncat logs.jsonl | node scripts/trace-story.mjs <traceId>\r\n```\r\n\r\nUse the trace ID from the response header to reconstruct \"what happened for this request\" from your log stream.\r\n\r\n## API reference\r\n\r\n| Method | Description |\r\n|--------|-------------|\r\n| `weave.create(values, options?)` | Create a new context. Patches globals on first call. |\r\n| `weave.run(ctx, fn)` | Run `fn` with `ctx` as the active context. |\r\n| `weave.runScoped(ctx, fn, { timeoutMs? })` | Run `fn`, then run cleanups. Optional timeout. |\r\n| `weave.bind(fn)` | Bind `fn` to the current context for later invocation. |\r\n| `weave.guard(name, fn)` | Wrap `fn` to log errors with context before rethrowing. |\r\n| `weave.withTimeout(label, ms, task)` | Run `task(signal)` with a timeout. |\r\n| `weave.cancel(reason?)` | Abort the current context's signal (cascades to children). |\r\n| `weave.setTraceIdOnResponse(res, headerName?)` | Set trace ID on a Node or Web response. |\r\n| `weave.fromSnapshot(snapshot, options?)` | Restore a context from a serialized snapshot. |\r\n| `weave.current` | The currently active context, or `undefined`. |\r\n| `weave.autoTraceId()` | Generate a trace ID (`crypto.randomUUID` or fallback). |\r\n\r\n**Context methods:** `ctx.child(values)`, `ctx.startSpan(name, attrs?)`, `ctx.onCleanup(fn)`, `ctx.snapshot()`, `ctx.log.*`, `ctx.signal`.\r\n\r\n**Options:** `{ redactKeys?: string[], enablePatching?: boolean, headerName?: string }`.\r\n\r\n**Exported types:** `WeaveContext`, `WeaveSnapshot`, `WeaveLogger`, `WeaveSpan`, `WeaveLogPayload`, `WeaveOptions`, `ContextRecord`.\r\n\r\n## How it works\r\n\r\nOn the first `weave.create()`, weave patches `setTimeout`, `setInterval`, `queueMicrotask`, `Promise.prototype.then/catch/finally`, `EventTarget.addEventListener`, and `fetch` so that the active context propagates into all async continuations. Disable with `enablePatching: false`.\r\n\r\nContext is stored in a module-level variable and swapped in/out by `withContext` (not `AsyncLocalStorage`) so it works in browsers and edge runtimes too.\r\n\r\n## Context checklist\r\n\r\nQuick checklist for \"one story per request\":\r\n\r\n- [ ] Create a context per request with `weave.create({ requestId, traceId })` or use a framework adapter.\r\n- [ ] Run handlers inside `weave.run(ctx, fn)` or `weave.runScoped(ctx, fn)`.\r\n- [ ] Set a default timeout: `weave.runScoped(ctx, fn, { timeoutMs: 30_000 })`.\r\n- [ ] Put the trace ID on the response: `weave.setTraceIdOnResponse(res)`.\r\n- [ ] In production, pipe JSON logs to your aggregator and filter by `traceId`.\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md","_rev":"1-3b8144f48cf0d20cbddc74f7edeb3f96"}