{"id":23787885,"url":"https://github.com/dab0mb/async-store","last_synced_at":"2025-07-08T11:39:00.444Z","repository":{"id":270340322,"uuid":"909465401","full_name":"DAB0mB/async-store","owner":"DAB0mB","description":null,"archived":false,"fork":false,"pushed_at":"2024-12-30T11:55:28.000Z","size":50,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-21T11:48:57.516Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DAB0mB.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-28T19:33:34.000Z","updated_at":"2024-12-30T11:55:31.000Z","dependencies_parsed_at":null,"dependency_job_id":"ef57fc30-712f-4634-919f-f8c8eaf50dc0","html_url":"https://github.com/DAB0mB/async-store","commit_stats":null,"previous_names":["dab0mb/async-store"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/DAB0mB/async-store","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DAB0mB%2Fasync-store","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DAB0mB%2Fasync-store/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DAB0mB%2Fasync-store/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DAB0mB%2Fasync-store/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DAB0mB","download_url":"https://codeload.github.com/DAB0mB/async-store/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DAB0mB%2Fasync-store/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264261052,"owners_count":23581143,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-01T15:28:29.086Z","updated_at":"2025-07-08T11:39:00.431Z","avatar_url":"https://github.com/DAB0mB.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Async Store\n\nAsync Store is a small wrap around Node's [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html) API that lets you define Async Vars. An Async Var is an object which holds a value that's bound to the current execution context. In other words, it enables dependency injection with minimal overhead.\n\nYou can get started by installing `async_store` via NPM:\n\n```sh\nnpm i --save-exact async_store@npm:@dabomb/async_store\n```\n\n## Usage Examlpes\n\n### Logging\n\nUse a file logger in development, otherwise use a Kafka logger.\n\n```ts\n/* main.ts */\n\nimport { AsyncScope } from 'async_store';\nimport { app } from './app';\nimport { logger } from './logger';\n\nasync function main() {\n  await new AsycScope().run(async () =\u003e {\n    await logger.init();\n\n    try {\n      await app.run();\n    }\n    catch (error) {\n      logger.error(error);\n      process.exitCode ??= 1;\n    }\n  });\n}\n\nvoid main();\n```\n```ts\n/* app/index.ts */\n\nimport { createServer } from 'http';\nimport { logger } from './logger';\n\nexport const app = {\n  run() {\n    const server = createServer(router);\n\n    server.listen(process.env.PORT, process.env.HOST, () =\u003e {\n      logger.info(`Server is listening on ${process.env.HOST}:${process.env.PORT}`);\n    });\n\n    return new Promise((resolve, reject) =\u003e {\n      server.on('close', resolve);\n      server.on('error', reject);\n    });\n  }\n};\n\nfunction router(req: http.IncomingMessage, res: http.ServerResponse) {\n  logger.info('Incoming request', req.socket.address());\n\n  // handle request ...\n}\n```\n```ts\n/* logger/index.ts */\n\nimport { AsyncVar } from 'async_store';\nimport { ILogger } from './logger';\n\nconst LoggerVar = new AsyncVar\u003cILogger\u003e('Logger');\n\nexport const logger = {\n  init() {\n    let logger: ILogger;\n\n    if (process.env.NODE_ENV === 'development') {\n      const { FileLogger } = await import('./file_logger');\n      logger = new FileLogger();\n    }\n    else {\n      const { KafkaLogger } = await import('./kafka_logger');\n      logger = new KafkaLogger();\n    }\n\n    LoggerVar.set(logger);\n  },\n\n  info(message: string) {\n    LoggerVar.get().info(message);\n  },\n\n  warn(message: string) {\n    LoggerVar.get().warn(message);\n  },\n\n  error(message: string) {\n    LoggerVar.get().error(message);\n  },\n}\n```\n\n### Routing\n\nExtract account data from current request.\n\n```ts\n/* app.ts */\n\nimport express from 'express';\nimport { AsyncScope } from 'async_store';\nimport { authRouter } from './routers/auth_router';\nimport { accountRouter } from './routers/account_router';\nimport { publicRouter } from './routers/public_router';\n\nexport const app = express();\n\napp.use((req, res, next) =\u003e {\n  new AsyncScope().run(next);\n});\n\nappRouter.use('/auth', authRouter);\nappRouter.use('/account', accountRouter);\nappRouter.use('/public', publicRouter);\n```\n```ts\n/* routers/account_router.ts */\n\nimport { Router } from 'express';\nimport { auth } from '../auth';\n\nexport const accountRouter = Router();\n\naccountRouter.use((req, res, next) =\u003e {\n  auth.init(req);\n\n  if (!auth.account) {\n    res.status(401);\n    return;\n  }\n\n  next();\n})\n\naccountRouter.get('/', (req, res) =\u003e {\n  res.json(auth.account);\n});\n\n// more routes ...\n```\n```ts\n/* auth.ts */\n\nimport { AsyncVar } from 'async_store';\nimport { parse as parseCookie } from 'cookie';\nimport * as http from 'http';\nimport { Account } from './accout';\n\nconst AuthVar = new AsyncVar\u003c{ account: Account }\u003e('Auth');\n\nconst auth = {\n  get account() {\n    return AuthVar.exists() ? AuthVar.get().account : null;\n  },\n\n  init(req: http.IncomingMessage) {\n    const cookieHeader = req.headers['cookie'];\n    if (!cookieHeader) return null;\n\n    const cookies = parseCookie(cookieHeader);\n    const jwt = cookies['jwt'];\n    if (!jwt) return null;\n\n    const payload = jwt.split('.')[1];\n    if (!payload) return null;\n\n    const decodedPayload = Buffer.from(payload, 'base64').toString('utf-8');\n    const account = Account.parse(JSON.parse(decodedPayload));\n\n    Auth.set({ account });\n  },\n}\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdab0mb%2Fasync-store","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdab0mb%2Fasync-store","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdab0mb%2Fasync-store/lists"}