@thednp/rpc 0.1.1 → 0.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.
Files changed (39) hide show
  1. package/AGENTS.md +30 -15
  2. package/CHANGELOG.md +53 -0
  3. package/README.md +35 -29
  4. package/dist/express/express.d.mts +13 -1
  5. package/dist/express/express.d.mts.map +1 -1
  6. package/dist/express/express.mjs +51 -60
  7. package/dist/express/express.mjs.map +1 -1
  8. package/dist/fastify/fastify.d.mts +16 -1
  9. package/dist/fastify/fastify.d.mts.map +1 -1
  10. package/dist/fastify/fastify.mjs +40 -60
  11. package/dist/fastify/fastify.mjs.map +1 -1
  12. package/dist/fastify/plugin/fastify/plugin.d.mts +20 -3
  13. package/dist/fastify/plugin/fastify/plugin.d.mts.map +1 -1
  14. package/dist/fastify/plugin/fastify/plugin.mjs +39 -59
  15. package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -1
  16. package/dist/h3/h3.d.mts +94 -0
  17. package/dist/h3/h3.d.mts.map +1 -0
  18. package/dist/h3/h3.mjs +245 -0
  19. package/dist/h3/h3.mjs.map +1 -0
  20. package/dist/helpers/helpers.d.mts +2 -0
  21. package/dist/helpers/helpers.d.mts.map +1 -1
  22. package/dist/helpers/helpers.mjs.map +1 -1
  23. package/dist/hono/hono.d.mts +14 -40
  24. package/dist/hono/hono.d.mts.map +1 -1
  25. package/dist/hono/hono.mjs +39 -71
  26. package/dist/hono/hono.mjs.map +1 -1
  27. package/dist/index.d.mts +36 -5
  28. package/dist/index.d.mts.map +1 -1
  29. package/dist/index.mjs +4 -0
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/koa/koa.d.mts +12 -1
  32. package/dist/koa/koa.d.mts.map +1 -1
  33. package/dist/koa/koa.mjs +46 -61
  34. package/dist/koa/koa.mjs.map +1 -1
  35. package/dist/server/server.d.mts +98 -3
  36. package/dist/server/server.d.mts.map +1 -1
  37. package/dist/server/server.mjs +79 -1
  38. package/dist/server/server.mjs.map +1 -1
  39. package/package.json +10 -4
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","names":[],"sources":["../../../../src/options.ts","../../../../src/tools.ts","../../../../src/constants.ts","../../../../src/server-helpers.ts","../../../../src/fastify/helpers.ts","../../../../src/fastify/createMiddleware.ts","../../../../src/fastify/plugin.ts"],"sourcesContent":["import type {\n MiddlewareOptions,\n RpcPluginOptions,\n ServerFunctionOptions,\n} from \"./types.d.ts\";\n\nexport const defaultServerFnOptions = {\n contentType: \"application/json\",\n credentials: \"same-origin\",\n method: \"POST\",\n} satisfies ServerFunctionOptions;\n\nexport const defaultRPCOptions: RpcPluginOptions = {\n rpcPrefix: \"__rpc\",\n adapter: \"express\",\n serverFiles: \"exact\",\n scanRoot: undefined,\n};\n\nexport const defaultMiddlewareOptions = {\n rpcPrefix: undefined,\n path: undefined,\n origin: undefined,\n} satisfies MiddlewareOptions;\n","/**\n * Escapes special regex metacharacters in a string.\n * Used to safely embed user-configurable values (like rpcPrefix) into regular expressions,\n * preventing ReDoS and regex injection attacks.\n * @param s - The raw string to escape\n * @returns The escaped string safe for use in new RegExp()\n */\nexport function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","export const OPERATION_ABORTED = \"Operation aborted\";\n\nexport const REQUEST_CANCELLED = \"Request was cancelled\";\n\nexport const FETCH_ERROR_PREFIX = \"Fetch error: \";\n\nexport const NO_SERVER_FUNCTION_FOUND = \"No server function found.\";\n\nexport const ERROR_LOADING_FILE = \"Error loading file:\";\n\nexport const FUNCTION_NOT_FOUND = \"Function not found\";\n\nexport const METHOD_NOT_ALLOWED = \"Method Not Allowed\";\n\nexport const REQUEST_FORBIDDEN = \"Forbidden\";\n\nexport const INTERNAL_SERVER_ERROR = \"Internal Server Error\";\n\nexport const CLIENT_DISCONNECTED = \"client disconnected\";\n\n/** Returns a warning when a middleware name is reused, preventing registration conflicts. @param name - The duplicate middleware name */\nexport const MIDDLEWARE_NAME_USED = (name: string) =>\n `The middleware name \"${name}\" is already used.`;\n\n/** Error message when a value fails the safe-identifier validation. @param label - What kind of value was being validated. @param name - The rejected value */\nexport const INVALID_IDENTIFIER = (label: string, name: string) =>\n `Invalid ${label}: \"${name}\" must match /^[A-Za-z_$][A-Za-z0-9_$]*$/`;\n\n/** Error message when a value fails the safe-path-segment validation. @param label - What kind of value was being validated. @param segment - The rejected value */\nexport const INVALID_PATH_SEGMENT = (label: string, segment: string) =>\n `Invalid ${label}: \"${segment}\" must match /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/`;\n\n/** Warning message when a specified RPC config file cannot be resolved on disk. @param configFile - The requested config filename. @param configFilePath - The resolved absolute path */\nexport const CONFIG_FILE_NOT_FOUND = (\n configFile: string,\n configFilePath: string,\n) =>\n ` ⚠︎ The specified RPC config file ${configFile} cannot be found at ${configFilePath}, loading the defaults..`;\n\nexport const NO_CONFIG_FOUND = ` ⚡︎ No RPC config found, loading the defaults..`;\n\nexport const FAILED_LOAD_CONFIG = ` ⚠︎ Failed to load RPC config:`;\n\n/** Error template for duplicate server function names across files. @param name - The duplicate registered name */\nexport const DUPLICATE_FUNCTION_NAME = (name: string) =>\n `Duplicate server function \"${name}\" detected. Each server function must have a unique name. Remove or rename the duplicate.`;\n","/** @module Server-side utilities. Exports the `RPCError` class for typed server-side errors, `formatError` for middleware error responses, and `walkGlobFiles` for recursively discovering `*.server.*` files. Never import this module in client code — it is server-only. */\nimport type { JsonObject, JsonValue } from \"./types.d.ts\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { INTERNAL_SERVER_ERROR } from \"./constants.ts\";\n\nconst GLOB_REGEX = /^.+\\.server\\.(ts|js|mjs|mts)$/;\n\n/**\n * Recursively walks `dir` and collects absolute paths to files whose\n * basename matches the `*.server.{ts,js,mjs,mts}` glob pattern.\n */\nexport const walkGlobFiles = async (dir: string): Promise<string[]> => {\n const results: string[] = [];\n const stack = [dir];\n while (stack.length) {\n const current = stack.pop()!;\n let entries;\n try {\n entries = await readdir(current, { withFileTypes: true });\n } catch (_e) {\n continue;\n }\n for (const entry of entries) {\n const fullPath = join(current, entry.name);\n if (entry.isFile() && GLOB_REGEX.test(entry.name)) {\n results.push(fullPath);\n } else if (entry.isDirectory()) {\n stack.push(fullPath);\n }\n }\n }\n return results;\n};\n\n/**\n * A typed error thrown from server functions.\n * The middleware serializes the `message` and `code` in the response,\n * allowing clients to recognise and handle specific error conditions.\n */\nexport class RPCError extends Error {\n /** Machine-readable error code (e.g. \"VALIDATION_FAILED\", \"UNAUTHORIZED\") */\n code: string;\n /** Optional diagnostic payload */\n data?: JsonValue;\n constructor(message: string, code = \"INTERNAL\", data?: JsonValue) {\n super(message);\n this.name = \"RPCError\";\n this.code = code;\n this.data = data;\n }\n}\n\n/**\n * Formats an error for the RPC middleware response.\n * In development the full `RPCError` payload is included so developers\n * can quickly identify issues. Unexpected exceptions never expose their\n * message — only the generic \"Internal Server Error\" is sent, preventing\n * information disclosure; server-side diagnostics are preserved via the\n * middleware's `console.error` logging.\n */\nexport const formatError = (\n err: unknown,\n isProduction: boolean,\n): JsonObject => {\n if (isProduction) {\n return { error: INTERNAL_SERVER_ERROR };\n }\n if (err instanceof RPCError) {\n const payload: JsonObject = {\n error: err.message || INTERNAL_SERVER_ERROR,\n code: err.code,\n };\n if (err.data !== undefined) payload.data = err.data;\n return payload;\n }\n return { error: INTERNAL_SERVER_ERROR };\n};\n","// @thednp/rpc/src/fastify/helpers.ts\nimport type { FastifyRequest } from \"fastify\";\nimport type { ViteDevServer } from \"vite\";\nimport type { FastifyInstance } from \"fastify\";\nimport type { Buffer } from \"node:buffer\";\nimport type { BodyResult, JsonValue } from \"../types.d.ts\";\nimport fastifyRpcPlugin from \"./plugin.ts\";\n\n/**\n * Convenience function to load RPC config and register the RPC plugin to a Fastify instance.\n * Dynamically imports loadRPCConfig and registers the fastify-rpc plugin.\n * @param app - Fastify instance\n */\nexport async function attachRPC(app: FastifyInstance) {\n // The main plugin entry statically imports Vite, so loadRPCConfig is\n // imported lazily: function bundles that never call attachRPC (e.g.\n // serverless functions) keep Vite out of the bundle (or externalized).\n const { loadRPCConfig } = await import(\"@thednp/rpc\");\n const { adapter: _adapter, ...options } = await loadRPCConfig();\n await app.register(fastifyRpcPlugin, options);\n}\n\n/**\n * Attaches Vite's dev server middlewares to a Fastify instance for development mode.\n * Uses an `onRequest` hook to delegate to Vite's connect-compatible middleware stack.\n * @param app - Fastify instance\n * @param vite - Running Vite dev server\n */\nexport function attachVite(app: FastifyInstance, vite: ViteDevServer) {\n app.addHook(\"onRequest\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n vite.middlewares(request.raw, reply.raw, resolve);\n });\n await next();\n });\n}\n\n/**\n * Reads and parses the HTTP request body from a Fastify request.\n * If Fastify's body parser already consumed the stream, uses the pre-parsed body from `req.body`.\n * @param req - Fastify request object\n * @returns A promise resolving to the parsed body with its content type\n */\nexport const readBody = (\n req: FastifyRequest,\n): Promise<BodyResult> => {\n return new Promise((resolve, reject) => {\n const contentType = req.headers[\"content-type\"]?.toLowerCase() || \"\";\n const reqBody = req.body as JsonValue;\n\n if (reqBody !== undefined) {\n const isJSON = contentType.includes(\"json\");\n const isMultipart = contentType.includes(\"multipart/form-data\");\n resolve({\n contentType: isMultipart\n ? \"multipart/form-data\"\n : isJSON\n ? \"application/json\"\n : \"text/plain\",\n data: isMultipart\n ? (reqBody as Record<string, unknown>)\n : isJSON\n ? reqBody\n : String(reqBody),\n } as BodyResult);\n return;\n }\n\n const toggleListeners = (add?: boolean) => {\n const method = add ? \"on\" : \"off\";\n req.raw[method](\"data\", onData);\n req.raw[method](\"end\", onEnd);\n req.raw[method](\"error\", onError);\n };\n\n let body = \"\";\n\n const onData = (chunk: Buffer) => {\n body += chunk.toString();\n };\n\n const onEnd = () => {\n toggleListeners();\n const isJSON = contentType.includes(\"json\");\n const isMultipart = contentType.includes(\"multipart/form-data\");\n try {\n const data = isMultipart ? { raw: body } : JSON.parse(body);\n resolve({\n contentType: isMultipart\n ? \"multipart/form-data\"\n : isJSON\n ? \"application/json\"\n : \"text/plain\",\n data: isMultipart ? (data as Record<string, unknown>) : data,\n } as BodyResult);\n } catch (_e) {\n resolve({ contentType: \"text/plain\", data: String(body) });\n }\n };\n const onError = (err: Error) => {\n toggleListeners();\n reject(err);\n };\n\n toggleListeners(true);\n });\n};\n","// src/fastify/createMiddleware.ts\nimport type {\n FastifyReply,\n FastifyRequest,\n HookHandlerDoneFunction,\n} from \"fastify\";\nimport type {\n FastifyMiddlewareFn,\n FastifyMiddlewareOptions,\n} from \"./types.d.ts\";\nimport type { JsonValue } from \"../types.d.ts\";\nimport { scanForServerFiles, serverFunctionsMap } from \"@thednp/rpc/server\";\nimport { defaultMiddlewareOptions, defaultRPCOptions } from \"../options.ts\";\nimport { escapeRegExp } from \"../tools.ts\";\nimport { formatError } from \"../server-helpers.ts\";\nimport {\n CLIENT_DISCONNECTED,\n FUNCTION_NOT_FOUND,\n METHOD_NOT_ALLOWED,\n MIDDLEWARE_NAME_USED,\n REQUEST_FORBIDDEN,\n} from \"../constants.ts\";\nimport { readBody } from \"./helpers.ts\";\n\nlet middlewareCount = 0;\nconst middlewareStack = new Set<string>();\n\n/**\n * Creates a Fastify preHandler hook with optional path and rpcPrefix filtering.\n * Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.\n * @param initialOptions - Options for rpcPrefix, path matching, and the handler function\n * @returns A Fastify preHandler hook function\n */\nexport const createMiddleware: FastifyMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n const middlewareName = options.name;\n const rpcPrefix = options.rpcPrefix;\n const path = options.path;\n const handler = options.handler;\n\n let name = middlewareName;\n if (!name) {\n name = \"viteRPCMiddleware-\" + middlewareCount;\n middlewareCount += 1;\n }\n if (middlewareStack.has(name)) {\n throw new Error(MIDDLEWARE_NAME_USED(name));\n }\n middlewareStack.add(name);\n\n // Hoist regex compilation out of per-request path. Escape the prefix to\n // prevent regex injection via metacharacters in the config string.\n const prefixRegex: RegExp | null = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : null;\n const pathMatcher: RegExp | null = path\n ? (typeof path === \"string\" ? new RegExp(path) : path)\n : null;\n\n const middlewareHandler = async (\n req: FastifyRequest,\n reply: FastifyReply,\n done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // When serving from production server, scan for server files\n if (serverFunctionsMap.size === 0) {\n await scanForServerFiles();\n }\n\n // No need to continue when no handler provided\n if (!handler) {\n done();\n return;\n }\n\n // Path matching\n if (pathMatcher && !pathMatcher.test(url)) {\n done();\n return;\n }\n\n // rpcPrefix matching (boundary-safe via escaped regex)\n if (prefixRegex && !prefixRegex.test(url)) {\n done();\n return;\n }\n\n // Execute handler\n await handler(req, reply, done);\n };\n\n Object.defineProperty(middlewareHandler, \"name\", {\n value: name,\n });\n\n return middlewareHandler;\n};\n\n/**\n * Creates the Fastify RPC middleware that routes incoming requests to registered server functions.\n * Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches\n * to the matching function, and sends the JSON-serialized result.\n * @param initialOptions - Options including rpcPrefix for URL routing\n * @returns A Fastify preHandler hook function\n */\nexport const createRPCMiddleware: FastifyMiddlewareFn = (\n initialOptions = {},\n) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n { rpcPrefix: defaultRPCOptions.rpcPrefix },\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n // Hoist prefix regex (escaped) and the literal prefix-for-replace out of the\n // per-request handler to avoid regex injection and per-request compilation.\n const rpcPrefix = options.rpcPrefix;\n const prefixRegex = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${rpcPrefix}/`;\n\n return createMiddleware({\n ...options,\n handler: async (\n req: FastifyRequest,\n reply: FastifyReply,\n _done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // Defense-in-depth: validate prefix match via escaped regex even though\n // the outer createMiddleware gates on the same prefix already.\n // istanbul ignore if\n if (prefixRegex && !prefixRegex.test(url)) {\n return;\n }\n\n // Optional origin check: reject requests whose Origin header does not\n // match the configured origin. Requests without an Origin header\n // (curl, native clients) pass through unchecked.\n const origin = options.origin;\n const requestOrigin = req.headers.origin;\n if (origin && requestOrigin && requestOrigin !== origin) {\n reply.status(403).send({ error: REQUEST_FORBIDDEN });\n return;\n }\n\n const functionName = url.replace(prefixReplace, \"\");\n const serverFunction = serverFunctionsMap.get(functionName);\n\n if (!serverFunction) {\n reply.status(404).send({\n error: FUNCTION_NOT_FOUND,\n });\n return;\n }\n\n try {\n const method = serverFunction.options?.method || \"POST\";\n if (req.method.toUpperCase() !== method) {\n reply.status(405).send({ error: METHOD_NOT_ALLOWED });\n return;\n }\n\n let args: JsonValue[] = [];\n if (method === \"GET\") {\n const raw = reqUrl.searchParams.get(\"args\");\n // istanbul ignore else\n if (raw) args = JSON.parse(raw);\n } else {\n const body = await readBody(req);\n args = Array.isArray(body.data)\n ? body.data as JsonValue[]\n : [body.data as JsonValue];\n }\n const { data: dataResult, cancel } = serverFunction.handler(...args);\n const onClose = () => cancel(CLIENT_DISCONNECTED);\n\n req.raw.on(\"close\", onClose);\n const data = await dataResult;\n req.raw.off(\"close\", onClose);\n\n // istanbul ignore else\n if (!reply.raw.headersSent) reply.status(200).send({ data });\n } catch (err) {\n console.error(String(err));\n const isProduction = process.env.NODE_ENV === \"production\";\n reply.status(500).send(formatError(err, isProduction));\n }\n },\n });\n};\n","/** @module Fastify plugin. Exports the RPC plugin wrapped with `fastify-plugin` for lifecycle-compatible registration. */\n// inspired by https://github.com/royalswe/vike-fastify-boilerplate/blob/main/server/index.ts\nimport fp from \"fastify-plugin\";\nimport type { MiddlewareOptions } from \"../types.d.ts\";\nimport type {\n FastifyRPCPlugin,\n RegisteredFastifyRPCPlugin,\n} from \"./types.d.ts\";\nimport { createRPCMiddleware } from \"./createMiddleware.ts\";\n\nexport type { MiddlewareOptions };\n\nconst RpcPlugin: FastifyRPCPlugin = (\n fastify,\n initialOptions,\n done,\n) => {\n // Register RPC middleware as preHandler hook\n const rpcMiddleware = createRPCMiddleware(initialOptions);\n fastify.addHook(\"preHandler\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n rpcMiddleware(request, reply, resolve);\n });\n await next();\n });\n\n done();\n};\n\n// Export the plugin wrapped with fastify-plugin\nconst rpcPlugin = fp(RpcPlugin, {\n name: \"uni-rpc-fastify-plugin\",\n}) as RegisteredFastifyRPCPlugin;\n\nexport { rpcPlugin as default };\n"],"mappings":";;;AAYA,MAAa,oBAAsC;CACjD,WAAW;CACX,SAAS;CACT,aAAa;CACb,UAAU,KAAA;AACZ;AAEA,MAAa,2BAA2B;CACtC,WAAW,KAAA;CACX,MAAM,KAAA;CACN,QAAQ,KAAA;AACV;;;;;;;;;;AChBA,SAAgB,aAAa,GAAmB;CAC9C,OAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;;;ACCA,MAAa,qBAAqB;AAElC,MAAa,qBAAqB;AAElC,MAAa,oBAAoB;AAEjC,MAAa,wBAAwB;AAErC,MAAa,sBAAsB;;AAGnC,MAAa,wBAAwB,SACnC,wBAAwB,KAAK;;;;;;;;ACmB/B,IAAa,WAAb,cAA8B,MAAM;;CAElC;;CAEA;CACA,YAAY,SAAiB,OAAO,YAAY,MAAkB;EAChE,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;;;;;;;;;AAUA,MAAa,eACX,KACA,iBACe;CACf,IAAI,cACF,OAAO,EAAE,OAAO,sBAAsB;CAExC,IAAI,eAAe,UAAU;EAC3B,MAAM,UAAsB;GAC1B,OAAO,IAAI,WAAA;GACX,MAAM,IAAI;EACZ;EACA,IAAI,IAAI,SAAS,KAAA,GAAW,QAAQ,OAAO,IAAI;EAC/C,OAAO;CACT;CACA,OAAO,EAAE,OAAO,sBAAsB;AACxC;;;;;;;;;AClCA,MAAa,YACX,QACwB;CACxB,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,cAAc,IAAI,QAAQ,eAAe,EAAE,YAAY,KAAK;EAClE,MAAM,UAAU,IAAI;EAEpB,IAAI,YAAY,KAAA,GAAW;GACzB,MAAM,SAAS,YAAY,SAAS,MAAM;GAC1C,MAAM,cAAc,YAAY,SAAS,qBAAqB;GAC9D,QAAQ;IACN,aAAa,cACT,wBACA,SACA,qBACA;IACJ,MAAM,cACD,UACD,SACA,UACA,OAAO,OAAO;GACpB,CAAe;GACf;EACF;EAEA,MAAM,mBAAmB,QAAkB;GACzC,MAAM,SAAS,MAAM,OAAO;GAC5B,IAAI,IAAI,OAAO,CAAC,QAAQ,MAAM;GAC9B,IAAI,IAAI,OAAO,CAAC,OAAO,KAAK;GAC5B,IAAI,IAAI,OAAO,CAAC,SAAS,OAAO;EAClC;EAEA,IAAI,OAAO;EAEX,MAAM,UAAU,UAAkB;GAChC,QAAQ,MAAM,SAAS;EACzB;EAEA,MAAM,cAAc;GAClB,gBAAgB;GAChB,MAAM,SAAS,YAAY,SAAS,MAAM;GAC1C,MAAM,cAAc,YAAY,SAAS,qBAAqB;GAC9D,IAAI;IACF,MAAM,OAAO,cAAc,EAAE,KAAK,KAAK,IAAI,KAAK,MAAM,IAAI;IAC1D,QAAQ;KACN,aAAa,cACT,wBACA,SACA,qBACA;KACJ,MAAM,cAAe,OAAmC;IAC1D,CAAe;GACjB,SAAS,IAAI;IACX,QAAQ;KAAE,aAAa;KAAc,MAAM,OAAO,IAAI;IAAE,CAAC;GAC3D;EACF;EACA,MAAM,WAAW,QAAe;GAC9B,gBAAgB;GAChB,OAAO,GAAG;EACZ;EAEA,gBAAgB,IAAI;CACtB,CAAC;AACH;;;ACnFA,IAAI,kBAAkB;AACtB,MAAM,kCAAkB,IAAI,IAAY;;;;;;;AAQxC,MAAa,oBAAyC,iBAAiB,CAAC,MAAM;CAC5E,MAAM,UAAU,OAAO,OACrB,CAAC,GACD,0BACA,cACF;CAEA,MAAM,iBAAiB,QAAQ;CAC/B,MAAM,YAAY,QAAQ;CAC1B,MAAM,OAAO,QAAQ;CACrB,MAAM,UAAU,QAAQ;CAExB,IAAI,OAAO;CACX,IAAI,CAAC,MAAM;EACT,OAAO,uBAAuB;EAC9B,mBAAmB;CACrB;CACA,IAAI,gBAAgB,IAAI,IAAI,GAC1B,MAAM,IAAI,MAAM,qBAAqB,IAAI,CAAC;CAE5C,gBAAgB,IAAI,IAAI;CAIxB,MAAM,cAA6B,YAC/B,IAAI,OAAO,KAAK,aAAa,SAAS,EAAE,EAAE,IAC1C;CACJ,MAAM,cAA6B,OAC9B,OAAO,SAAS,WAAW,IAAI,OAAO,IAAI,IAAI,OAC/C;CAEJ,MAAM,oBAAoB,OACxB,KACA,OACA,SACG;EAEH,MAAM,MAAM,IADO,IAAI,IAAI,KAAK,kBACf,CAAC,CAAC;EAGnB,IAAI,mBAAmB,SAAS,GAC9B,MAAM,mBAAmB;EAI3B,IAAI,CAAC,SAAS;GACZ,KAAK;GACL;EACF;EAGA,IAAI,eAAe,CAAC,YAAY,KAAK,GAAG,GAAG;GACzC,KAAK;GACL;EACF;EAGA,IAAI,eAAe,CAAC,YAAY,KAAK,GAAG,GAAG;GACzC,KAAK;GACL;EACF;EAGA,MAAM,QAAQ,KAAK,OAAO,IAAI;CAChC;CAEA,OAAO,eAAe,mBAAmB,QAAQ,EAC/C,OAAO,KACT,CAAC;CAED,OAAO;AACT;;;;;;;;AASA,MAAa,uBACX,iBAAiB,CAAC,MACf;CACH,MAAM,UAAU,OAAO,OACrB,CAAC,GACD,0BACA,EAAE,WAAW,kBAAkB,UAAU,GACzC,cACF;CAIA,MAAM,YAAY,QAAQ;CAC1B,MAAM,cAAc,YAChB,IAAI,OAAO,KAAK,aAAa,SAAS,EAAE,EAAE,IACzC;CACL,MAAM,gBAAgB,IAAI,UAAU;CAEpC,OAAO,iBAAiB;EACtB,GAAG;EACH,SAAS,OACP,KACA,OACA,UACG;GACH,MAAM,SAAS,IAAI,IAAI,IAAI,KAAK,kBAAkB;GAClD,MAAM,MAAM,OAAO;GAKnB,IAAI,eAAe,CAAC,YAAY,KAAK,GAAG,GACtC;GAMF,MAAM,SAAS,QAAQ;GACvB,MAAM,gBAAgB,IAAI,QAAQ;GAClC,IAAI,UAAU,iBAAiB,kBAAkB,QAAQ;IACvD,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,kBAAkB,CAAC;IACnD;GACF;GAEA,MAAM,eAAe,IAAI,QAAQ,eAAe,EAAE;GAClD,MAAM,iBAAiB,mBAAmB,IAAI,YAAY;GAE1D,IAAI,CAAC,gBAAgB;IACnB,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,EACrB,OAAO,mBACT,CAAC;IACD;GACF;GAEA,IAAI;IACF,MAAM,SAAS,eAAe,SAAS,UAAU;IACjD,IAAI,IAAI,OAAO,YAAY,MAAM,QAAQ;KACvC,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,mBAAmB,CAAC;KACpD;IACF;IAEA,IAAI,OAAoB,CAAC;IACzB,IAAI,WAAW,OAAO;KACpB,MAAM,MAAM,OAAO,aAAa,IAAI,MAAM;KAE1C,IAAI,KAAK,OAAO,KAAK,MAAM,GAAG;IAChC,OAAO;KACL,MAAM,OAAO,MAAM,SAAS,GAAG;KAC/B,OAAO,MAAM,QAAQ,KAAK,IAAI,IAC1B,KAAK,OACL,CAAC,KAAK,IAAiB;IAC7B;IACA,MAAM,EAAE,MAAM,YAAY,WAAW,eAAe,QAAQ,GAAG,IAAI;IACnE,MAAM,gBAAgB,OAAO,mBAAmB;IAEhD,IAAI,IAAI,GAAG,SAAS,OAAO;IAC3B,MAAM,OAAO,MAAM;IACnB,IAAI,IAAI,IAAI,SAAS,OAAO;IAG5B,IAAI,CAAC,MAAM,IAAI,aAAa,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC;GAC7D,SAAS,KAAK;IACZ,QAAQ,MAAM,OAAO,GAAG,CAAC;IACzB,MAAM,eAAe,QAAQ,IAAI,aAAa;IAC9C,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,YAAY,KAAK,YAAY,CAAC;GACvD;EACF;CACF,CAAC;AACH;;;;AC9LA,MAAM,aACJ,SACA,gBACA,SACG;CAEH,MAAM,gBAAgB,oBAAoB,cAAc;CACxD,QAAQ,QAAQ,cAAc,OAAO,SAAS,UAAU;EACtD,MAAM,aACJ,IAAI,SAAS,YAAY;GACvB,cAAc,SAAS,OAAO,OAAO;EACvC,CAAC;EACH,MAAM,KAAK;CACb,CAAC;CAED,KAAK;AACP;AAGA,MAAM,YAAY,GAAG,WAAW,EAC9B,MAAM,yBACR,CAAC"}
1
+ {"version":3,"file":"plugin.mjs","names":[],"sources":["../../../../src/options.ts","../../../../src/constants.ts","../../../../src/fastify/helpers.ts","../../../../src/fastify/createMiddleware.ts","../../../../src/fastify/plugin.ts"],"sourcesContent":["import type {\n MiddlewareOptions,\n RpcPluginOptions,\n ServerFunctionOptions,\n} from \"./types.d.ts\";\n\nexport const defaultServerFnOptions = {\n contentType: \"application/json\",\n credentials: \"same-origin\",\n method: \"POST\",\n} satisfies ServerFunctionOptions;\n\nexport const defaultRPCOptions: RpcPluginOptions = {\n rpcPrefix: \"__rpc\",\n adapter: \"express\",\n serverFiles: \"exact\",\n scanRoot: undefined,\n};\n\nexport const defaultMiddlewareOptions = {\n rpcPrefix: undefined,\n path: undefined,\n origin: undefined,\n} satisfies MiddlewareOptions;\n","export const OPERATION_ABORTED = \"Operation aborted\";\n\nexport const REQUEST_CANCELLED = \"Request was cancelled\";\n\nexport const FETCH_ERROR_PREFIX = \"Fetch error: \";\n\nexport const NO_SERVER_FUNCTION_FOUND = \"No server function found.\";\n\nexport const ERROR_LOADING_FILE = \"Error loading file:\";\n\nexport const FUNCTION_NOT_FOUND = \"Function not found\";\n\nexport const METHOD_NOT_ALLOWED = \"Method Not Allowed\";\n\nexport const REQUEST_FORBIDDEN = \"Forbidden\";\n\nexport const UNSUPPORTED_MEDIA_TYPE = \"Unsupported Media Type\";\n\nexport const INTERNAL_SERVER_ERROR = \"Internal Server Error\";\n\nexport const CLIENT_DISCONNECTED = \"client disconnected\";\n\n/** Returns a warning when a middleware name is reused, preventing registration conflicts. @param name - The duplicate middleware name */\nexport const MIDDLEWARE_NAME_USED = (name: string) =>\n `The middleware name \"${name}\" is already used.`;\n\n/** Error message when a value fails the safe-identifier validation. @param label - What kind of value was being validated. @param name - The rejected value */\nexport const INVALID_IDENTIFIER = (label: string, name: string) =>\n `Invalid ${label}: \"${name}\" must match /^[A-Za-z_$][A-Za-z0-9_$]*$/`;\n\n/** Error message when a value fails the safe-path-segment validation. @param label - What kind of value was being validated. @param segment - The rejected value */\nexport const INVALID_PATH_SEGMENT = (label: string, segment: string) =>\n `Invalid ${label}: \"${segment}\" must match /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/`;\n\n/** Warning message when a specified RPC config file cannot be resolved on disk. @param configFile - The requested config filename. @param configFilePath - The resolved absolute path */\nexport const CONFIG_FILE_NOT_FOUND = (\n configFile: string,\n configFilePath: string,\n) =>\n ` ⚠︎ The specified RPC config file ${configFile} cannot be found at ${configFilePath}, loading the defaults..`;\n\nexport const NO_CONFIG_FOUND = ` ⚡︎ No RPC config found, loading the defaults..`;\n\nexport const FAILED_LOAD_CONFIG = ` ⚠︎ Failed to load RPC config:`;\n\n/** Error template for duplicate server function names across files. @param name - The duplicate registered name */\nexport const DUPLICATE_FUNCTION_NAME = (name: string) =>\n `Duplicate server function \"${name}\" detected. Each server function must have a unique name. Remove or rename the duplicate.`;\n","// @thednp/rpc/src/fastify/helpers.ts\nimport type { FastifyReply, FastifyRequest } from \"fastify\";\nimport type { ViteDevServer } from \"vite\";\nimport type { FastifyInstance } from \"fastify\";\nimport type { Buffer } from \"node:buffer\";\nimport type { BodyResult, JsonValue } from \"../types.d.ts\";\nimport fastifyRpcPlugin from \"./plugin.ts\";\n\n/**\n * Convenience function to load RPC config and register the RPC plugin to a Fastify instance.\n * Dynamically imports loadRPCConfig and registers the fastify-rpc plugin.\n * @param app - Fastify instance\n */\nexport async function attachRPC(app: FastifyInstance) {\n // The main plugin entry statically imports Vite, so loadRPCConfig is\n // imported lazily: function bundles that never call attachRPC (e.g.\n // serverless functions) keep Vite out of the bundle (or externalized).\n const { loadRPCConfig } = await import(\"@thednp/rpc\");\n const { adapter: _adapter, ...options } = await loadRPCConfig();\n await app.register(fastifyRpcPlugin, options);\n}\n\n/**\n * Attaches Vite's dev server middlewares to a Fastify instance for development mode.\n * Uses an `onRequest` hook to delegate to Vite's connect-compatible middleware stack.\n * @param app - Fastify instance\n * @param vite - Running Vite dev server\n */\nexport function attachVite(app: FastifyInstance, vite: ViteDevServer) {\n app.addHook(\"onRequest\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n vite.middlewares(request.raw, reply.raw, resolve);\n });\n await next();\n });\n}\n\n/**\n * Reads and parses the HTTP request body from a Fastify request.\n * If Fastify's body parser already consumed the stream, uses the pre-parsed body from `req.body`.\n * @param req - Fastify request object\n * @returns A promise resolving to the parsed body with its content type\n */\nexport const readBody = (\n req: FastifyRequest,\n): Promise<BodyResult> => {\n return new Promise((resolve, reject) => {\n const contentType = req.headers[\"content-type\"]?.toLowerCase() || \"\";\n const reqBody = req.body as JsonValue;\n\n if (reqBody !== undefined) {\n const isJSON = contentType.includes(\"json\");\n const isMultipart = contentType.includes(\"multipart/form-data\");\n const isUrlEncoded = contentType.includes(\"urlencoded\");\n resolve({\n contentType: isMultipart\n ? \"multipart/form-data\"\n : isJSON\n ? \"application/json\"\n : isUrlEncoded\n ? \"application/x-www-form-urlencoded\"\n : \"text/plain\",\n data: isMultipart\n ? (reqBody as Record<string, unknown>)\n : isJSON\n ? reqBody\n : isUrlEncoded\n ? (reqBody as Record<string, unknown>)\n : String(reqBody),\n } as BodyResult);\n return;\n }\n\n const toggleListeners = (add?: boolean) => {\n const method = add ? \"on\" : \"off\";\n req.raw[method](\"data\", onData);\n req.raw[method](\"end\", onEnd);\n req.raw[method](\"error\", onError);\n };\n\n let body = \"\";\n\n const onData = (chunk: Buffer) => {\n body += chunk.toString();\n };\n\n const onEnd = () => {\n toggleListeners();\n const isJSON = contentType.includes(\"json\");\n const isMultipart = contentType.includes(\"multipart/form-data\");\n const isUrlEncoded = contentType.includes(\"urlencoded\");\n try {\n const data = isMultipart\n ? { raw: body }\n : isUrlEncoded\n ? Object.fromEntries(new URLSearchParams(body))\n : JSON.parse(body);\n resolve({\n contentType: isMultipart\n ? \"multipart/form-data\"\n : isJSON\n ? \"application/json\"\n : isUrlEncoded\n ? \"application/x-www-form-urlencoded\"\n : \"text/plain\",\n data: isMultipart ? (data as Record<string, unknown>) : data,\n } as BodyResult);\n } catch (_e) {\n resolve({ contentType: \"text/plain\", data: String(body) });\n }\n };\n const onError = (err: Error) => {\n toggleListeners();\n reject(err);\n };\n\n toggleListeners(true);\n });\n};\n\n/**\n * Issues an HTTP redirect on a Fastify reply using the native\n * `reply.redirect(location, status)` API (Fastify v5 signature: destination\n * URL first, status code optional). Defaults to `303 See Other` for\n * convention (Post/Redirect/Get).\n * @param reply - Fastify reply object\n * @param location - The URL to redirect to\n * @param status - HTTP status code, defaults to 303\n */\nexport const redirect = (\n reply: FastifyReply,\n location: string,\n status = 303,\n): void => {\n reply.redirect(location, status);\n};\n","// src/fastify/createMiddleware.ts\nimport type {\n FastifyReply,\n FastifyRequest,\n HookHandlerDoneFunction,\n} from \"fastify\";\nimport type {\n FastifyMiddlewareFn,\n FastifyMiddlewareOptions,\n} from \"./types.d.ts\";\nimport type { JsonValue } from \"@thednp/rpc\";\nimport type { RequestEvent } from \"@thednp/rpc/server\";\nimport {\n escapeRegExp,\n formatError,\n hasContentTypeMismatch,\n provideRequestContext,\n scanForServerFiles,\n serverFunctionsMap,\n} from \"@thednp/rpc/server\";\nimport { defaultMiddlewareOptions, defaultRPCOptions } from \"../options.ts\";\nimport {\n CLIENT_DISCONNECTED,\n FUNCTION_NOT_FOUND,\n METHOD_NOT_ALLOWED,\n MIDDLEWARE_NAME_USED,\n REQUEST_FORBIDDEN,\n UNSUPPORTED_MEDIA_TYPE,\n} from \"../constants.ts\";\nimport { readBody, redirect as fastifyRedirect } from \"./helpers.ts\";\n\nlet middlewareCount = 0;\nconst middlewareStack = new Set<string>();\n\n/**\n * Creates a Fastify preHandler hook with optional path and rpcPrefix filtering.\n * Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.\n * @param initialOptions - Options for rpcPrefix, path matching, and the handler function\n * @returns A Fastify preHandler hook function\n */\nexport const createMiddleware: FastifyMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n const middlewareName = options.name;\n const rpcPrefix = options.rpcPrefix;\n const path = options.path;\n const handler = options.handler;\n\n let name = middlewareName;\n if (!name) {\n name = \"viteRPCMiddleware-\" + middlewareCount;\n middlewareCount += 1;\n }\n if (middlewareStack.has(name)) {\n throw new Error(MIDDLEWARE_NAME_USED(name));\n }\n middlewareStack.add(name);\n\n // Hoist regex compilation out of per-request path. Escape the prefix to\n // prevent regex injection via metacharacters in the config string.\n const prefixRegex: RegExp | null = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : null;\n const pathMatcher: RegExp | null = path\n ? (typeof path === \"string\" ? new RegExp(path) : path)\n : null;\n\n const middlewareHandler = async (\n req: FastifyRequest,\n reply: FastifyReply,\n done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // When serving from production server, scan for server files\n if (serverFunctionsMap.size === 0) {\n await scanForServerFiles();\n }\n\n // No need to continue when no handler provided\n if (!handler) {\n done();\n return;\n }\n\n // Path matching\n if (pathMatcher && !pathMatcher.test(url)) {\n done();\n return;\n }\n\n // rpcPrefix matching (boundary-safe via escaped regex)\n if (prefixRegex && !prefixRegex.test(url)) {\n done();\n return;\n }\n\n // Execute handler\n await handler(req, reply, done);\n };\n\n Object.defineProperty(middlewareHandler, \"name\", {\n value: name,\n });\n\n return middlewareHandler;\n};\n\n/**\n * Creates the Fastify RPC middleware that routes incoming requests to registered server functions.\n * Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches\n * to the matching function, and sends the JSON-serialized result.\n * @param initialOptions - Options including rpcPrefix for URL routing\n * @returns A Fastify preHandler hook function\n */\nexport const createRPCMiddleware: FastifyMiddlewareFn = (\n initialOptions = {},\n) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n { rpcPrefix: defaultRPCOptions.rpcPrefix },\n initialOptions,\n ) as FastifyMiddlewareOptions;\n\n // Hoist prefix regex (escaped) and the literal prefix-for-replace out of the\n // per-request handler to avoid regex injection and per-request compilation.\n const rpcPrefix = options.rpcPrefix;\n const prefixRegex = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${rpcPrefix}/`;\n\n return createMiddleware({\n ...options,\n handler: async (\n req: FastifyRequest,\n reply: FastifyReply,\n _done: HookHandlerDoneFunction,\n ) => {\n const reqUrl = new URL(req.url, \"http://localhost\");\n const url = reqUrl.pathname;\n\n // Defense-in-depth: validate prefix match via escaped regex even though\n // the outer createMiddleware gates on the same prefix already.\n // istanbul ignore if\n if (prefixRegex && !prefixRegex.test(url)) {\n return;\n }\n\n // Optional origin check: reject requests whose Origin header does not\n // match the configured origin. Requests without an Origin header\n // (curl, native clients) pass through unchecked.\n const origin = options.origin;\n const requestOrigin = req.headers.origin;\n if (origin && requestOrigin && requestOrigin !== origin) {\n reply.status(403).send({ error: REQUEST_FORBIDDEN });\n return;\n }\n\n const functionName = url.replace(prefixReplace, \"\");\n const serverFunction = serverFunctionsMap.get(functionName);\n\n if (!serverFunction) {\n reply.status(404).send({\n error: FUNCTION_NOT_FOUND,\n });\n return;\n }\n\n try {\n const method = serverFunction.options?.method || \"POST\";\n if (req.method.toUpperCase() !== method) {\n reply.status(405).send({ error: METHOD_NOT_ALLOWED });\n return;\n }\n\n let args: JsonValue[] = [];\n if (method === \"GET\") {\n const raw = reqUrl.searchParams.get(\"args\");\n // istanbul ignore else\n if (raw) args = JSON.parse(raw);\n } else {\n // Content-type enforcement: strict for json/text, lenient between forms.\n // Requests without a Content-Type header are exempt (curl/GET compat).\n // Checked BEFORE readBody so mismatched bodies are never buffered.\n if (\n hasContentTypeMismatch(\n serverFunction.options?.contentType ?? \"application/json\",\n req.headers[\"content-type\"],\n )\n ) {\n reply.status(415).send({ error: UNSUPPORTED_MEDIA_TYPE });\n return;\n }\n const body = await readBody(req);\n args = Array.isArray(body.data)\n ? body.data as JsonValue[]\n : [body.data as JsonValue];\n }\n const requestEvent: RequestEvent = {\n request: req,\n response: reply,\n nativeEvent: req,\n locals: {},\n redirect: (location, status = 303) => {\n requestEvent.redirected = { location, status };\n fastifyRedirect(reply, location, status);\n },\n };\n const { data: dataResult, cancel } = provideRequestContext(\n requestEvent,\n () => serverFunction.handler(...args),\n );\n const onClose = () => cancel(CLIENT_DISCONNECTED);\n\n req.raw.on(\"close\", onClose);\n const data = await dataResult;\n req.raw.off(\"close\", onClose);\n\n // istanbul ignore else\n if (!requestEvent.redirected && !reply.raw.headersSent) {\n reply.status(200).send({ data });\n }\n } catch (err) {\n console.error(String(err));\n const isProduction = process.env.NODE_ENV === \"production\";\n reply.status(500).send(formatError(err, isProduction));\n }\n },\n });\n};\n","/** @module Fastify plugin. Exports the RPC plugin wrapped with `fastify-plugin` for lifecycle-compatible registration. */\n// inspired by https://github.com/royalswe/vike-fastify-boilerplate/blob/main/server/index.ts\nimport fp from \"fastify-plugin\";\nimport type { MiddlewareOptions } from \"../types.d.ts\";\nimport type {\n FastifyRPCPlugin,\n RegisteredFastifyRPCPlugin,\n} from \"./types.d.ts\";\nimport { createRPCMiddleware } from \"./createMiddleware.ts\";\n\nexport type { MiddlewareOptions };\n\nconst RpcPlugin: FastifyRPCPlugin = (\n fastify,\n initialOptions,\n done,\n) => {\n // Register RPC middleware as preHandler hook\n const rpcMiddleware = createRPCMiddleware(initialOptions);\n fastify.addHook(\"preHandler\", async (request, reply) => {\n const next = () =>\n new Promise((resolve) => {\n rpcMiddleware(request, reply, resolve);\n });\n await next();\n });\n\n done();\n};\n\n// Export the plugin wrapped with fastify-plugin\nconst rpcPlugin = fp(RpcPlugin, {\n name: \"uni-rpc-fastify-plugin\",\n}) as RegisteredFastifyRPCPlugin;\n\nexport { rpcPlugin as default };\n"],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import { H3, H3Event, HTTPResponse, Middleware } from "h3";
2
+ import { BodyResult, MiddlewareOptions, RpcPluginOptions } from "@thednp/rpc";
3
+ import { ViteDevServer } from "vite";
4
+ //#region src/h3/types.d.ts
5
+ /**
6
+ * h3-specific middleware options, constrained to the `"h3"` adapter.
7
+ */
8
+ type H3MiddlewareOptions = MiddlewareOptions<"h3">;
9
+ /**
10
+ * h3 middleware handler signature used by the RPC middleware.
11
+ */
12
+ interface H3MiddlewareHooks {
13
+ /**
14
+ * The handler invoked for each matched request.
15
+ * @param event - h3 event object
16
+ * @param next - h3 next function
17
+ */
18
+ handler: Middleware;
19
+ }
20
+ /**
21
+ * h3 middleware factory: takes optional initial options and returns
22
+ * the h3-compatible handler.
23
+ */
24
+ type H3MiddlewareFn = <A extends RpcPluginOptions["adapter"] = "h3">(initialOptions?: Partial<H3MiddlewareOptions>) => H3MiddlewareHooks["handler"];
25
+ /**
26
+ * h3 application reference used by helpers that attach middleware to an app.
27
+ */
28
+ type H3App = H3;
29
+ /**
30
+ * h3 event extended with an optional pre-parsed body.
31
+ */
32
+ type H3EventWithBody = H3Event & {
33
+ body?: unknown;
34
+ };
35
+ //#endregion
36
+ //#region src/h3/createMiddleware.d.ts
37
+ /**
38
+ * Creates an h3 middleware with optional path and rpcPrefix filtering.
39
+ * Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.
40
+ * h3 URL is normalized via `event.url` (query strings are not part of the pathname).
41
+ * @param initialOptions - Options for rpcPrefix, path matching, and the handler function
42
+ * @returns An h3 middleware function
43
+ */
44
+ declare const createMiddleware: H3MiddlewareFn;
45
+ /**
46
+ * Creates the h3 RPC middleware that routes incoming requests to registered server functions.
47
+ * Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches
48
+ * to the matching function, and returns the JSON-serialized result.
49
+ * @param initialOptions - Options including rpcPrefix for URL routing
50
+ * @returns An h3 middleware function
51
+ */
52
+ declare const createRPCMiddleware: H3MiddlewareFn;
53
+ //#endregion
54
+ //#region src/h3/helpers.d.ts
55
+ /**
56
+ * Convenience function to load RPC config and attach the RPC middleware to an h3 app.
57
+ * Dynamically imports loadRPCConfig and registers the middleware.
58
+ * @param app - h3 application instance
59
+ */
60
+ declare function attachRPC(app: H3App): Promise<void>;
61
+ /**
62
+ * Attaches Vite's dev server middlewares to an h3 app for development mode.
63
+ * Uses the viteMiddleware wrapper to bridge Vite's Connect-compatible stack into h3.
64
+ * @param app - h3 application instance
65
+ * @param vite - Running Vite dev server
66
+ */
67
+ declare const attachVite: (app: H3App, vite: ViteDevServer) => void;
68
+ /**
69
+ * Creates an h3-compatible middleware from a Vite dev server middleware stack.
70
+ * Bridges the Connect/Express middleware interface to h3's event-based request/response model.
71
+ * Supports both Node.js and web runtimes with separate polyfill paths.
72
+ * @param vite - Running Vite dev server
73
+ * @returns An h3 middleware function
74
+ */
75
+ declare const viteMiddleware: (vite: ViteDevServer) => Middleware;
76
+ /**
77
+ * Reads and parses the HTTP request body from an h3 event.
78
+ * Supports JSON, text, urlencoded, and multipart content types.
79
+ * @param event - h3 event object
80
+ * @returns A promise resolving to the parsed body with its content type
81
+ */
82
+ declare const readBody: (event: H3Event) => Promise<BodyResult>;
83
+ /**
84
+ * Issues an HTTP redirect. h3's `redirect()` returns an `HTTPResponse`
85
+ * object that the handler must return (it never writes directly). Defaults
86
+ * to `303 See Other` for convention (Post/Redirect/Get).
87
+ * @param location - The URL to redirect to
88
+ * @param status - HTTP status code, defaults to 303
89
+ * @returns An h3 `HTTPResponse` to return from the handler
90
+ */
91
+ declare const redirect: (location: string, status?: number) => HTTPResponse;
92
+ //#endregion
93
+ export { type H3App, type H3EventWithBody, type H3MiddlewareFn, type H3MiddlewareHooks, type H3MiddlewareOptions, attachRPC, attachVite, createMiddleware, createRPCMiddleware, readBody, redirect, viteMiddleware };
94
+ //# sourceMappingURL=h3.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"h3.d.mts","names":[],"sources":["../../src/h3/types.d.ts","../../src/h3/createMiddleware.ts","../../src/h3/helpers.ts"],"mappings":";;;;;;;KAOY,sBAAsB;;;;UAKjB;;;;;;EAMf,SAAS;;;;;;KAOC,kBAAkB,UAAU,oCACtC,iBAAiB,QAAQ,yBACtB;;;;KAKO,QAAQ;;;;KAKR,kBAAkB;EAAY;;;;;;;;;;;cCH7B,kBAAkB;;;;;;;;cAoElB,qBAAqB;;;;;;;;iBCxFZ,UAAU,KAAK,QAAK;;;;;;;cAgB7B,aAAU,KAAS,OAAK,MAAQ;;;;;;;;cAWhC,iBAAc,MAAU,kBAAgB;;;;;;;cA6ExC,WAAQ,OAAiB,YAAU,QAAQ;;;;;;;;;cAmC3C,WAAQ,kBACH,oBAEf"}
package/dist/h3/h3.mjs ADDED
@@ -0,0 +1,245 @@
1
+ import { escapeRegExp, formatError, hasContentTypeMismatch, provideRequestContext, scanForServerFiles, serverFunctionsMap } from "@thednp/rpc/server";
2
+ import { HTTPResponse, redirect as redirect$1 } from "h3";
3
+ //#region src/constants.ts
4
+ const FUNCTION_NOT_FOUND = "Function not found";
5
+ const METHOD_NOT_ALLOWED = "Method Not Allowed";
6
+ const REQUEST_FORBIDDEN = "Forbidden";
7
+ const UNSUPPORTED_MEDIA_TYPE = "Unsupported Media Type";
8
+ const CLIENT_DISCONNECTED = "client disconnected";
9
+ /** Returns a warning when a middleware name is reused, preventing registration conflicts. @param name - The duplicate middleware name */
10
+ const MIDDLEWARE_NAME_USED = (name) => `The middleware name "${name}" is already used.`;
11
+ //#endregion
12
+ //#region src/options.ts
13
+ const defaultRPCOptions = {
14
+ rpcPrefix: "__rpc",
15
+ adapter: "express",
16
+ serverFiles: "exact",
17
+ scanRoot: void 0
18
+ };
19
+ const defaultMiddlewareOptions = {
20
+ rpcPrefix: void 0,
21
+ path: void 0,
22
+ origin: void 0
23
+ };
24
+ //#endregion
25
+ //#region src/h3/helpers.ts
26
+ /**
27
+ * Convenience function to load RPC config and attach the RPC middleware to an h3 app.
28
+ * Dynamically imports loadRPCConfig and registers the middleware.
29
+ * @param app - h3 application instance
30
+ */
31
+ async function attachRPC(app) {
32
+ const { loadRPCConfig } = await import("@thednp/rpc");
33
+ const { adapter: _adapter, ...options } = await loadRPCConfig();
34
+ app.use(createRPCMiddleware(options));
35
+ }
36
+ /**
37
+ * Attaches Vite's dev server middlewares to an h3 app for development mode.
38
+ * Uses the viteMiddleware wrapper to bridge Vite's Connect-compatible stack into h3.
39
+ * @param app - h3 application instance
40
+ * @param vite - Running Vite dev server
41
+ */
42
+ const attachVite = (app, vite) => {
43
+ app.use(viteMiddleware(vite));
44
+ };
45
+ /**
46
+ * Creates an h3-compatible middleware from a Vite dev server middleware stack.
47
+ * Bridges the Connect/Express middleware interface to h3's event-based request/response model.
48
+ * Supports both Node.js and web runtimes with separate polyfill paths.
49
+ * @param vite - Running Vite dev server
50
+ * @returns An h3 middleware function
51
+ */
52
+ const viteMiddleware = (vite) => {
53
+ return (event, next) => new Promise((resolve) => {
54
+ const node = event.runtime?.node;
55
+ if (node?.req && node?.res) {
56
+ const nodeReq = node.req;
57
+ const nodeRes = node.res;
58
+ let settled = false;
59
+ const settle = (value) => {
60
+ if (settled) return;
61
+ settled = true;
62
+ resolve(value);
63
+ };
64
+ nodeRes.once("close", () => settle(new Response(null)));
65
+ nodeRes.once("finish", () => settle(new Response(null)));
66
+ vite.middlewares(nodeReq, nodeRes, () => {
67
+ if (nodeRes.writableEnded || nodeRes.headersSent) settle(new Response(null));
68
+ else settle(next());
69
+ });
70
+ return;
71
+ }
72
+ let sent = false;
73
+ const headers = new Headers();
74
+ const req = {
75
+ url: event.url.pathname + event.url.search,
76
+ method: event.req.method,
77
+ headers: Object.fromEntries(event.req.headers)
78
+ };
79
+ vite.middlewares(req, {
80
+ setHeader(name, value) {
81
+ headers.set(name, String(value));
82
+ return this;
83
+ },
84
+ writeHead(status) {
85
+ return this;
86
+ },
87
+ end(body) {
88
+ sent = true;
89
+ resolve(new HTTPResponse(body == null ? "" : body, { headers }));
90
+ return this;
91
+ }
92
+ }, () => {
93
+ if (!sent) resolve(next());
94
+ });
95
+ });
96
+ };
97
+ /**
98
+ * Reads and parses the HTTP request body from an h3 event.
99
+ * Supports JSON, text, urlencoded, and multipart content types.
100
+ * @param event - h3 event object
101
+ * @returns A promise resolving to the parsed body with its content type
102
+ */
103
+ const readBody = async (event) => {
104
+ const contentType = event.req.headers.get("content-type")?.toLowerCase() || "";
105
+ const isJSON = contentType.includes("json");
106
+ const isMultipart = contentType.includes("multipart/form-data");
107
+ const isUrlEncoded = contentType.includes("urlencoded");
108
+ const text = await event.req.text();
109
+ if (isJSON) return {
110
+ contentType: "application/json",
111
+ data: JSON.parse(text)
112
+ };
113
+ return {
114
+ contentType: isMultipart ? "multipart/form-data" : isUrlEncoded ? "application/x-www-form-urlencoded" : "text/plain",
115
+ data: isMultipart ? { raw: text } : isUrlEncoded ? Object.fromEntries(new URLSearchParams(text)) : String(text)
116
+ };
117
+ };
118
+ /**
119
+ * Issues an HTTP redirect. h3's `redirect()` returns an `HTTPResponse`
120
+ * object that the handler must return (it never writes directly). Defaults
121
+ * to `303 See Other` for convention (Post/Redirect/Get).
122
+ * @param location - The URL to redirect to
123
+ * @param status - HTTP status code, defaults to 303
124
+ * @returns An h3 `HTTPResponse` to return from the handler
125
+ */
126
+ const redirect = (location, status = 303) => {
127
+ return redirect$1(location, status, status === 303 ? "See Other" : void 0);
128
+ };
129
+ //#endregion
130
+ //#region src/h3/createMiddleware.ts
131
+ let middlewareCount = 0;
132
+ const middlewareStack = /* @__PURE__ */ new Set();
133
+ /**
134
+ * Creates an h3 middleware with optional path and rpcPrefix filtering.
135
+ * Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.
136
+ * h3 URL is normalized via `event.url` (query strings are not part of the pathname).
137
+ * @param initialOptions - Options for rpcPrefix, path matching, and the handler function
138
+ * @returns An h3 middleware function
139
+ */
140
+ const createMiddleware = (initialOptions = {}) => {
141
+ const options = Object.assign({}, defaultMiddlewareOptions, initialOptions);
142
+ const middlewareName = options.name;
143
+ const rpcPrefix = options.rpcPrefix;
144
+ const path = options.path;
145
+ const handler = options.handler;
146
+ let name = middlewareName;
147
+ if (!name) {
148
+ name = "viteRPCMiddleware-" + middlewareCount;
149
+ middlewareCount += 1;
150
+ }
151
+ if (middlewareStack.has(name)) throw new Error(MIDDLEWARE_NAME_USED(name));
152
+ middlewareStack.add(name);
153
+ const prefixRegex = rpcPrefix ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`) : null;
154
+ const pathMatcher = path ? typeof path === "string" ? new RegExp(path) : path : null;
155
+ const middlewareHandler = async (event, next) => {
156
+ const url = event.url.pathname;
157
+ if (serverFunctionsMap.size === 0) await scanForServerFiles();
158
+ if (!handler) return next();
159
+ if (pathMatcher && !pathMatcher.test(url)) return next();
160
+ if (prefixRegex && !prefixRegex.test(url)) return next();
161
+ return handler(event, next);
162
+ };
163
+ Object.defineProperty(middlewareHandler, "name", { value: name });
164
+ return middlewareHandler;
165
+ };
166
+ /**
167
+ * Creates the h3 RPC middleware that routes incoming requests to registered server functions.
168
+ * Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches
169
+ * to the matching function, and returns the JSON-serialized result.
170
+ * @param initialOptions - Options including rpcPrefix for URL routing
171
+ * @returns An h3 middleware function
172
+ */
173
+ const createRPCMiddleware = (initialOptions = {}) => {
174
+ const options = Object.assign({}, defaultMiddlewareOptions, { rpcPrefix: defaultRPCOptions.rpcPrefix }, initialOptions);
175
+ const rpcPrefix = options.rpcPrefix;
176
+ const prefixRegex = rpcPrefix ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`) : null;
177
+ const prefixReplace = `/${rpcPrefix}/`;
178
+ return createMiddleware({
179
+ ...options,
180
+ handler: async (event, _next) => {
181
+ const url = event.url.pathname;
182
+ if (prefixRegex && !prefixRegex.test(url)) return;
183
+ const origin = options.origin;
184
+ const requestOrigin = event.req.headers.get("origin") ?? void 0;
185
+ if (origin && requestOrigin && requestOrigin !== origin) {
186
+ event.res.status = 403;
187
+ return { error: REQUEST_FORBIDDEN };
188
+ }
189
+ const functionName = url.replace(prefixReplace, "");
190
+ const serverFunction = serverFunctionsMap.get(functionName);
191
+ if (!serverFunction) {
192
+ event.res.status = 404;
193
+ return { error: FUNCTION_NOT_FOUND };
194
+ }
195
+ try {
196
+ const method = serverFunction.options?.method || "POST";
197
+ if (event.req.method.toUpperCase() !== method) {
198
+ event.res.status = 405;
199
+ return { error: METHOD_NOT_ALLOWED };
200
+ }
201
+ let args = [];
202
+ if (method === "GET") {
203
+ const raw = event.url.searchParams.get("args");
204
+ if (raw) args = JSON.parse(raw);
205
+ } else {
206
+ if (hasContentTypeMismatch(serverFunction.options?.contentType ?? "application/json", event.req.headers.get("content-type") ?? void 0)) {
207
+ event.res.status = 415;
208
+ return { error: UNSUPPORTED_MEDIA_TYPE };
209
+ }
210
+ const body = await readBody(event);
211
+ args = Array.isArray(body.data) ? body.data : [body.data];
212
+ }
213
+ const requestEvent = {
214
+ request: event.req,
215
+ response: event.res,
216
+ nativeEvent: event,
217
+ locals: event.context,
218
+ redirect: (location, status = 303) => {
219
+ requestEvent.redirected = {
220
+ location,
221
+ status
222
+ };
223
+ }
224
+ };
225
+ const fnResult = provideRequestContext(requestEvent, () => serverFunction.handler(...args));
226
+ const onClose = () => fnResult.cancel(CLIENT_DISCONNECTED);
227
+ const nodeReq = event.runtime?.node?.req;
228
+ if (nodeReq) nodeReq.on("close", onClose);
229
+ const result = await fnResult.data;
230
+ if (nodeReq) nodeReq.off("close", onClose);
231
+ if (requestEvent.redirected) return redirect(requestEvent.redirected.location, requestEvent.redirected.status);
232
+ return { data: result };
233
+ } catch (err) {
234
+ console.error(String(err));
235
+ const isProduction = process.env.NODE_ENV === "production";
236
+ event.res.status = 500;
237
+ return formatError(err, isProduction);
238
+ }
239
+ }
240
+ });
241
+ };
242
+ //#endregion
243
+ export { attachRPC, attachVite, createMiddleware, createRPCMiddleware, readBody, redirect, viteMiddleware };
244
+
245
+ //# sourceMappingURL=h3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"h3.mjs","names":["h3Redirect","h3Redirect"],"sources":["../../src/constants.ts","../../src/options.ts","../../src/h3/helpers.ts","../../src/h3/createMiddleware.ts"],"sourcesContent":["export const OPERATION_ABORTED = \"Operation aborted\";\n\nexport const REQUEST_CANCELLED = \"Request was cancelled\";\n\nexport const FETCH_ERROR_PREFIX = \"Fetch error: \";\n\nexport const NO_SERVER_FUNCTION_FOUND = \"No server function found.\";\n\nexport const ERROR_LOADING_FILE = \"Error loading file:\";\n\nexport const FUNCTION_NOT_FOUND = \"Function not found\";\n\nexport const METHOD_NOT_ALLOWED = \"Method Not Allowed\";\n\nexport const REQUEST_FORBIDDEN = \"Forbidden\";\n\nexport const UNSUPPORTED_MEDIA_TYPE = \"Unsupported Media Type\";\n\nexport const INTERNAL_SERVER_ERROR = \"Internal Server Error\";\n\nexport const CLIENT_DISCONNECTED = \"client disconnected\";\n\n/** Returns a warning when a middleware name is reused, preventing registration conflicts. @param name - The duplicate middleware name */\nexport const MIDDLEWARE_NAME_USED = (name: string) =>\n `The middleware name \"${name}\" is already used.`;\n\n/** Error message when a value fails the safe-identifier validation. @param label - What kind of value was being validated. @param name - The rejected value */\nexport const INVALID_IDENTIFIER = (label: string, name: string) =>\n `Invalid ${label}: \"${name}\" must match /^[A-Za-z_$][A-Za-z0-9_$]*$/`;\n\n/** Error message when a value fails the safe-path-segment validation. @param label - What kind of value was being validated. @param segment - The rejected value */\nexport const INVALID_PATH_SEGMENT = (label: string, segment: string) =>\n `Invalid ${label}: \"${segment}\" must match /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/`;\n\n/** Warning message when a specified RPC config file cannot be resolved on disk. @param configFile - The requested config filename. @param configFilePath - The resolved absolute path */\nexport const CONFIG_FILE_NOT_FOUND = (\n configFile: string,\n configFilePath: string,\n) =>\n ` ⚠︎ The specified RPC config file ${configFile} cannot be found at ${configFilePath}, loading the defaults..`;\n\nexport const NO_CONFIG_FOUND = ` ⚡︎ No RPC config found, loading the defaults..`;\n\nexport const FAILED_LOAD_CONFIG = ` ⚠︎ Failed to load RPC config:`;\n\n/** Error template for duplicate server function names across files. @param name - The duplicate registered name */\nexport const DUPLICATE_FUNCTION_NAME = (name: string) =>\n `Duplicate server function \"${name}\" detected. Each server function must have a unique name. Remove or rename the duplicate.`;\n","import type {\n MiddlewareOptions,\n RpcPluginOptions,\n ServerFunctionOptions,\n} from \"./types.d.ts\";\n\nexport const defaultServerFnOptions = {\n contentType: \"application/json\",\n credentials: \"same-origin\",\n method: \"POST\",\n} satisfies ServerFunctionOptions;\n\nexport const defaultRPCOptions: RpcPluginOptions = {\n rpcPrefix: \"__rpc\",\n adapter: \"express\",\n serverFiles: \"exact\",\n scanRoot: undefined,\n};\n\nexport const defaultMiddlewareOptions = {\n rpcPrefix: undefined,\n path: undefined,\n origin: undefined,\n} satisfies MiddlewareOptions;\n","// src/h3/helpers.ts\nimport type { H3Event, Middleware } from \"h3\";\nimport { HTTPResponse, redirect as h3Redirect } from \"h3\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { ViteDevServer } from \"vite\";\nimport type { BodyResult } from \"@thednp/rpc\";\nimport type { H3App } from \"./types.d.ts\";\nimport { createRPCMiddleware } from \"./createMiddleware.ts\";\n\n/**\n * Convenience function to load RPC config and attach the RPC middleware to an h3 app.\n * Dynamically imports loadRPCConfig and registers the middleware.\n * @param app - h3 application instance\n */\nexport async function attachRPC(app: H3App) {\n // The main plugin entry statically imports Vite, so loadRPCConfig is\n // imported lazily: function bundles that never call attachRPC (e.g.\n // serverless functions) keep Vite out of the bundle (or externalized).\n const { loadRPCConfig } = await import(\"@thednp/rpc\");\n const { adapter: _adapter, ...options } = await loadRPCConfig();\n\n app.use(createRPCMiddleware(options));\n}\n\n/**\n * Attaches Vite's dev server middlewares to an h3 app for development mode.\n * Uses the viteMiddleware wrapper to bridge Vite's Connect-compatible stack into h3.\n * @param app - h3 application instance\n * @param vite - Running Vite dev server\n */\nexport const attachVite = (app: H3App, vite: ViteDevServer): void => {\n app.use(viteMiddleware(vite));\n};\n\n/**\n * Creates an h3-compatible middleware from a Vite dev server middleware stack.\n * Bridges the Connect/Express middleware interface to h3's event-based request/response model.\n * Supports both Node.js and web runtimes with separate polyfill paths.\n * @param vite - Running Vite dev server\n * @returns An h3 middleware function\n */\nexport const viteMiddleware = (vite: ViteDevServer): Middleware => {\n return (event, next) =>\n new Promise((resolve) => {\n const node = event.runtime?.node;\n if (node?.req && node?.res) {\n const nodeReq = node.req;\n const nodeRes = node.res;\n // ─── Node.js runtime ─────────────────────────────────────────────\n // Forward to the real node req/res: if the Vite/Connect stack writes\n // the response, the socket is already flushed (dev-mode asset serving,\n // HMR). Connect never calls the final callback once a middleware has\n // written the response, so also settle on the response lifecycle\n // events. Stop the chain with an empty response once the socket is\n // used; the runtime's write guard prevents a second write. When the\n // stack passes through, continue to the next middleware.\n let settled = false;\n const settle = (value: unknown) => {\n // istanbul ignore if\n if (settled) return;\n settled = true;\n resolve(value);\n };\n nodeRes.once(\"close\", () => settle(new Response(null)));\n nodeRes.once(\"finish\", () => settle(new Response(null)));\n vite.middlewares(\n nodeReq as IncomingMessage,\n nodeRes as ServerResponse,\n () => {\n if (nodeRes.writableEnded || nodeRes.headersSent) {\n settle(new Response(null));\n } else {\n settle(next());\n }\n },\n );\n return;\n }\n\n // ─── Web runtime fallback ──────────────────────────────────────────\n let sent = false;\n const headers = new Headers();\n const req = {\n url: event.url.pathname + event.url.search,\n method: event.req.method,\n headers: Object.fromEntries(event.req.headers),\n } as IncomingMessage;\n const res = {\n setHeader(name: string, value: unknown) {\n headers.set(name, String(value));\n return this;\n },\n writeHead(status: number) {\n void status;\n return this;\n },\n end(body?: unknown) {\n sent = true;\n resolve(\n new HTTPResponse(body == null ? \"\" : (body as BodyInit), {\n headers,\n }),\n );\n return this;\n },\n } as ServerResponse;\n vite.middlewares(req, res, () => {\n if (!sent) resolve(next());\n });\n });\n};\n\n/**\n * Reads and parses the HTTP request body from an h3 event.\n * Supports JSON, text, urlencoded, and multipart content types.\n * @param event - h3 event object\n * @returns A promise resolving to the parsed body with its content type\n */\nexport const readBody = async (event: H3Event): Promise<BodyResult> => {\n const contentType = event.req.headers.get(\"content-type\")?.toLowerCase() ||\n \"\";\n const isJSON = contentType.includes(\"json\");\n const isMultipart = contentType.includes(\"multipart/form-data\");\n const isUrlEncoded = contentType.includes(\"urlencoded\");\n const text = await event.req.text();\n if (isJSON) {\n return {\n contentType: \"application/json\",\n data: JSON.parse(text),\n } as BodyResult;\n }\n return {\n contentType: isMultipart\n ? \"multipart/form-data\"\n : isUrlEncoded\n ? \"application/x-www-form-urlencoded\"\n : \"text/plain\",\n data: isMultipart\n ? ({ raw: text } as Record<string, unknown>)\n : isUrlEncoded\n ? Object.fromEntries(new URLSearchParams(text))\n : String(text),\n } as BodyResult;\n};\n\n/**\n * Issues an HTTP redirect. h3's `redirect()` returns an `HTTPResponse`\n * object that the handler must return (it never writes directly). Defaults\n * to `303 See Other` for convention (Post/Redirect/Get).\n * @param location - The URL to redirect to\n * @param status - HTTP status code, defaults to 303\n * @returns An h3 `HTTPResponse` to return from the handler\n */\nexport const redirect = (\n location: string,\n status = 303,\n): HTTPResponse => {\n return h3Redirect(\n location,\n status,\n status === 303 ? \"See Other\" : undefined,\n );\n};\n","// src/h3/createMiddleware.ts\nimport type { H3Event, Middleware } from \"h3\";\nimport type { H3MiddlewareFn, H3MiddlewareOptions } from \"./types.d.ts\";\nimport type { JsonValue } from \"@thednp/rpc\";\nimport type { RequestEvent } from \"@thednp/rpc/server\";\nimport {\n escapeRegExp,\n formatError,\n hasContentTypeMismatch,\n provideRequestContext,\n scanForServerFiles,\n serverFunctionsMap,\n} from \"@thednp/rpc/server\";\nimport {\n CLIENT_DISCONNECTED,\n FUNCTION_NOT_FOUND,\n METHOD_NOT_ALLOWED,\n MIDDLEWARE_NAME_USED,\n REQUEST_FORBIDDEN,\n UNSUPPORTED_MEDIA_TYPE,\n} from \"../constants.ts\";\nimport { defaultMiddlewareOptions, defaultRPCOptions } from \"../options.ts\";\nimport { readBody, redirect as h3Redirect } from \"./helpers.ts\";\n\nlet middlewareCount = 0;\nconst middlewareStack = new Set<string>();\n\n/**\n * Creates an h3 middleware with optional path and rpcPrefix filtering.\n * Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.\n * h3 URL is normalized via `event.url` (query strings are not part of the pathname).\n * @param initialOptions - Options for rpcPrefix, path matching, and the handler function\n * @returns An h3 middleware function\n */\nexport const createMiddleware: H3MiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n initialOptions,\n ) as H3MiddlewareOptions;\n\n const middlewareName = options.name;\n const rpcPrefix = options.rpcPrefix;\n const path = options.path;\n const handler = options.handler;\n\n let name = middlewareName;\n if (!name) {\n name = \"viteRPCMiddleware-\" + middlewareCount;\n middlewareCount += 1;\n }\n if (middlewareStack.has(name)) {\n throw new Error(MIDDLEWARE_NAME_USED(name));\n }\n middlewareStack.add(name);\n\n // Hoist regex compilation out of per-request path. Escape the prefix to\n // prevent regex injection via metacharacters in the config string.\n const prefixRegex: RegExp | null = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : null;\n const pathMatcher: RegExp | null = path\n ? (typeof path === \"string\" ? new RegExp(path) : path)\n : null;\n\n const middlewareHandler: Middleware = async (event: H3Event, next) => {\n const url = event.url.pathname;\n\n if (serverFunctionsMap.size === 0) {\n await scanForServerFiles();\n }\n\n // No need to continue when no handler provided\n if (!handler) {\n return next();\n }\n\n if (pathMatcher && !pathMatcher.test(url)) {\n return next();\n }\n\n if (prefixRegex && !prefixRegex.test(url)) {\n return next();\n }\n\n return handler(event, next);\n };\n\n Object.defineProperty(middlewareHandler, \"name\", {\n value: name,\n });\n\n return middlewareHandler;\n};\n\n/**\n * Creates the h3 RPC middleware that routes incoming requests to registered server functions.\n * Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches\n * to the matching function, and returns the JSON-serialized result.\n * @param initialOptions - Options including rpcPrefix for URL routing\n * @returns An h3 middleware function\n */\nexport const createRPCMiddleware: H3MiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n { rpcPrefix: defaultRPCOptions.rpcPrefix },\n initialOptions,\n ) as H3MiddlewareOptions;\n\n // Hoist prefix regex (escaped) and the literal prefix-for-replace out of the\n // per-request handler to avoid regex injection and per-request compilation.\n const rpcPrefix = options.rpcPrefix;\n const prefixRegex = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${rpcPrefix}/`;\n\n return createMiddleware({\n ...options,\n handler: async (event: H3Event, _next?: () => unknown) => {\n const url = event.url.pathname;\n // const { rpcPrefix } = options;\n\n // Defense-in-depth: validate prefix match via escaped regex even though\n // the outer createMiddleware gates on the same prefix already.\n // istanbul ignore if\n if (prefixRegex && !prefixRegex.test(url)) {\n /* istanbul ignore next */\n return undefined;\n }\n\n // Optional origin check: reject requests whose Origin header does not\n // match the configured origin. Requests without an Origin header\n // (curl, native clients) pass through unchecked.\n const origin = options.origin;\n const requestOrigin = event.req.headers.get(\"origin\") ?? undefined;\n if (origin && requestOrigin && requestOrigin !== origin) {\n event.res.status = 403;\n return { error: REQUEST_FORBIDDEN };\n }\n\n const functionName = url.replace(prefixReplace, \"\");\n const serverFunction = serverFunctionsMap.get(functionName);\n\n if (!serverFunction) {\n event.res.status = 404;\n return { error: FUNCTION_NOT_FOUND };\n }\n\n try {\n const method = serverFunction.options?.method || \"POST\";\n if (event.req.method.toUpperCase() !== method) {\n event.res.status = 405;\n return { error: METHOD_NOT_ALLOWED };\n }\n\n let args: JsonValue[] = [];\n if (method === \"GET\") {\n const raw = event.url.searchParams.get(\"args\");\n // istanbul ignore else\n if (raw) args = JSON.parse(raw);\n } else {\n // Content-type enforcement: strict for json/text, lenient between forms.\n // Requests without a Content-Type header are exempt (curl/GET compat).\n // Checked BEFORE readBody so mismatched bodies are never buffered.\n if (\n hasContentTypeMismatch(\n serverFunction.options?.contentType ?? \"application/json\",\n event.req.headers.get(\"content-type\") ?? undefined,\n )\n ) {\n event.res.status = 415;\n return { error: UNSUPPORTED_MEDIA_TYPE };\n }\n const body = await readBody(event);\n args = Array.isArray(body.data)\n ? body.data as JsonValue[]\n : [body.data as JsonValue];\n }\n const requestEvent: RequestEvent = {\n request: event.req,\n response: event.res,\n nativeEvent: event,\n locals: event.context,\n // h3's `redirect()` returns an `HTTPResponse` (never writes directly),\n // so the bound redirect only records the intent; the middleware uses\n // it after the dispatch to return `redirect(location, status)`.\n redirect: (location, status = 303) => {\n requestEvent.redirected = { location, status };\n },\n };\n const fnResult = provideRequestContext(\n requestEvent,\n () => serverFunction.handler(...args),\n );\n const onClose = () => fnResult.cancel(CLIENT_DISCONNECTED);\n // The node runtime gives us the raw incoming stream for close events;\n // other runtimes have no node req, so the abort hook is skipped.\n const nodeReq = event.runtime?.node?.req;\n if (nodeReq) nodeReq.on(\"close\", onClose);\n const result = await fnResult.data;\n if (nodeReq) nodeReq.off(\"close\", onClose);\n\n if (requestEvent.redirected) {\n return h3Redirect(\n requestEvent.redirected.location,\n requestEvent.redirected.status,\n );\n }\n\n return { data: result };\n } catch (err) {\n console.error(String(err));\n const isProduction = process.env.NODE_ENV === \"production\";\n event.res.status = 500;\n\n return formatError(err, isProduction);\n }\n },\n });\n};\n"],"mappings":"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"}
@@ -3,10 +3,12 @@ import "@thednp/rpc";
3
3
  import "express";
