@silkweave/fastify 5.0.1 → 5.2.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/build/index.d.mts CHANGED
@@ -3,7 +3,6 @@ import { AdapterFactory } from "@silkweave/core";
3
3
  import { FastifyBaseLogger, FastifyHttpOptions } from "fastify";
4
4
  import { FastifyCorsOptions, FastifyCorsOptions as FastifyCorsOptions$1 } from "@fastify/cors";
5
5
  import { Server } from "http";
6
-
7
6
  //#region src/adapter/fastify.d.ts
8
7
  interface FastifyAdapterOptions extends FastifyHttpOptions<Server, FastifyBaseLogger> {
9
8
  host?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/fastify.ts"],"mappings":";;;;;;;UAqBiB,qBAAA,SAA8B,kBAAA,CAAmB,MAAA,EAAQ,iBAAA;EACxE,IAAA;EACA,IAAA;EACA,IAAA,GAAO,UAAA;;EAEP,IAAA,GAAO,oBAAA;AAAA;AAAA,cAsTI,OAAA,EAAS,cAAA,CAAe,qBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/fastify.ts"],"mappings":";;;;;;UA4DiB,8BAA8B,mBAAmB,QAAQ;EACxE;EACA;EACA,OAAO;;EAEP,OAAO;;cA2WI,SAAS,eAAe"}
package/build/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { generateProtectedResourceMetadata, validateToken } from "@silkweave/auth";
1
+ import { PROTECTED_RESOURCE_WELL_KNOWN, generateProtectedResourceMetadata, resolveProtectedResourceMetadata, toResourceRequest, validateToken } from "@silkweave/auth";
2
2
  import { SilkweaveError, actionMethod, binarySchemaMeta, buildLogLevels, isBinarySchema, isStreamingAction, methodHasBody, pathParamNames, resolveActionInput, resourceBytes, runStreamingAction, toActionResource, validateActionRouting } from "@silkweave/core";
3
3
  import { once } from "events";
4
4
  import { fastify as fastify$1 } from "fastify";
@@ -29,7 +29,7 @@ function sendOAuth(reply, oauthRes) {
29
29
  function mountOAuthRoutes(instance, auth) {
30
30
  const provider = auth.provider;
31
31
  const callbackPath = auth.callbackPath ?? "/auth/callback";
32
- const paths = new Set([
32
+ const paths = /* @__PURE__ */ new Set([
33
33
  "/.well-known/oauth-authorization-server",
34
34
  "/authorize",
35
35
  callbackPath,
@@ -306,9 +306,22 @@ const fastify = ({ host, port, auth, cors: corsConfig, ...fastifyOptions }) => {
306
306
  version: options.version
307
307
  } } });
308
308
  await registerScalarDocs(instance);
309
- if (auth?.authorizationServers?.length && auth.resourceUrl) instance.get("/.well-known/oauth-protected-resource", () => {
310
- return generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers, auth.requiredScopes);
311
- });
309
+ if (auth?.authorizationServers?.length && typeof auth.resourceUrl === "string") {
310
+ const metadata = generateProtectedResourceMetadata(auth.resourceUrl, auth.authorizationServers, auth.requiredScopes);
311
+ instance.get(PROTECTED_RESOURCE_WELL_KNOWN, () => metadata);
312
+ } else if (auth?.authorizationServers?.length && typeof auth.resourceUrl === "function") {
313
+ const resolve = (request, reply) => {
314
+ const resourceRequest = toResourceRequest(context.fork({ request }));
315
+ const metadata = resourceRequest && resolveProtectedResourceMetadata(auth, resourceRequest, context);
316
+ if (!metadata) return reply.status(404).send({
317
+ error: "not_found",
318
+ error_description: "Unknown protected resource"
319
+ });
320
+ return metadata;
321
+ };
322
+ instance.get(PROTECTED_RESOURCE_WELL_KNOWN, resolve);
323
+ instance.get(`${PROTECTED_RESOURCE_WELL_KNOWN}/*`, resolve);
324
+ }
312
325
  const oauthPaths = auth?.provider ? mountOAuthRoutes(instance, auth) : /* @__PURE__ */ new Set();
313
326
  if (auth) mountAuthMiddleware(instance, auth, oauthPaths, context);
314
327
  instance.setErrorHandler(errorHandler(instance));
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["fastifyInstance"],"sources":["../src/adapter/fastify.ts"],"sourcesContent":["import type { FastifyCorsOptions } from '@fastify/cors'\nimport { AuthConfig, AuthInfo, generateProtectedResourceMetadata, OAuthRequest, OAuthResponse, validateToken } from '@silkweave/auth'\nimport { Action, actionMethod, ActionStreamRun, AdapterFactory, binarySchemaMeta, buildLogLevels, HttpMethod, isBinarySchema, isStreamingAction, Logger, LogLevel, methodHasBody, pathParamNames, resolveActionInput, resourceBytes, runStreamingAction, SilkweaveContext, SilkweaveError, toActionResource, validateActionRouting, type ActionResource } from '@silkweave/core'\nimport { once } from 'events'\nimport { FastifyBaseLogger, FastifyHttpOptions, fastify as fastifyInstance, FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'\nimport { Server } from 'http'\nimport z from 'zod/v4'\n\ntype FastifyLogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace'\n\nconst PINO_LEVEL_MAP: Record<LogLevel, FastifyLogLevel> = {\n emergency: 'fatal',\n alert: 'fatal',\n critical: 'fatal',\n error: 'error',\n warning: 'warn',\n notice: 'debug',\n info: 'info',\n debug: 'debug'\n}\n\nexport interface FastifyAdapterOptions extends FastifyHttpOptions<Server, FastifyBaseLogger> {\n host?: string\n port?: number\n auth?: AuthConfig\n /** CORS configuration. `false` to disable, `true`/`undefined` for permissive defaults, or a FastifyCorsOptions object. */\n cors?: FastifyCorsOptions | boolean\n}\n\nfunction toOAuthReq(request: FastifyRequest): OAuthRequest {\n return {\n method: request.method,\n url: new URL(request.url, `http://${request.hostname}`),\n headers: Object.fromEntries(Object.entries(request.headers).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v])),\n body: request.body as Record<string, string> | undefined\n }\n}\n\nfunction sendOAuth(reply: FastifyReply, oauthRes: OAuthResponse) {\n for (const [key, value] of Object.entries(oauthRes.headers)) { reply.header(key, value) }\n return reply.status(oauthRes.status).send(oauthRes.body)\n}\n\nfunction mountOAuthRoutes(instance: FastifyInstance, auth: AuthConfig): Set<string> {\n const provider = auth.provider!\n // The provider builds its upstream redirect_uri as `${resourceUrl}${callbackPath}`,\n // so the callback route must match the configured callbackPath, not a hardcoded one.\n const callbackPath = auth.callbackPath ?? '/auth/callback'\n\n const paths = new Set([\n '/.well-known/oauth-authorization-server',\n '/authorize',\n callbackPath,\n '/token',\n '/register'\n ])\n\n instance.get('/.well-known/oauth-authorization-server', async (_req, reply) => {\n return sendOAuth(reply, provider.metadata())\n })\n instance.get('/authorize', async (req, reply) => {\n return sendOAuth(reply, await provider.authorize(toOAuthReq(req)))\n })\n instance.get(callbackPath, async (req, reply) => {\n return sendOAuth(reply, await provider.callback(toOAuthReq(req)))\n })\n instance.post('/token', async (req, reply) => {\n return sendOAuth(reply, await provider.token(toOAuthReq(req)))\n })\n instance.post('/register', async (req, reply) => {\n return sendOAuth(reply, await provider.register(toOAuthReq(req)))\n })\n\n return paths\n}\n\nfunction mountAuthMiddleware(instance: FastifyInstance, auth: AuthConfig, oauthPaths: Set<string>, context: SilkweaveContext) {\n instance.decorateRequest('__silkweave_auth', undefined)\n instance.addHook('onRequest', async (request: FastifyRequest, reply: FastifyReply) => {\n if (request.url.startsWith('/.well-known/') || oauthPaths.has(request.url.split('?')[0])) { return }\n const result = await validateToken(request.headers.authorization, auth, context.fork({ request }))\n if (result.error) {\n for (const [key, value] of Object.entries(result.error.headers)) {\n reply.header(key, value)\n }\n return reply.status(result.error.statusCode).send(result.error.body)\n }\n if (result.auth) {\n (request as FastifyRequest & { __silkweave_auth?: AuthInfo }).__silkweave_auth = result.auth\n }\n })\n}\n\ntype StreamFormat = 'sse' | 'ndjson'\n\nfunction pickStreamFormat(acceptHeader: string | undefined): StreamFormat | null {\n if (!acceptHeader) { return null }\n if (acceptHeader.includes('text/event-stream')) { return 'sse' }\n if (acceptHeader.includes('application/x-ndjson') || acceptHeader.includes('application/ndjson')) { return 'ndjson' }\n return null\n}\n\nfunction encodeChunk(format: StreamFormat, chunk: unknown): string {\n const payload = JSON.stringify(chunk)\n return format === 'sse' ? `data: ${payload}\\n\\n` : `${payload}\\n`\n}\n\nfunction encodeStreamError(format: StreamFormat, error: unknown): string {\n const payload = JSON.stringify({\n error: error instanceof Error ? error.message : String(error),\n name: error instanceof Error ? error.name : 'Error'\n })\n return format === 'sse' ? `event: error\\ndata: ${payload}\\n\\n` : `${payload}\\n`\n}\n\nasync function streamAction(\n reply: FastifyReply,\n format: StreamFormat,\n action: Action,\n input: object,\n context: SilkweaveContext\n): Promise<void> {\n reply.raw.setHeader('Content-Type', format === 'sse' ? 'text/event-stream' : 'application/x-ndjson')\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform')\n reply.raw.setHeader('Connection', 'keep-alive')\n reply.raw.flushHeaders?.()\n reply.hijack()\n const raw = reply.raw\n const iter = (action.run as ActionStreamRun<object, unknown>)(input, context)\n // On client disconnect the next write() returns false and a plain\n // `once(raw, 'drain')` would park forever - the generator would never resume,\n // its finally/cleanup would never run, and response/context stay pinned. Track\n // 'close' and race the drain against it so we bail and tear the generator down.\n const gone = { value: raw.destroyed || raw.writableEnded }\n const onClose = () => { gone.value = true }\n raw.on('close', onClose)\n const closed = once(raw, 'close')\n try {\n for await (const chunk of iter) {\n if (gone.value) { break }\n if (!raw.write(encodeChunk(format, chunk))) {\n await Promise.race([once(raw, 'drain'), closed])\n if (gone.value) { break }\n }\n }\n if (!gone.value && format === 'sse') { raw.write('event: done\\ndata: {}\\n\\n') }\n } catch (error) {\n if (!gone.value) { raw.write(encodeStreamError(format, error)) }\n } finally {\n raw.off('close', onClose)\n // Ask the generator to run its finally/cleanup (release DB handles etc).\n await iter.return?.().catch(() => { /* generator cleanup best-effort */ })\n if (!raw.writableEnded) { raw.end() }\n }\n}\n\nfunction createActionLogger(instance: FastifyInstance): Logger {\n return {\n ...buildLogLevels((level, data) => {\n const pinoLevel = PINO_LEVEL_MAP[level] ?? 'info'\n instance.log[pinoLevel](data)\n }),\n progress: ({ progress, total, message }) => { instance.log.trace({ progress, total }, message) }\n }\n}\n\ninterface ObjectSchema {\n type: 'object'\n properties: Record<string, unknown>\n required: string[]\n}\n\n/**\n * Split an action's input JSON Schema into Fastify `params`/`querystring`/`body`\n * schemas based on its `path` placeholders and `queryParams`. Fastify's own AJV\n * validation, type coercion, and default-filling then apply per source, and the\n * three are merged back into a single input by `resolveActionInput` at runtime.\n */\nfunction splitInputSchema(action: Action, method: HttpMethod): {\n params?: ObjectSchema\n querystring?: ObjectSchema\n body?: ObjectSchema\n} {\n const json = z.toJSONSchema(action.input) as { properties?: Record<string, unknown>; required?: string[] }\n const properties = json.properties ?? {}\n const required = new Set(json.required ?? [])\n const pathParams = new Set(pathParamNames(action.path))\n const queryKeys = new Set((action.queryParams ?? []).map(String))\n const hasBody = methodHasBody(method)\n\n const params: ObjectSchema = { type: 'object', properties: {}, required: [] }\n const querystring: ObjectSchema = { type: 'object', properties: {}, required: [] }\n const body: ObjectSchema = { type: 'object', properties: {}, required: [] }\n\n for (const [key, prop] of Object.entries(properties)) {\n if (pathParams.has(key)) {\n params.properties[key] = prop\n params.required.push(key)\n } else if (!hasBody || queryKeys.has(key)) {\n querystring.properties[key] = prop\n if (required.has(key)) { querystring.required.push(key) }\n } else {\n body.properties[key] = prop\n if (required.has(key)) { body.required.push(key) }\n }\n }\n\n return {\n params: Object.keys(params.properties).length ? params : undefined,\n querystring: Object.keys(querystring.properties).length ? querystring : undefined,\n body: hasBody && Object.keys(body.properties).length ? body : undefined\n }\n}\n\n/** Strip characters that would break (or smuggle headers into) an HTTP header value. */\nfunction headerSafe(value: string): string {\n return value.replace(/[\"\\r\\n]/g, ' ').trim()\n}\n\n/**\n * Send a resource result as a raw HTTP body: bytes with `Content-Type` from\n * the resource's media type, a `Content-Disposition` filename when the\n * resource carries a name, and the description (if any) as\n * `Content-Description` - over REST the binary itself IS the response, so the\n * metadata rides in headers.\n */\nfunction sendResource(reply: FastifyReply, res: ActionResource) {\n reply.header('Content-Type', res.mimeType)\n if (res.name) { reply.header('Content-Disposition', `inline; filename=\"${headerSafe(res.name)}\"`) }\n if (res.description) { reply.header('Content-Description', headerSafe(res.description)) }\n return reply.send(Buffer.from(resourceBytes(res)))\n}\n\n/**\n * OpenAPI response schema: binary actions document their payload as\n * `type: string, format: binary` under the declared media type.\n */\nfunction responseSchema(action: Action) {\n if (!isBinarySchema(action.output)) {\n return { 200: { description: 'Successful response' } }\n }\n const mimeType = binarySchemaMeta(action.output).mimeType ?? 'application/octet-stream'\n return {\n 200: {\n description: 'Successful response',\n content: { [mimeType]: { schema: { type: 'string', format: 'binary' } } }\n }\n }\n}\n\n/**\n * Register the Scalar API-reference UI. It's an optional peer, so a missing\n * module is skipped with a hint rather than crashing a headless deployment.\n * Resolving the import here (vs. handing `register` a rejecting plugin promise)\n * is what makes the absence catchable now instead of later during `listen`.\n */\nasync function registerScalarDocs(instance: FastifyInstance): Promise<void> {\n const scalar = await import('@scalar/fastify-api-reference').catch((error: unknown) => {\n const code = (error as { code?: string }).code\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') {\n instance.log.info('@scalar/fastify-api-reference is not installed - skipping the API reference UI. Install it to enable the docs UI.')\n return null\n }\n throw error\n })\n if (scalar) {\n await instance.register(scalar.default, { routePrefix: '/' })\n }\n}\n\n/** The instance-wide error handler: Silkweave/Zod/AJV failures map to legible statuses. */\nfunction errorHandler(instance: FastifyInstance) {\n return (error: Error, _request: FastifyRequest, reply: FastifyReply) => {\n if (error instanceof SilkweaveError) {\n return reply.status(error.statusCode).send({ error: error.code, message: error.message })\n }\n if (error instanceof z.ZodError) {\n return reply.status(400).send({ error: 'validation_error', issues: error.issues })\n }\n // Fastify's schema (AJV) validation failures for body/querystring/params.\n const validation = (error as { validation?: unknown }).validation\n if (validation) {\n return reply.status(400).send({ error: 'validation_error', issues: validation })\n }\n instance.log.error(error)\n return reply.status(500).send({ error: 'internal', message: 'Internal server error' })\n }\n}\n\n/** Register one action as a Fastify route (validation, streaming, resource, and JSON paths). */\nfunction registerActionRoute(instance: FastifyInstance, action: Action, context: SilkweaveContext, logger: Logger, hasAuth: boolean) {\n validateActionRouting(action)\n const method = actionMethod(action)\n const url = action.path ? `/${action.path.replace(/^\\//, '')}` : `/${action.name}`\n const { params, querystring, body } = splitInputSchema(action, method)\n const streaming = isStreamingAction(action)\n instance.route({\n method,\n url,\n schema: {\n description: action.description,\n ...(params ? { params } : {}),\n ...(querystring ? { querystring } : {}),\n ...(body ? { body } : {}),\n response: responseSchema(action)\n },\n handler: async (request, reply) => {\n const authInfo = hasAuth ? (request as FastifyRequest & { __silkweave_auth?: AuthInfo }).__silkweave_auth : undefined\n const actionContext = context.fork({ logger, request, ...(authInfo ? { auth: authInfo } : {}) })\n // Fastify's AJV only validates the JSON-Schema projection, which\n // cannot express Zod refinements/transforms. Parse the merged input\n // so .refine()/.email()/.transform() are enforced here as they are\n // over MCP/tRPC/CLI (a ZodError becomes a 400 via setErrorHandler).\n const input = action.input.parse(resolveActionInput(action, {\n params: request.params as Record<string, string | undefined>,\n query: request.query as Record<string, unknown>,\n body: request.body\n }))\n if (streaming) {\n const format = pickStreamFormat(request.headers.accept)\n if (format) {\n await streamAction(reply, format, action, input, actionContext)\n return reply\n }\n return runStreamingAction(action, input, actionContext)\n }\n const result = await (action.run as (input: object, context: SilkweaveContext) => Promise<object>)(input, actionContext)\n // A resource result (resource()/File/Blob/bytes) leaves as raw\n // bytes with mime headers instead of JSON.\n const res = await toActionResource(result, binarySchemaMeta(action.output))\n if (res) { return sendResource(reply, res) }\n return result\n }\n })\n}\n\nexport const fastify: AdapterFactory<FastifyAdapterOptions> = ({ host, port, auth, cors: corsConfig, ...fastifyOptions }) => {\n const instance = fastifyInstance(fastifyOptions)\n return (options, baseContext) => {\n const context = baseContext.fork({ adapter: 'fastify' })\n return {\n context,\n start: async (actions) => {\n if (corsConfig !== false) {\n const userConfig = corsConfig === true || corsConfig === undefined ? {} : corsConfig\n await instance.register(import('@fastify/cors'), { origin: '*', ...userConfig })\n }\n\n await instance.register(import('@fastify/swagger'), {\n openapi: {\n info: {\n title: options.name,\n description: options.description,\n version: options.version\n }\n }\n })\n await registerScalarDocs(instance)\n\n if (auth?.authorizationServers?.length && auth.resourceUrl) {\n instance.get('/.well-known/oauth-protected-resource', () => {\n return generateProtectedResourceMetadata(auth.resourceUrl!, auth.authorizationServers!, auth.requiredScopes)\n })\n }\n\n const oauthPaths = auth?.provider ? mountOAuthRoutes(instance, auth) : new Set<string>()\n\n if (auth) {\n mountAuthMiddleware(instance, auth, oauthPaths, context)\n }\n\n instance.setErrorHandler(errorHandler(instance))\n\n const logger = createActionLogger(instance)\n\n for (const action of actions) {\n registerActionRoute(instance, action, context, logger, Boolean(auth))\n }\n await instance.listen({ host, port })\n },\n stop: async () => { await instance.close() }\n }\n }\n}\n"],"mappings":";;;;;;AAUA,MAAM,iBAAoD;CACxD,WAAW;CACX,OAAO;CACP,UAAU;CACV,OAAO;CACP,SAAS;CACT,QAAQ;CACR,MAAM;CACN,OAAO;CACR;AAUD,SAAS,WAAW,SAAuC;AACzD,QAAO;EACL,QAAQ,QAAQ;EAChB,KAAK,IAAI,IAAI,QAAQ,KAAK,UAAU,QAAQ,WAAW;EACvD,SAAS,OAAO,YAAY,OAAO,QAAQ,QAAQ,QAAQ,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,GAAG,MAAM,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;EAC9G,MAAM,QAAQ;EACf;;AAGH,SAAS,UAAU,OAAqB,UAAyB;AAC/D,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,QAAQ,CAAI,OAAM,OAAO,KAAK,MAAM;AACvF,QAAO,MAAM,OAAO,SAAS,OAAO,CAAC,KAAK,SAAS,KAAK;;AAG1D,SAAS,iBAAiB,UAA2B,MAA+B;CAClF,MAAM,WAAW,KAAK;CAGtB,MAAM,eAAe,KAAK,gBAAgB;CAE1C,MAAM,QAAQ,IAAI,IAAI;EACpB;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,UAAS,IAAI,2CAA2C,OAAO,MAAM,UAAU;AAC7E,SAAO,UAAU,OAAO,SAAS,UAAU,CAAC;GAC5C;AACF,UAAS,IAAI,cAAc,OAAO,KAAK,UAAU;AAC/C,SAAO,UAAU,OAAO,MAAM,SAAS,UAAU,WAAW,IAAI,CAAC,CAAC;GAClE;AACF,UAAS,IAAI,cAAc,OAAO,KAAK,UAAU;AAC/C,SAAO,UAAU,OAAO,MAAM,SAAS,SAAS,WAAW,IAAI,CAAC,CAAC;GACjE;AACF,UAAS,KAAK,UAAU,OAAO,KAAK,UAAU;AAC5C,SAAO,UAAU,OAAO,MAAM,SAAS,MAAM,WAAW,IAAI,CAAC,CAAC;GAC9D;AACF,UAAS,KAAK,aAAa,OAAO,KAAK,UAAU;AAC/C,SAAO,UAAU,OAAO,MAAM,SAAS,SAAS,WAAW,IAAI,CAAC,CAAC;GACjE;AAEF,QAAO;;AAGT,SAAS,oBAAoB,UAA2B,MAAkB,YAAyB,SAA2B;AAC5H,UAAS,gBAAgB,oBAAoB,KAAA,EAAU;AACvD,UAAS,QAAQ,aAAa,OAAO,SAAyB,UAAwB;AACpF,MAAI,QAAQ,IAAI,WAAW,gBAAgB,IAAI,WAAW,IAAI,QAAQ,IAAI,MAAM,IAAI,CAAC,GAAG,CAAI;EAC5F,MAAM,SAAS,MAAM,cAAc,QAAQ,QAAQ,eAAe,MAAM,QAAQ,KAAK,EAAE,SAAS,CAAC,CAAC;AAClG,MAAI,OAAO,OAAO;AAChB,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,MAAM,QAAQ,CAC7D,OAAM,OAAO,KAAK,MAAM;AAE1B,UAAO,MAAM,OAAO,OAAO,MAAM,WAAW,CAAC,KAAK,OAAO,MAAM,KAAK;;AAEtE,MAAI,OAAO,KACR,SAA6D,mBAAmB,OAAO;GAE1F;;AAKJ,SAAS,iBAAiB,cAAuD;AAC/E,KAAI,CAAC,aAAgB,QAAO;AAC5B,KAAI,aAAa,SAAS,oBAAoB,CAAI,QAAO;AACzD,KAAI,aAAa,SAAS,uBAAuB,IAAI,aAAa,SAAS,qBAAqB,CAAI,QAAO;AAC3G,QAAO;;AAGT,SAAS,YAAY,QAAsB,OAAwB;CACjE,MAAM,UAAU,KAAK,UAAU,MAAM;AACrC,QAAO,WAAW,QAAQ,SAAS,QAAQ,QAAQ,GAAG,QAAQ;;AAGhE,SAAS,kBAAkB,QAAsB,OAAwB;CACvE,MAAM,UAAU,KAAK,UAAU;EAC7B,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;EAC7D,MAAM,iBAAiB,QAAQ,MAAM,OAAO;EAC7C,CAAC;AACF,QAAO,WAAW,QAAQ,uBAAuB,QAAQ,QAAQ,GAAG,QAAQ;;AAG9E,eAAe,aACb,OACA,QACA,QACA,OACA,SACe;AACf,OAAM,IAAI,UAAU,gBAAgB,WAAW,QAAQ,sBAAsB,uBAAuB;AACpG,OAAM,IAAI,UAAU,iBAAiB,yBAAyB;AAC9D,OAAM,IAAI,UAAU,cAAc,aAAa;AAC/C,OAAM,IAAI,gBAAgB;AAC1B,OAAM,QAAQ;CACd,MAAM,MAAM,MAAM;CAClB,MAAM,OAAQ,OAAO,IAAyC,OAAO,QAAQ;CAK7E,MAAM,OAAO,EAAE,OAAO,IAAI,aAAa,IAAI,eAAe;CAC1D,MAAM,gBAAgB;AAAE,OAAK,QAAQ;;AACrC,KAAI,GAAG,SAAS,QAAQ;CACxB,MAAM,SAAS,KAAK,KAAK,QAAQ;AACjC,KAAI;AACF,aAAW,MAAM,SAAS,MAAM;AAC9B,OAAI,KAAK,MAAS;AAClB,OAAI,CAAC,IAAI,MAAM,YAAY,QAAQ,MAAM,CAAC,EAAE;AAC1C,UAAM,QAAQ,KAAK,CAAC,KAAK,KAAK,QAAQ,EAAE,OAAO,CAAC;AAChD,QAAI,KAAK,MAAS;;;AAGtB,MAAI,CAAC,KAAK,SAAS,WAAW,MAAS,KAAI,MAAM,4BAA4B;UACtE,OAAO;AACd,MAAI,CAAC,KAAK,MAAS,KAAI,MAAM,kBAAkB,QAAQ,MAAM,CAAC;WACtD;AACR,MAAI,IAAI,SAAS,QAAQ;AAEzB,QAAM,KAAK,UAAU,CAAC,YAAY,GAAwC;AAC1E,MAAI,CAAC,IAAI,cAAiB,KAAI,KAAK;;;AAIvC,SAAS,mBAAmB,UAAmC;AAC7D,QAAO;EACL,GAAG,gBAAgB,OAAO,SAAS;GACjC,MAAM,YAAY,eAAe,UAAU;AAC3C,YAAS,IAAI,WAAW,KAAK;IAC7B;EACF,WAAW,EAAE,UAAU,OAAO,cAAc;AAAE,YAAS,IAAI,MAAM;IAAE;IAAU;IAAO,EAAE,QAAQ;;EAC/F;;;;;;;;AAeH,SAAS,iBAAiB,QAAgB,QAIxC;CACA,MAAM,OAAO,EAAE,aAAa,OAAO,MAAM;CACzC,MAAM,aAAa,KAAK,cAAc,EAAE;CACxC,MAAM,WAAW,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;CAC7C,MAAM,aAAa,IAAI,IAAI,eAAe,OAAO,KAAK,CAAC;CACvD,MAAM,YAAY,IAAI,KAAK,OAAO,eAAe,EAAE,EAAE,IAAI,OAAO,CAAC;CACjE,MAAM,UAAU,cAAc,OAAO;CAErC,MAAM,SAAuB;EAAE,MAAM;EAAU,YAAY,EAAE;EAAE,UAAU,EAAE;EAAE;CAC7E,MAAM,cAA4B;EAAE,MAAM;EAAU,YAAY,EAAE;EAAE,UAAU,EAAE;EAAE;CAClF,MAAM,OAAqB;EAAE,MAAM;EAAU,YAAY,EAAE;EAAE,UAAU,EAAE;EAAE;AAE3E,MAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,WAAW,CAClD,KAAI,WAAW,IAAI,IAAI,EAAE;AACvB,SAAO,WAAW,OAAO;AACzB,SAAO,SAAS,KAAK,IAAI;YAChB,CAAC,WAAW,UAAU,IAAI,IAAI,EAAE;AACzC,cAAY,WAAW,OAAO;AAC9B,MAAI,SAAS,IAAI,IAAI,CAAI,aAAY,SAAS,KAAK,IAAI;QAClD;AACL,OAAK,WAAW,OAAO;AACvB,MAAI,SAAS,IAAI,IAAI,CAAI,MAAK,SAAS,KAAK,IAAI;;AAIpD,QAAO;EACL,QAAQ,OAAO,KAAK,OAAO,WAAW,CAAC,SAAS,SAAS,KAAA;EACzD,aAAa,OAAO,KAAK,YAAY,WAAW,CAAC,SAAS,cAAc,KAAA;EACxE,MAAM,WAAW,OAAO,KAAK,KAAK,WAAW,CAAC,SAAS,OAAO,KAAA;EAC/D;;;AAIH,SAAS,WAAW,OAAuB;AACzC,QAAO,MAAM,QAAQ,YAAY,IAAI,CAAC,MAAM;;;;;;;;;AAU9C,SAAS,aAAa,OAAqB,KAAqB;AAC9D,OAAM,OAAO,gBAAgB,IAAI,SAAS;AAC1C,KAAI,IAAI,KAAQ,OAAM,OAAO,uBAAuB,qBAAqB,WAAW,IAAI,KAAK,CAAC,GAAG;AACjG,KAAI,IAAI,YAAe,OAAM,OAAO,uBAAuB,WAAW,IAAI,YAAY,CAAC;AACvF,QAAO,MAAM,KAAK,OAAO,KAAK,cAAc,IAAI,CAAC,CAAC;;;;;;AAOpD,SAAS,eAAe,QAAgB;AACtC,KAAI,CAAC,eAAe,OAAO,OAAO,CAChC,QAAO,EAAE,KAAK,EAAE,aAAa,uBAAuB,EAAE;AAGxD,QAAO,EACL,KAAK;EACH,aAAa;EACb,SAAS,GAJI,iBAAiB,OAAO,OAAO,CAAC,YAAY,6BAIlC,EAAE,QAAQ;GAAE,MAAM;GAAU,QAAQ;GAAU,EAAE,EAAE;EAC1E,EACF;;;;;;;;AASH,eAAe,mBAAmB,UAA0C;CAC1E,MAAM,SAAS,MAAM,OAAO,iCAAiC,OAAO,UAAmB;EACrF,MAAM,OAAQ,MAA4B;AAC1C,MAAI,SAAS,0BAA0B,SAAS,oBAAoB;AAClE,YAAS,IAAI,KAAK,oHAAoH;AACtI,UAAO;;AAET,QAAM;GACN;AACF,KAAI,OACF,OAAM,SAAS,SAAS,OAAO,SAAS,EAAE,aAAa,KAAK,CAAC;;;AAKjE,SAAS,aAAa,UAA2B;AAC/C,SAAQ,OAAc,UAA0B,UAAwB;AACtE,MAAI,iBAAiB,eACnB,QAAO,MAAM,OAAO,MAAM,WAAW,CAAC,KAAK;GAAE,OAAO,MAAM;GAAM,SAAS,MAAM;GAAS,CAAC;AAE3F,MAAI,iBAAiB,EAAE,SACrB,QAAO,MAAM,OAAO,IAAI,CAAC,KAAK;GAAE,OAAO;GAAoB,QAAQ,MAAM;GAAQ,CAAC;EAGpF,MAAM,aAAc,MAAmC;AACvD,MAAI,WACF,QAAO,MAAM,OAAO,IAAI,CAAC,KAAK;GAAE,OAAO;GAAoB,QAAQ;GAAY,CAAC;AAElF,WAAS,IAAI,MAAM,MAAM;AACzB,SAAO,MAAM,OAAO,IAAI,CAAC,KAAK;GAAE,OAAO;GAAY,SAAS;GAAyB,CAAC;;;;AAK1F,SAAS,oBAAoB,UAA2B,QAAgB,SAA2B,QAAgB,SAAkB;AACnI,uBAAsB,OAAO;CAC7B,MAAM,SAAS,aAAa,OAAO;CACnC,MAAM,MAAM,OAAO,OAAO,IAAI,OAAO,KAAK,QAAQ,OAAO,GAAG,KAAK,IAAI,OAAO;CAC5E,MAAM,EAAE,QAAQ,aAAa,SAAS,iBAAiB,QAAQ,OAAO;CACtE,MAAM,YAAY,kBAAkB,OAAO;AAC3C,UAAS,MAAM;EACb;EACA;EACA,QAAQ;GACN,aAAa,OAAO;GACpB,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;GAC5B,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;GACtC,GAAI,OAAO,EAAE,MAAM,GAAG,EAAE;GACxB,UAAU,eAAe,OAAO;GACjC;EACD,SAAS,OAAO,SAAS,UAAU;GACjC,MAAM,WAAW,UAAW,QAA6D,mBAAmB,KAAA;GAC5G,MAAM,gBAAgB,QAAQ,KAAK;IAAE;IAAQ;IAAS,GAAI,WAAW,EAAE,MAAM,UAAU,GAAG,EAAE;IAAG,CAAC;GAKhG,MAAM,QAAQ,OAAO,MAAM,MAAM,mBAAmB,QAAQ;IAC1D,QAAQ,QAAQ;IAChB,OAAO,QAAQ;IACf,MAAM,QAAQ;IACf,CAAC,CAAC;AACH,OAAI,WAAW;IACb,MAAM,SAAS,iBAAiB,QAAQ,QAAQ,OAAO;AACvD,QAAI,QAAQ;AACV,WAAM,aAAa,OAAO,QAAQ,QAAQ,OAAO,cAAc;AAC/D,YAAO;;AAET,WAAO,mBAAmB,QAAQ,OAAO,cAAc;;GAEzD,MAAM,SAAS,MAAO,OAAO,IAAsE,OAAO,cAAc;GAGxH,MAAM,MAAM,MAAM,iBAAiB,QAAQ,iBAAiB,OAAO,OAAO,CAAC;AAC3E,OAAI,IAAO,QAAO,aAAa,OAAO,IAAI;AAC1C,UAAO;;EAEV,CAAC;;AAGJ,MAAa,WAAkD,EAAE,MAAM,MAAM,MAAM,MAAM,YAAY,GAAG,qBAAqB;CAC3H,MAAM,WAAWA,UAAgB,eAAe;AAChD,SAAQ,SAAS,gBAAgB;EAC/B,MAAM,UAAU,YAAY,KAAK,EAAE,SAAS,WAAW,CAAC;AACxD,SAAO;GACL;GACA,OAAO,OAAO,YAAY;AACxB,QAAI,eAAe,OAAO;KACxB,MAAM,aAAa,eAAe,QAAQ,eAAe,KAAA,IAAY,EAAE,GAAG;AAC1E,WAAM,SAAS,SAAS,OAAO,kBAAkB;MAAE,QAAQ;MAAK,GAAG;MAAY,CAAC;;AAGlF,UAAM,SAAS,SAAS,OAAO,qBAAqB,EAClD,SAAS,EACP,MAAM;KACJ,OAAO,QAAQ;KACf,aAAa,QAAQ;KACrB,SAAS,QAAQ;KAClB,EACF,EACF,CAAC;AACF,UAAM,mBAAmB,SAAS;AAElC,QAAI,MAAM,sBAAsB,UAAU,KAAK,YAC7C,UAAS,IAAI,+CAA+C;AAC1D,YAAO,kCAAkC,KAAK,aAAc,KAAK,sBAAuB,KAAK,eAAe;MAC5G;IAGJ,MAAM,aAAa,MAAM,WAAW,iBAAiB,UAAU,KAAK,mBAAG,IAAI,KAAa;AAExF,QAAI,KACF,qBAAoB,UAAU,MAAM,YAAY,QAAQ;AAG1D,aAAS,gBAAgB,aAAa,SAAS,CAAC;IAEhD,MAAM,SAAS,mBAAmB,SAAS;AAE3C,SAAK,MAAM,UAAU,QACnB,qBAAoB,UAAU,QAAQ,SAAS,QAAQ,QAAQ,KAAK,CAAC;AAEvE,UAAM,SAAS,OAAO;KAAE;KAAM;KAAM,CAAC;;GAEvC,MAAM,YAAY;AAAE,UAAM,SAAS,OAAO;;GAC3C"}
1
+ {"version":3,"file":"index.mjs","names":["fastifyInstance"],"sources":["../src/adapter/fastify.ts"],"sourcesContent":["import type { FastifyCorsOptions } from '@fastify/cors'\nimport {\n AuthConfig,\n AuthInfo,\n generateProtectedResourceMetadata,\n OAuthRequest,\n OAuthResponse,\n PROTECTED_RESOURCE_WELL_KNOWN,\n resolveProtectedResourceMetadata,\n toResourceRequest,\n validateToken\n} from '@silkweave/auth'\nimport {\n Action,\n actionMethod,\n ActionStreamRun,\n AdapterFactory,\n binarySchemaMeta,\n buildLogLevels,\n HttpMethod,\n isBinarySchema,\n isStreamingAction,\n Logger,\n LogLevel,\n methodHasBody,\n pathParamNames,\n resolveActionInput,\n resourceBytes,\n runStreamingAction,\n SilkweaveContext,\n SilkweaveError,\n toActionResource,\n validateActionRouting,\n type ActionResource\n} from '@silkweave/core'\nimport { once } from 'events'\nimport {\n FastifyBaseLogger,\n FastifyHttpOptions,\n fastify as fastifyInstance,\n FastifyInstance,\n FastifyReply,\n FastifyRequest\n} from 'fastify'\nimport { Server } from 'http'\nimport z from 'zod/v4'\n\ntype FastifyLogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace'\n\nconst PINO_LEVEL_MAP: Record<LogLevel, FastifyLogLevel> = {\n emergency: 'fatal',\n alert: 'fatal',\n critical: 'fatal',\n error: 'error',\n warning: 'warn',\n notice: 'debug',\n info: 'info',\n debug: 'debug'\n}\n\nexport interface FastifyAdapterOptions extends FastifyHttpOptions<Server, FastifyBaseLogger> {\n host?: string\n port?: number\n auth?: AuthConfig\n /** CORS configuration. `false` to disable, `true`/`undefined` for permissive defaults, or a FastifyCorsOptions object. */\n cors?: FastifyCorsOptions | boolean\n}\n\nfunction toOAuthReq(request: FastifyRequest): OAuthRequest {\n return {\n method: request.method,\n url: new URL(request.url, `http://${request.hostname}`),\n headers: Object.fromEntries(Object.entries(request.headers).map(([k, v]) => [k, Array.isArray(v) ? v[0] : v])),\n body: request.body as Record<string, string> | undefined\n }\n}\n\nfunction sendOAuth(reply: FastifyReply, oauthRes: OAuthResponse) {\n for (const [key, value] of Object.entries(oauthRes.headers)) {\n reply.header(key, value)\n }\n return reply.status(oauthRes.status).send(oauthRes.body)\n}\n\nfunction mountOAuthRoutes(instance: FastifyInstance, auth: AuthConfig): Set<string> {\n const provider = auth.provider!\n // The provider builds its upstream redirect_uri as `${resourceUrl}${callbackPath}`,\n // so the callback route must match the configured callbackPath, not a hardcoded one.\n const callbackPath = auth.callbackPath ?? '/auth/callback'\n\n const paths = new Set(['/.well-known/oauth-authorization-server', '/authorize', callbackPath, '/token', '/register'])\n\n instance.get('/.well-known/oauth-authorization-server', async (_req, reply) => {\n return sendOAuth(reply, provider.metadata())\n })\n instance.get('/authorize', async (req, reply) => {\n return sendOAuth(reply, await provider.authorize(toOAuthReq(req)))\n })\n instance.get(callbackPath, async (req, reply) => {\n return sendOAuth(reply, await provider.callback(toOAuthReq(req)))\n })\n instance.post('/token', async (req, reply) => {\n return sendOAuth(reply, await provider.token(toOAuthReq(req)))\n })\n instance.post('/register', async (req, reply) => {\n return sendOAuth(reply, await provider.register(toOAuthReq(req)))\n })\n\n return paths\n}\n\nfunction mountAuthMiddleware(\n instance: FastifyInstance,\n auth: AuthConfig,\n oauthPaths: Set<string>,\n context: SilkweaveContext\n) {\n instance.decorateRequest('__silkweave_auth', undefined)\n instance.addHook('onRequest', async (request: FastifyRequest, reply: FastifyReply) => {\n if (request.url.startsWith('/.well-known/') || oauthPaths.has(request.url.split('?')[0])) {\n return\n }\n const result = await validateToken(request.headers.authorization, auth, context.fork({ request }))\n if (result.error) {\n for (const [key, value] of Object.entries(result.error.headers)) {\n reply.header(key, value)\n }\n return reply.status(result.error.statusCode).send(result.error.body)\n }\n if (result.auth) {\n ;(request as FastifyRequest & { __silkweave_auth?: AuthInfo }).__silkweave_auth = result.auth\n }\n })\n}\n\ntype StreamFormat = 'sse' | 'ndjson'\n\nfunction pickStreamFormat(acceptHeader: string | undefined): StreamFormat | null {\n if (!acceptHeader) {\n return null\n }\n if (acceptHeader.includes('text/event-stream')) {\n return 'sse'\n }\n if (acceptHeader.includes('application/x-ndjson') || acceptHeader.includes('application/ndjson')) {\n return 'ndjson'\n }\n return null\n}\n\nfunction encodeChunk(format: StreamFormat, chunk: unknown): string {\n const payload = JSON.stringify(chunk)\n return format === 'sse' ? `data: ${payload}\\n\\n` : `${payload}\\n`\n}\n\nfunction encodeStreamError(format: StreamFormat, error: unknown): string {\n const payload = JSON.stringify({\n error: error instanceof Error ? error.message : String(error),\n name: error instanceof Error ? error.name : 'Error'\n })\n return format === 'sse' ? `event: error\\ndata: ${payload}\\n\\n` : `${payload}\\n`\n}\n\nasync function streamAction(\n reply: FastifyReply,\n format: StreamFormat,\n action: Action,\n input: object,\n context: SilkweaveContext\n): Promise<void> {\n reply.raw.setHeader('Content-Type', format === 'sse' ? 'text/event-stream' : 'application/x-ndjson')\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform')\n reply.raw.setHeader('Connection', 'keep-alive')\n reply.raw.flushHeaders?.()\n reply.hijack()\n const raw = reply.raw\n const iter = (action.run as ActionStreamRun<object, unknown>)(input, context)\n // On client disconnect the next write() returns false and a plain\n // `once(raw, 'drain')` would park forever - the generator would never resume,\n // its finally/cleanup would never run, and response/context stay pinned. Track\n // 'close' and race the drain against it so we bail and tear the generator down.\n const gone = { value: raw.destroyed || raw.writableEnded }\n const onClose = () => {\n gone.value = true\n }\n raw.on('close', onClose)\n const closed = once(raw, 'close')\n try {\n for await (const chunk of iter) {\n if (gone.value) {\n break\n }\n if (!raw.write(encodeChunk(format, chunk))) {\n await Promise.race([once(raw, 'drain'), closed])\n if (gone.value) {\n break\n }\n }\n }\n if (!gone.value && format === 'sse') {\n raw.write('event: done\\ndata: {}\\n\\n')\n }\n } catch (error) {\n if (!gone.value) {\n raw.write(encodeStreamError(format, error))\n }\n } finally {\n raw.off('close', onClose)\n // Ask the generator to run its finally/cleanup (release DB handles etc).\n await iter.return?.().catch(() => {\n /* generator cleanup best-effort */\n })\n if (!raw.writableEnded) {\n raw.end()\n }\n }\n}\n\nfunction createActionLogger(instance: FastifyInstance): Logger {\n return {\n ...buildLogLevels((level, data) => {\n const pinoLevel = PINO_LEVEL_MAP[level] ?? 'info'\n instance.log[pinoLevel](data)\n }),\n progress: ({ progress, total, message }) => {\n instance.log.trace({ progress, total }, message)\n }\n }\n}\n\ninterface ObjectSchema {\n type: 'object'\n properties: Record<string, unknown>\n required: string[]\n}\n\n/**\n * Split an action's input JSON Schema into Fastify `params`/`querystring`/`body`\n * schemas based on its `path` placeholders and `queryParams`. Fastify's own AJV\n * validation, type coercion, and default-filling then apply per source, and the\n * three are merged back into a single input by `resolveActionInput` at runtime.\n */\nfunction splitInputSchema(\n action: Action,\n method: HttpMethod\n): {\n params?: ObjectSchema\n querystring?: ObjectSchema\n body?: ObjectSchema\n} {\n const json = z.toJSONSchema(action.input) as { properties?: Record<string, unknown>; required?: string[] }\n const properties = json.properties ?? {}\n const required = new Set(json.required ?? [])\n const pathParams = new Set(pathParamNames(action.path))\n const queryKeys = new Set((action.queryParams ?? []).map(String))\n const hasBody = methodHasBody(method)\n\n const params: ObjectSchema = { type: 'object', properties: {}, required: [] }\n const querystring: ObjectSchema = { type: 'object', properties: {}, required: [] }\n const body: ObjectSchema = { type: 'object', properties: {}, required: [] }\n\n for (const [key, prop] of Object.entries(properties)) {\n if (pathParams.has(key)) {\n params.properties[key] = prop\n params.required.push(key)\n } else if (!hasBody || queryKeys.has(key)) {\n querystring.properties[key] = prop\n if (required.has(key)) {\n querystring.required.push(key)\n }\n } else {\n body.properties[key] = prop\n if (required.has(key)) {\n body.required.push(key)\n }\n }\n }\n\n return {\n params: Object.keys(params.properties).length ? params : undefined,\n querystring: Object.keys(querystring.properties).length ? querystring : undefined,\n body: hasBody && Object.keys(body.properties).length ? body : undefined\n }\n}\n\n/** Strip characters that would break (or smuggle headers into) an HTTP header value. */\nfunction headerSafe(value: string): string {\n return value.replace(/[\"\\r\\n]/g, ' ').trim()\n}\n\n/**\n * Send a resource result as a raw HTTP body: bytes with `Content-Type` from\n * the resource's media type, a `Content-Disposition` filename when the\n * resource carries a name, and the description (if any) as\n * `Content-Description` - over REST the binary itself IS the response, so the\n * metadata rides in headers.\n */\nfunction sendResource(reply: FastifyReply, res: ActionResource) {\n reply.header('Content-Type', res.mimeType)\n if (res.name) {\n reply.header('Content-Disposition', `inline; filename=\"${headerSafe(res.name)}\"`)\n }\n if (res.description) {\n reply.header('Content-Description', headerSafe(res.description))\n }\n return reply.send(Buffer.from(resourceBytes(res)))\n}\n\n/**\n * OpenAPI response schema: binary actions document their payload as\n * `type: string, format: binary` under the declared media type.\n */\nfunction responseSchema(action: Action) {\n if (!isBinarySchema(action.output)) {\n return { 200: { description: 'Successful response' } }\n }\n const mimeType = binarySchemaMeta(action.output).mimeType ?? 'application/octet-stream'\n return {\n 200: {\n description: 'Successful response',\n content: { [mimeType]: { schema: { type: 'string', format: 'binary' } } }\n }\n }\n}\n\n/**\n * Register the Scalar API-reference UI. It's an optional peer, so a missing\n * module is skipped with a hint rather than crashing a headless deployment.\n * Resolving the import here (vs. handing `register` a rejecting plugin promise)\n * is what makes the absence catchable now instead of later during `listen`.\n */\nasync function registerScalarDocs(instance: FastifyInstance): Promise<void> {\n const scalar = await import('@scalar/fastify-api-reference').catch((error: unknown) => {\n const code = (error as { code?: string }).code\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') {\n instance.log.info(\n '@scalar/fastify-api-reference is not installed - skipping the API reference UI. Install it to enable the docs UI.'\n )\n return null\n }\n throw error\n })\n if (scalar) {\n await instance.register(scalar.default, { routePrefix: '/' })\n }\n}\n\n/** The instance-wide error handler: Silkweave/Zod/AJV failures map to legible statuses. */\nfunction errorHandler(instance: FastifyInstance) {\n return (error: Error, _request: FastifyRequest, reply: FastifyReply) => {\n if (error instanceof SilkweaveError) {\n return reply.status(error.statusCode).send({ error: error.code, message: error.message })\n }\n if (error instanceof z.ZodError) {\n return reply.status(400).send({ error: 'validation_error', issues: error.issues })\n }\n // Fastify's schema (AJV) validation failures for body/querystring/params.\n const validation = (error as { validation?: unknown }).validation\n if (validation) {\n return reply.status(400).send({ error: 'validation_error', issues: validation })\n }\n instance.log.error(error)\n return reply.status(500).send({ error: 'internal', message: 'Internal server error' })\n }\n}\n\n/** Register one action as a Fastify route (validation, streaming, resource, and JSON paths). */\nfunction registerActionRoute(\n instance: FastifyInstance,\n action: Action,\n context: SilkweaveContext,\n logger: Logger,\n hasAuth: boolean\n) {\n validateActionRouting(action)\n const method = actionMethod(action)\n const url = action.path ? `/${action.path.replace(/^\\//, '')}` : `/${action.name}`\n const { params, querystring, body } = splitInputSchema(action, method)\n const streaming = isStreamingAction(action)\n instance.route({\n method,\n url,\n schema: {\n description: action.description,\n ...(params ? { params } : {}),\n ...(querystring ? { querystring } : {}),\n ...(body ? { body } : {}),\n response: responseSchema(action)\n },\n handler: async (request, reply) => {\n const authInfo = hasAuth\n ? (request as FastifyRequest & { __silkweave_auth?: AuthInfo }).__silkweave_auth\n : undefined\n const actionContext = context.fork({ logger, request, ...(authInfo ? { auth: authInfo } : {}) })\n // Fastify's AJV only validates the JSON-Schema projection, which\n // cannot express Zod refinements/transforms. Parse the merged input\n // so .refine()/.email()/.transform() are enforced here as they are\n // over MCP/tRPC/CLI (a ZodError becomes a 400 via setErrorHandler).\n const input = action.input.parse(\n resolveActionInput(action, {\n params: request.params as Record<string, string | undefined>,\n query: request.query as Record<string, unknown>,\n body: request.body\n })\n )\n if (streaming) {\n const format = pickStreamFormat(request.headers.accept)\n if (format) {\n await streamAction(reply, format, action, input, actionContext)\n return reply\n }\n return runStreamingAction(action, input, actionContext)\n }\n const result = await (action.run as (input: object, context: SilkweaveContext) => Promise<object>)(\n input,\n actionContext\n )\n // A resource result (resource()/File/Blob/bytes) leaves as raw\n // bytes with mime headers instead of JSON.\n const res = await toActionResource(result, binarySchemaMeta(action.output))\n if (res) {\n return sendResource(reply, res)\n }\n return result\n }\n })\n}\n\nexport const fastify: AdapterFactory<FastifyAdapterOptions> = ({\n host,\n port,\n auth,\n cors: corsConfig,\n ...fastifyOptions\n}) => {\n const instance = fastifyInstance(fastifyOptions)\n return (options, baseContext) => {\n const context = baseContext.fork({ adapter: 'fastify' })\n return {\n context,\n start: async (actions) => {\n if (corsConfig !== false) {\n const userConfig = corsConfig === true || corsConfig === undefined ? {} : corsConfig\n await instance.register(import('@fastify/cors'), { origin: '*', ...userConfig })\n }\n\n await instance.register(import('@fastify/swagger'), {\n openapi: {\n info: {\n title: options.name,\n description: options.description,\n version: options.version\n }\n }\n })\n await registerScalarDocs(instance)\n\n if (auth?.authorizationServers?.length && typeof auth.resourceUrl === 'string') {\n const metadata = generateProtectedResourceMetadata(\n auth.resourceUrl,\n auth.authorizationServers,\n auth.requiredScopes\n )\n instance.get(PROTECTED_RESOURCE_WELL_KNOWN, () => metadata)\n } else if (auth?.authorizationServers?.length && typeof auth.resourceUrl === 'function') {\n // A resolver serves a family of insertion-form documents, one per\n // resource, so the route is a wildcard and an unknown one is a 404.\n const resolve = (request: FastifyRequest, reply: FastifyReply) => {\n const resourceRequest = toResourceRequest(context.fork({ request }))\n const metadata = resourceRequest && resolveProtectedResourceMetadata(auth, resourceRequest, context)\n if (!metadata) {\n return reply.status(404).send({ error: 'not_found', error_description: 'Unknown protected resource' })\n }\n return metadata\n }\n instance.get(PROTECTED_RESOURCE_WELL_KNOWN, resolve)\n instance.get(`${PROTECTED_RESOURCE_WELL_KNOWN}/*`, resolve)\n }\n\n const oauthPaths = auth?.provider ? mountOAuthRoutes(instance, auth) : new Set<string>()\n\n if (auth) {\n mountAuthMiddleware(instance, auth, oauthPaths, context)\n }\n\n instance.setErrorHandler(errorHandler(instance))\n\n const logger = createActionLogger(instance)\n\n for (const action of actions) {\n registerActionRoute(instance, action, context, logger, Boolean(auth))\n }\n await instance.listen({ host, port })\n },\n stop: async () => {\n await instance.close()\n }\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silkweave/fastify",
3
- "version": "5.0.1",
3
+ "version": "5.2.0",
4
4
  "description": "Silkweave Fastify REST Adapter",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.silkweave.dev",
@@ -26,12 +26,12 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "@fastify/cors": "^11.2.0",
30
- "@fastify/swagger": "^9.7.0",
31
- "fastify": "^5.8.2",
32
- "zod": "^3.25.0",
33
- "@silkweave/auth": "5.0.1",
34
- "@silkweave/core": "5.0.1"
29
+ "@fastify/cors": "^11.3.0",
30
+ "@fastify/swagger": "^9.8.1",
31
+ "fastify": "^5.11.3",
32
+ "zod": "^3.25.0 || ^4.0.0",
33
+ "@silkweave/auth": "5.2.0",
34
+ "@silkweave/core": "5.2.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@scalar/fastify-api-reference": "^1.49.0"
@@ -42,23 +42,19 @@
42
42
  }
43
43
  },
44
44
  "devDependencies": {
45
- "@eslint/js": "^10.0.1",
46
- "@scalar/fastify-api-reference": "^1.49.0",
47
- "@stylistic/eslint-plugin": "^5.10.0",
48
- "@types/node": "^22.0.0",
49
- "eslint": "^10.4.0",
45
+ "@scalar/fastify-api-reference": "^1.64.1",
46
+ "@types/node": "^26.2.0",
50
47
  "rimraf": "^6.1.3",
51
- "tsdown": "^0.21.8",
52
- "tsx": "^4.21.0",
53
- "typescript": "^5.9.3",
54
- "typescript-eslint": "^8.56.1"
48
+ "tsdown": "^0.22.14",
49
+ "tsx": "^4.23.12",
50
+ "typescript": "^7.0.2"
55
51
  },
56
52
  "scripts": {
57
53
  "clean": "rimraf build",
58
54
  "build": "tsdown",
59
55
  "watch": "tsdown --watch",
60
56
  "typecheck": "tsc --noEmit",
61
- "lint": "eslint",
57
+ "lint": "oxlint",
62
58
  "check": "pnpm lint && pnpm typecheck"
63
59
  }
64
60
  }