{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "plugin-fastify", "title": "Fastify Plugin", "description": "Fastify boundary Plugin spanning request hooks through final onResponse status.", "type": "registry:lib", "dependencies": [ "fastify@^5.2.1", "@useamplio/amplio@^0.1.0-alpha.17" ], "registryDependencies": [ "@useamplio/event-http-request", "@useamplio/runtime" ], "meta": { "amplio": { "kind": "plugin", "role": "boundary", "recipeVersion": "1.0.0", "coreRange": ">=0.1.0-alpha.17 <1", "providerRanges": { "fastify": ">=5 <6" }, "testedProviderVersions": { "fastify": { "minimum": "5.0.0", "latest": "5.12.0" } }, "events": [ { "id": "http.request", "version": 1, "semanticDigest": "sha256-0907ef8a9cc8957a6e1b7fc6038d832319dc7a0c2f2b32764d6e2e9fb4f474ce" } ], "semanticDigest": "sha256-166a77128834d0958ea50d1aa95bd768dda65c16e43d55076383bcd5ea4994c8", "nativeTransform": { "version": 1, "digest": "sha256-cfebb520d2e7c9f1b35cb0569a8a9c8babd2183e7b5a711876ae52541cbd8698" }, "wiringActions": [ { "type": "register-boundary", "export": "FastifyPlugin", "description": "Register once before ordinary Fastify routes." } ], "privacy": { "includes": [ "request_id", "http.method", "http.route", "http.status" ], "excludes": [ "url", "path", "query", "headers", "cookies", "body" ] } } }, "files": [ { "path": "registry/plugins/fastify.ts", "target": "~/telemetry/plugins/fastify.ts", "type": "registry:lib", "content": "import \"../runtime.js\";\n\nimport { openEvent, type EventScope } from \"@useamplio/amplio/plugin\";\nimport type { FastifyError, FastifyPluginAsync, FastifyRequest } from \"fastify\";\nimport { HttpRequest, resolveRequestId } from \"../events/http-request.js\";\n\ninterface PendingRequest {\n readonly scope: EventScope;\n error?: FastifyError;\n}\n\n/** Register once before routes; ordinary Fastify handlers remain untouched. */\nconst implementation: FastifyPluginAsync = async (app) => {\n const pending = new WeakMap();\n\n app.addHook(\"onRequest\", (request, _reply, done) => {\n const scope = openEvent(HttpRequest, {\n request_id: resolveRequestId(request.headers[\"x-request-id\"]),\n http: { method: request.method },\n });\n pending.set(request, { scope });\n scope.run(done);\n });\n\n app.addHook(\"onError\", (request, _reply, error, done) => {\n const state = pending.get(request);\n if (state) state.error = error;\n done();\n });\n\n app.addHook(\"onResponse\", (request, reply, done) => {\n const state = pending.get(request);\n pending.delete(request);\n if (state) {\n const output = {\n http: {\n route: request.routeOptions.url,\n status: reply.statusCode,\n },\n };\n if (state.error) state.scope.fail(state.error, output);\n else {\n state.scope.finish(output, { success: reply.statusCode < 400 });\n }\n }\n done();\n });\n\n app.addHook(\"onRequestAbort\", (request, done) => {\n const state = pending.get(request);\n pending.delete(request);\n if (state) {\n state.scope.update({\n http: { route: request.routeOptions.url },\n });\n state.scope.cancel(\"request_aborted\");\n }\n done();\n });\n};\n\n// Fastify uses this documented symbol to expose lifecycle hooks outside the\n// registration scope, which is exactly what a composition-root Plugin needs.\nObject.defineProperties(implementation, {\n [Symbol.for(\"skip-override\")]: { value: true },\n [Symbol.for(\"fastify.display-name\")]: { value: \"amplio-fastify\" },\n});\n\nexport const FastifyPlugin = implementation;\n" } ] }