4
4
  import "hono";
5
5
  import "@hono/node-server";
6
+ import "hono/utils/http-status";
6
7
  import "hono/factory";
7
8
  import "fastify";
8
9
  import "fastify-plugin";
9
10
  import "koa";
11
+ import "h3";
10
12
  //#region src/types.d.ts
11
13
  /**
12
14
  * Fetch `credentials` policy used by the generated client modules.
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.mts","names":[],"sources":["../../src/types.d.ts","../../src/client-helpers.ts"],"mappings":";;;;;;;;;;;;;KAmEY;;;;;KAsCA;;;;KAIA;GAAgB,cAAc,YAAY;;;;;KAI1C,aAAa,WAAW;;;;KAIxB,YAAY,gBAAgB,YAAY;;;;;KAqNxC;;EAEV,MAAM,QAAQ;;EAEd,SAAS;;;;;;;;;;;;cClUE,iBAAc,UACf,aACT,QAAQ;;;;;;;;;;;;;;cAyBE,cAAW,MAChB,UAAQ,SACL,aAAW,aACP,aAAW,gBACV,cACF,4BAEX"}
1
+ {"version":3,"file":"helpers.d.mts","names":[],"sources":["../../src/types.d.ts","../../src/client-helpers.ts"],"mappings":";;;;;;;;;;;;;;;KA2EY;;;;;KA0CA;;;;KAIA;GAAgB,cAAc,YAAY;;;;;KAI1C,aAAa,WAAW;;;;KAIxB,YAAY,gBAAgB,YAAY;;;;;KAqNxC;;EAEV,MAAM,QAAQ;;EAEd,SAAS;;;;;;;;;;;;cC9UE,iBAAc,UACf,aACT,QAAQ;;;;;;;;;;;;;;cAyBE,cAAW,MAChB,UAAQ,SACL,aAAW,aACP,aAAW,gBACV,cACF,4BAEX"}
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/constants.ts","../../src/client-helpers.ts"],"sourcesContent":["export const OPERATION_ABORTED = \"Operation aborted\";\n\nexport const REQUEST_CANCELLED = \"Request was cancelled\";\n\nexport const FETCH_ERROR_PREFIX = \"Fetch error: \";\n\nexport const NO_SERVER_FUNCTION_FOUND = \"No server function found.\";\n\nexport const ERROR_LOADING_FILE = \"Error loading file:\";\n\nexport const FUNCTION_NOT_FOUND = \"Function not found\";\n\nexport const METHOD_NOT_ALLOWED = \"Method Not Allowed\";\n\nexport const REQUEST_FORBIDDEN = \"Forbidden\";\n\nexport const INTERNAL_SERVER_ERROR = \"Internal Server Error\";\n\nexport const CLIENT_DISCONNECTED = \"client disconnected\";\n\n/** Returns a warning when a middleware name is reused, preventing registration conflicts. @param name - The duplicate middleware name */\nexport const MIDDLEWARE_NAME_USED = (name: string) =>\n `The middleware name \"${name}\" is already used.`;\n\n/** Error message when a value fails the safe-identifier validation. @param label - What kind of value was being validated. @param name - The rejected value */\nexport const INVALID_IDENTIFIER = (label: string, name: string) =>\n `Invalid ${label}: \"${name}\" must match /^[A-Za-z_$][A-Za-z0-9_$]*$/`;\n\n/** Error message when a value fails the safe-path-segment validation. @param label - What kind of value was being validated. @param segment - The rejected value */\nexport const INVALID_PATH_SEGMENT = (label: string, segment: string) =>\n `Invalid ${label}: \"${segment}\" must match /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/`;\n\n/** Warning message when a specified RPC config file cannot be resolved on disk. @param configFile - The requested config filename. @param configFilePath - The resolved absolute path */\nexport const CONFIG_FILE_NOT_FOUND = (\n configFile: string,\n configFilePath: string,\n) =>\n ` ⚠︎ The specified RPC config file ${configFile} cannot be found at ${configFilePath}, loading the defaults..`;\n\nexport const NO_CONFIG_FOUND = ` ⚡︎ No RPC config found, loading the defaults..`;\n\nexport const FAILED_LOAD_CONFIG = ` ⚠︎ Failed to load RPC config:`;\n\n/** Error template for duplicate server function names across files. @param name - The duplicate registered name */\nexport const DUPLICATE_FUNCTION_NAME = (name: string) =>\n `Duplicate server function \"${name}\" detected. Each server function must have a unique name. Remove or rename the duplicate.`;\n","/** @module Client-side helper utilities. Exports `handleResponse` for processing fetch responses and `innerModule` for creating AbortController-bound RPC fetch calls. This module is bundled into the generated client modules — keep it free of server-only code. */\nimport type { Credentials, InnerModReturn, JsonValue } from \"./types.d.ts\";\nimport { FETCH_ERROR_PREFIX, REQUEST_CANCELLED } from \"./constants.ts\";\n\n/**\n * Processes an HTTP fetch response from the RPC server.\n * On HTTP 499 or 408 (client cancellation), logs a warning and returns undefined.\n * On other error statuses, throws a Fetch error.\n * On success, parses JSON and returns `result.data` — or throws if `result.error` is set.\n * @param response - Fetch Response object from the RPC endpoint\n * @returns The response data, or void on cancellation\n */\nexport const handleResponse = async (\n response: Response,\n): Promise<JsonValue | void> => {\n if (!response.ok) {\n if (response.status === 499 || response.status === 408) {\n return console.warn(REQUEST_CANCELLED);\n }\n throw new Error(FETCH_ERROR_PREFIX + response.statusText);\n }\n const result = await response.json();\n if (result.error) throw new Error(result.error);\n return result.data;\n};\n\n/**\n * Creates an AbortController-bound fetch call for a single RPC function.\n * Used by the auto-generated client modules to issue HTTP requests with cancellation support.\n * GET requests carry arguments as an `?args=` JSON query parameter, since a fetch\n * request body is not allowed on GET.\n * @param body - Serialized request body (JSON string or raw text)\n * @param headers - HTTP headers (Content-Type, etc.)\n * @param credentials - Fetch credentials policy (\"same-origin\", \"include\", or \"omit\")\n * @param prefix - RPC endpoint prefix (e.g. \"__rpc\")\n * @param name - Registered server function name\n * @param method - HTTP method to use, \"POST\" by default\n * @returns An object with `data` (promise resolving to the server response) and `cancel` (abort function)\n */\nexport const innerModule = (\n body: BodyInit,\n headers: HeadersInit,\n credentials: Credentials,\n prefix: string,\n name: string,\n method?: \"GET\" | \"POST\",\n): InnerModReturn => {\n const controller = new AbortController();\n const cancel = (reason: string) => controller.abort(reason);\n\n const fetcher = async () => {\n try {\n const isGet = method === \"GET\";\n const url = isGet\n ? `/${prefix}/${name}?args=${encodeURIComponent(String(body))}`\n : `/${prefix}/${name}`;\n const response = await fetch(url, {\n method: isGet ? \"GET\" : \"POST\",\n headers,\n credentials,\n body: isGet ? undefined : body,\n signal: controller.signal,\n });\n return await handleResponse(response);\n } catch (err) {\n throw err;\n }\n };\n\n return {\n data: fetcher(),\n cancel,\n };\n};\n"],"mappings":";AAEA,MAAa,oBAAoB;AAEjC,MAAa,qBAAqB;;;;;;;;;;;ACQlC,MAAa,iBAAiB,OAC5B,aAC8B;CAC9B,IAAI,CAAC,SAAS,IAAI;EAChB,IAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KACjD,OAAO,QAAQ,KAAK,iBAAiB;EAEvC,MAAM,IAAI,MAAM,qBAAqB,SAAS,UAAU;CAC1D;CACA,MAAM,SAAS,MAAM,SAAS,KAAK;CACnC,IAAI,OAAO,OAAO,MAAM,IAAI,MAAM,OAAO,KAAK;CAC9C,OAAO,OAAO;AAChB;;;;;;;;;;;;;;AAeA,MAAa,eACX,MACA,SACA,aACA,QACA,MACA,WACmB;CACnB,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,WAAmB,WAAW,MAAM,MAAM;CAE1D,MAAM,UAAU,YAAY;EAC1B,IAAI;GACF,MAAM,QAAQ,WAAW;GACzB,MAAM,MAAM,QACR,IAAI,OAAO,GAAG,KAAK,QAAQ,mBAAmB,OAAO,IAAI,CAAC,MAC1D,IAAI,OAAO,GAAG;GAClB,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC,QAAQ,QAAQ,QAAQ;IACxB;IACA;IACA,MAAM,QAAQ,KAAA,IAAY;IAC1B,QAAQ,WAAW;GACrB,CAAC;GACD,OAAO,MAAM,eAAe,QAAQ;EACtC,SAAS,KAAK;GACZ,MAAM;EACR;CACF;CAEA,OAAO;EACL,MAAM,QAAQ;EACd;CACF;AACF"}
1
+ {"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/constants.ts","../../src/client-helpers.ts"],"sourcesContent":["export const OPERATION_ABORTED = \"Operation aborted\";\n\nexport const REQUEST_CANCELLED = \"Request was cancelled\";\n\nexport const FETCH_ERROR_PREFIX = \"Fetch error: \";\n\nexport const NO_SERVER_FUNCTION_FOUND = \"No server function found.\";\n\nexport const ERROR_LOADING_FILE = \"Error loading file:\";\n\nexport const FUNCTION_NOT_FOUND = \"Function not found\";\n\nexport const METHOD_NOT_ALLOWED = \"Method Not Allowed\";\n\nexport const REQUEST_FORBIDDEN = \"Forbidden\";\n\nexport const UNSUPPORTED_MEDIA_TYPE = \"Unsupported Media Type\";\n\nexport const INTERNAL_SERVER_ERROR = \"Internal Server Error\";\n\nexport const CLIENT_DISCONNECTED = \"client disconnected\";\n\n/** Returns a warning when a middleware name is reused, preventing registration conflicts. @param name - The duplicate middleware name */\nexport const MIDDLEWARE_NAME_USED = (name: string) =>\n `The middleware name \"${name}\" is already used.`;\n\n/** Error message when a value fails the safe-identifier validation. @param label - What kind of value was being validated. @param name - The rejected value */\nexport const INVALID_IDENTIFIER = (label: string, name: string) =>\n `Invalid ${label}: \"${name}\" must match /^[A-Za-z_$][A-Za-z0-9_$]*$/`;\n\n/** Error message when a value fails the safe-path-segment validation. @param label - What kind of value was being validated. @param segment - The rejected value */\nexport const INVALID_PATH_SEGMENT = (label: string, segment: string) =>\n `Invalid ${label}: \"${segment}\" must match /^[A-Za-z0-9_$][A-Za-z0-9_$/-]*$/`;\n\n/** Warning message when a specified RPC config file cannot be resolved on disk. @param configFile - The requested config filename. @param configFilePath - The resolved absolute path */\nexport const CONFIG_FILE_NOT_FOUND = (\n configFile: string,\n configFilePath: string,\n) =>\n ` ⚠︎ The specified RPC config file ${configFile} cannot be found at ${configFilePath}, loading the defaults..`;\n\nexport const NO_CONFIG_FOUND = ` ⚡︎ No RPC config found, loading the defaults..`;\n\nexport const FAILED_LOAD_CONFIG = ` ⚠︎ Failed to load RPC config:`;\n\n/** Error template for duplicate server function names across files. @param name - The duplicate registered name */\nexport const DUPLICATE_FUNCTION_NAME = (name: string) =>\n `Duplicate server function \"${name}\" detected. Each server function must have a unique name. Remove or rename the duplicate.`;\n","/** @module Client-side helper utilities. Exports `handleResponse` for processing fetch responses and `innerModule` for creating AbortController-bound RPC fetch calls. This module is bundled into the generated client modules — keep it free of server-only code. */\nimport type { Credentials, InnerModReturn, JsonValue } from \"./types.d.ts\";\nimport { FETCH_ERROR_PREFIX, REQUEST_CANCELLED } from \"./constants.ts\";\n\n/**\n * Processes an HTTP fetch response from the RPC server.\n * On HTTP 499 or 408 (client cancellation), logs a warning and returns undefined.\n * On other error statuses, throws a Fetch error.\n * On success, parses JSON and returns `result.data` — or throws if `result.error` is set.\n * @param response - Fetch Response object from the RPC endpoint\n * @returns The response data, or void on cancellation\n */\nexport const handleResponse = async (\n response: Response,\n): Promise<JsonValue | void> => {\n if (!response.ok) {\n if (response.status === 499 || response.status === 408) {\n return console.warn(REQUEST_CANCELLED);\n }\n throw new Error(FETCH_ERROR_PREFIX + response.statusText);\n }\n const result = await response.json();\n if (result.error) throw new Error(result.error);\n return result.data;\n};\n\n/**\n * Creates an AbortController-bound fetch call for a single RPC function.\n * Used by the auto-generated client modules to issue HTTP requests with cancellation support.\n * GET requests carry arguments as an `?args=` JSON query parameter, since a fetch\n * request body is not allowed on GET.\n * @param body - Serialized request body (JSON string or raw text)\n * @param headers - HTTP headers (Content-Type, etc.)\n * @param credentials - Fetch credentials policy (\"same-origin\", \"include\", or \"omit\")\n * @param prefix - RPC endpoint prefix (e.g. \"__rpc\")\n * @param name - Registered server function name\n * @param method - HTTP method to use, \"POST\" by default\n * @returns An object with `data` (promise resolving to the server response) and `cancel` (abort function)\n */\nexport const innerModule = (\n body: BodyInit,\n headers: HeadersInit,\n credentials: Credentials,\n prefix: string,\n name: string,\n method?: \"GET\" | \"POST\",\n): InnerModReturn => {\n const controller = new AbortController();\n const cancel = (reason: string) => controller.abort(reason);\n\n const fetcher = async () => {\n try {\n const isGet = method === \"GET\";\n const url = isGet\n ? `/${prefix}/${name}?args=${encodeURIComponent(String(body))}`\n : `/${prefix}/${name}`;\n const response = await fetch(url, {\n method: isGet ? \"GET\" : \"POST\",\n headers,\n credentials,\n body: isGet ? undefined : body,\n signal: controller.signal,\n });\n return await handleResponse(response);\n } catch (err) {\n throw err;\n }\n };\n\n return {\n data: fetcher(),\n cancel,\n };\n};\n"],"mappings":";AAEA,MAAa,oBAAoB;AAEjC,MAAa,qBAAqB;;;;;;;;;;;ACQlC,MAAa,iBAAiB,OAC5B,aAC8B;CAC9B,IAAI,CAAC,SAAS,IAAI;EAChB,IAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KACjD,OAAO,QAAQ,KAAK,iBAAiB;EAEvC,MAAM,IAAI,MAAM,qBAAqB,SAAS,UAAU;CAC1D;CACA,MAAM,SAAS,MAAM,SAAS,KAAK;CACnC,IAAI,OAAO,OAAO,MAAM,IAAI,MAAM,OAAO,KAAK;CAC9C,OAAO,OAAO;AAChB;;;;;;;;;;;;;;AAeA,MAAa,eACX,MACA,SACA,aACA,QACA,MACA,WACmB;CACnB,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,WAAmB,WAAW,MAAM,MAAM;CAE1D,MAAM,UAAU,YAAY;EAC1B,IAAI;GACF,MAAM,QAAQ,WAAW;GACzB,MAAM,MAAM,QACR,IAAI,OAAO,GAAG,KAAK,QAAQ,mBAAmB,OAAO,IAAI,CAAC,MAC1D,IAAI,OAAO,GAAG;GAClB,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC,QAAQ,QAAQ,QAAQ;IACxB;IACA;IACA,MAAM,QAAQ,KAAA,IAAY;IAC1B,QAAQ,WAAW;GACrB,CAAC;GACD,OAAO,MAAM,eAAe,QAAQ;EACtC,SAAS,KAAK;GACZ,MAAM;EACR;CACF;CAEA,OAAO;EACL,MAAM,QAAQ;EACd;CACF;AACF"}
@@ -1,13 +1,10 @@
1
1
  import { createMiddleware as createMiddleware$1 } from "hono/factory";
2
2
  import { Context, Hono, MiddlewareHandler } from "hono";
3
3
  import { IncomingMessage } from "node:http";
4
- import { MiddlewareOptions, RpcPluginOptions } from "@thednp/rpc";
4
+ import { BodyResult, MiddlewareOptions, RpcPluginOptions } from "@thednp/rpc";
5
5
  import { HttpBindings } from "@hono/node-server";
6
6
  import { ViteDevServer } from "vite";
7
- import "express";
8
- import "fastify";
9
- import "fastify-plugin";
10
- import "koa";
7
+ import { RedirectStatusCode } from "hono/utils/http-status";
11
8
  //#region src/hono/types.d.ts
12
9
  /**
13
10
  * Node incoming message with an optional pre-parsed body.
@@ -50,40 +47,6 @@ declare const createMiddleware: HonoMiddlewareFn;
50
47
  */
51
48
  declare const createRPCMiddleware: HonoMiddlewareFn;
52
49
  //#endregion
53
- //#region src/types.d.ts
54
- /**
55
- * Parsed request body result discriminated by content type.
56
- */
57
- type BodyResult = {
58
- contentType: "application/json";
59
- data: JsonValue;
60
- } | {
61
- contentType: "text/plain";
62
- data: string;
63
- } | {
64
- contentType: "multipart/form-data";
65
- data: Record<string, unknown>;
66
- };
67
- // primitives and their compositions
68
- /**
69
- * Primitive JSON values, including `undefined` for optional parameters.
70
- */
71
- type JsonPrimitive = string | number | boolean | null | undefined;
72
- /**
73
- * A JSON object whose values are JSON values or arrays.
74
- */
75
- type JsonObject = {
76
- [key: string]: JsonValue | JsonArray;
77
- };
78
- /**
79
- * A JSON array of JSON values.
80
- */
81
- type JsonArray = (FormData | JsonValue)[];
82
- /**
83
- * Any JSON-serializable value: primitive, array, or object.
84
- */
85
- type JsonValue = JsonPrimitive | JsonArray | JsonObject;
86
- //#endregion
87
50
  //#region src/hono/helpers.d.ts
88
51
  /**
89
52
  * Convenience function to load RPC config and attach the RPC middleware to a Hono app.
@@ -116,6 +79,17 @@ declare const viteMiddleware: (vite: ViteDevServer) => ReturnType<typeof createM
116
79
  * @returns A promise resolving to the parsed body with its content type
117
80
  */
118
81
  declare const readBody: (c: Context) => Promise<BodyResult>;
82
+ /**
83
+ * Issues an HTTP redirect on a Hono context. Hono's `c.redirect(location,
84
+ * status)` returns a `Response` object that the handler must return (it never
85
+ * writes directly). Defaults to `303 See Other` for convention
86
+ * (Post/Redirect/Get).
87
+ * @param c - Hono context
88
+ * @param location - The URL to redirect to
89
+ * @param status - HTTP status code, defaults to 303
90
+ * @returns A Hono `Response` to return from the handler
91
+ */
92
+ declare const redirect: (c: Context, location: string, status?: RedirectStatusCode) => Response;
119
93
  //#endregion
120
- export { type HonoMiddlewareFn, type HonoMiddlewareHooks, type HonoMiddlewareOptions, type IncomingWithBody, attachRPC, attachVite, createMiddleware, createRPCMiddleware, readBody, viteMiddleware };
94
+ export { type HonoMiddlewareFn, type HonoMiddlewareHooks, type HonoMiddlewareOptions, type IncomingWithBody, attachRPC, attachVite, createMiddleware, createRPCMiddleware, readBody, redirect, viteMiddleware };
121
95
  //# sourceMappingURL=hono.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hono.d.mts","names":[],"sources":["../../src/hono/types.d.ts","../../src/hono/createMiddleware.ts","../../src/types.d.ts","../../src/hono/helpers.ts"],"mappings":";;;;;;;;;;;;;;KAOY,mBAAmB;EAAoB;;;;;KAKvC,wBAAwB;;;;UAKnB;;EAEf,SAAS;;;;;;KAOC,oBAAoB,UAAU,sCACxC,iBAAiB,QAAQ,kBAAkB,QACxC;;;;;;;;;;cCAQ,kBAAkB;;;;;;;;cA0ElB,qBAAqB;;;;;;KC9BtB;EACN;EAAiC,MAAM;;EACvC;EAA2B;;EAC3B;EAAoC,MAAM;;;;;;KA8BpC;;;;KAIA;GAAgB,cAAc,YAAY;;;;;KAI1C,aAAa,WAAW;;;;KAIxB,YAAY,gBAAgB,YAAY;;;;;;;;iBCrG9B,UAAU,KAAK,OAAI;;;;;;;cAgB5B,aAAU,KAAS,MAAI,MAAQ;;;;;;;;;cAY/B,iBAAc,MACnB,kBACL,kBAAkB;EAAmB,UAAU;;;;;;;;cAgDrC,WAAQ,GAChB,YACF,QAAQ"}
1
+ {"version":3,"file":"hono.d.mts","names":[],"sources":["../../src/hono/types.d.ts","../../src/hono/createMiddleware.ts","../../src/hono/helpers.ts"],"mappings":";;;;;;;;;;;KAOY,mBAAmB;EAAoB;;;;;KAKvC,wBAAwB;;;;UAKnB;;EAEf,SAAS;;;;;;KAOC,oBAAoB,UAAU,sCACxC,iBAAiB,QAAQ,kBAAkB,QACxC;;;;;;;;;;cCQQ,kBAAkB;;;;;;;;cAuElB,qBAAqB;;;;;;;;iBCxFZ,UAAU,KAAK,OAAI;;;;;;;cAgB5B,aAAU,KAAS,MAAI,MAAQ;;;;;;;;;cAY/B,iBAAc,MACnB,kBACL,kBAAkB;EAAmB,UAAU;;;;;;;;cAgDrC,WAAQ,GAChB,YACF,QAAQ;;;;;;;;;;;cA4DE,WAAQ,GAChB,SAAO,kBACM,SACR,uBACP"}