@forinda/kickjs 8.5.0 → 8.7.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/dist/{adapter-989ssCBx.d.mts → adapter-DK8WDonT.d.mts} +138 -12
- package/dist/adapter-DK8WDonT.d.mts.map +1 -0
- package/dist/{app-module-OQsddkor.d.mts → app-module-DCA65GZZ.d.mts} +2 -2
- package/dist/{app-module-OQsddkor.d.mts.map → app-module-DCA65GZZ.d.mts.map} +1 -1
- package/dist/{application-BRvykQ6h.d.mts → application-B6Gp1nqK.d.mts} +113 -16
- package/dist/application-B6Gp1nqK.d.mts.map +1 -0
- package/dist/application-D2scwBz-.mjs +12 -0
- package/dist/application-D2scwBz-.mjs.map +1 -0
- package/dist/bootstrap-Dds2PVAn.mjs +12 -0
- package/dist/bootstrap-Dds2PVAn.mjs.map +1 -0
- package/dist/cache-C0qL0NSD.d.mts.map +1 -1
- package/dist/{client-ip-CW7HwSOm.mjs → client-ip-BuY7_pDD.mjs} +2 -2
- package/dist/{client-ip-CW7HwSOm.mjs.map → client-ip-BuY7_pDD.mjs.map} +1 -1
- package/dist/config/index.d.mts +1 -1
- package/dist/config/index.mjs +2 -2
- package/dist/{config-Cajrc3qr.mjs → config-DBebCpg_.mjs} +3 -3
- package/dist/{config-Cajrc3qr.mjs.map → config-DBebCpg_.mjs.map} +1 -1
- package/dist/container-Cqow_bOU.mjs +132 -0
- package/dist/container-Cqow_bOU.mjs.map +1 -0
- package/dist/context-CHuuc9r6.mjs +12 -0
- package/dist/context-CHuuc9r6.mjs.map +1 -0
- package/dist/{context-errors-Cj2_aDaj.mjs → context-errors-VduncZFQ.mjs} +2 -2
- package/dist/{context-errors-Cj2_aDaj.mjs.map → context-errors-VduncZFQ.mjs.map} +1 -1
- package/dist/{contributor-runner-DQCZLpvk.mjs → contributor-runner-BoVekHyj.mjs} +3 -3
- package/dist/{contributor-runner-DQCZLpvk.mjs.map → contributor-runner-BoVekHyj.mjs.map} +1 -1
- package/dist/core/adapter.d.mts +2 -2
- package/dist/core/adapter.mjs +1 -1
- package/dist/core/app-module.d.mts +1 -1
- package/dist/core/app-module.mjs +1 -1
- package/dist/core/cache.mjs +2 -2
- package/dist/core/cache.mjs.map +1 -1
- package/dist/core/container.mjs +2 -2
- package/dist/core/cron.d.mts +2 -2
- package/dist/core/cron.mjs +2 -2
- package/dist/core/cron.mjs.map +1 -1
- package/dist/core/decorators.d.mts +1 -1
- package/dist/core/decorators.mjs +2 -2
- package/dist/core/decorators.mjs.map +1 -1
- package/dist/core/errors.mjs +1 -1
- package/dist/core/interfaces.mjs +1 -1
- package/dist/core/logger.mjs +1 -1
- package/dist/core/path.mjs +1 -1
- package/dist/core/plugin.d.mts +1 -1
- package/dist/core/plugin.mjs +1 -1
- package/dist/core/reactivity.mjs +1 -1
- package/dist/{core-DZmGThrF.mjs → core-Chc61ahe.mjs} +3 -3
- package/dist/{core-DZmGThrF.mjs.map → core-Chc61ahe.mjs.map} +1 -1
- package/dist/cron-BTGytNg-.d.mts +122 -0
- package/dist/cron-BTGytNg-.d.mts.map +1 -0
- package/dist/{csrf-DDwUOF45.d.mts → csrf-COFjRrUz.d.mts} +3 -3
- package/dist/{csrf-DDwUOF45.d.mts.map → csrf-COFjRrUz.d.mts.map} +1 -1
- package/dist/{csrf-DPPIM72I.mjs → csrf-D-8W1iv7.mjs} +3 -3
- package/dist/csrf-D-8W1iv7.mjs.map +1 -0
- package/dist/{decorate-DHyinlPY.mjs → decorate-D9n58F0b.mjs} +1 -1
- package/dist/{decorators-mg0qJ6Qc.d.mts → decorators-DOcWsXci.d.mts} +2 -2
- package/dist/{decorators-mg0qJ6Qc.d.mts.map → decorators-DOcWsXci.d.mts.map} +1 -1
- package/dist/{define-adapter-0asM4v0B.mjs → define-adapter-BJunWqi0.mjs} +2 -2
- package/dist/{define-adapter-0asM4v0B.mjs.map → define-adapter-BJunWqi0.mjs.map} +1 -1
- package/dist/{disposables-BYYy0y_z.mjs → disposables-BFIEp5VA.mjs} +2 -2
- package/dist/{disposables-BYYy0y_z.mjs.map → disposables-BFIEp5VA.mjs.map} +1 -1
- package/dist/{env-DWP_2owJ.mjs → env-8BLpzy-M.mjs} +3 -3
- package/dist/{env-DWP_2owJ.mjs.map → env-8BLpzy-M.mjs.map} +1 -1
- package/dist/{error-handler-BNpeUs-0.d.mts → error-handler-DxF2EogM.d.mts} +2 -2
- package/dist/{error-handler-BNpeUs-0.d.mts.map → error-handler-DxF2EogM.d.mts.map} +1 -1
- package/dist/{handler-B4MQmJR5.mjs → handler-MSvgGVk0.mjs} +3 -3
- package/dist/handler-MSvgGVk0.mjs.map +1 -0
- package/dist/http/application.d.mts +2 -2
- package/dist/http/application.mjs +2 -2
- package/dist/http/bootstrap.d.mts +1 -1
- package/dist/http/bootstrap.mjs +2 -2
- package/dist/http/context.d.mts +2 -2
- package/dist/http/context.mjs +2 -2
- package/dist/http/middleware/cors.mjs +1 -1
- package/dist/http/middleware/csrf.d.mts +1 -1
- package/dist/http/middleware/csrf.mjs +2 -2
- package/dist/http/middleware/error-handler.d.mts +1 -1
- package/dist/http/middleware/error-handler.mjs +2 -2
- package/dist/http/middleware/helmet.mjs +1 -1
- package/dist/http/middleware/rate-limit.d.mts +1 -1
- package/dist/http/middleware/rate-limit.mjs +2 -2
- package/dist/http/middleware/request-id.mjs +1 -1
- package/dist/http/middleware/request-logger.mjs +2 -2
- package/dist/http/middleware/session.mjs +2 -2
- package/dist/http/middleware/spa.d.mts +1 -1
- package/dist/http/middleware/spa.mjs +2 -2
- package/dist/http/middleware/upload.d.mts +1 -1
- package/dist/http/middleware/upload.mjs +2 -2
- package/dist/http/middleware/validate.mjs +1 -1
- package/dist/http/middleware/views.d.mts +1 -1
- package/dist/http/middleware/views.mjs +2 -2
- package/dist/http/query/index.mjs +2 -2
- package/dist/http/router-builder.d.mts +1 -1
- package/dist/http/router-builder.mjs +2 -2
- package/dist/http/runtimes/fastify.d.mts +1 -1
- package/dist/http/runtimes/fastify.d.mts.map +1 -1
- package/dist/http/runtimes/fastify.mjs +3 -3
- package/dist/http/runtimes/fastify.mjs.map +1 -1
- package/dist/http/runtimes/h3-web.d.mts +1 -1
- package/dist/http/runtimes/h3-web.d.mts.map +1 -1
- package/dist/http/runtimes/h3-web.mjs +2 -2
- package/dist/http/runtimes/h3-web.mjs.map +1 -1
- package/dist/http/runtimes/h3.d.mts +1 -6
- package/dist/http/runtimes/h3.d.mts.map +1 -1
- package/dist/http/runtimes/h3.mjs +2 -2
- package/dist/http/runtimes/h3.mjs.map +1 -1
- package/dist/{index-Btz1W0X5.d.mts → index-ClP0EE9p.d.mts} +2 -2
- package/dist/{index-Btz1W0X5.d.mts.map → index-ClP0EE9p.d.mts.map} +1 -1
- package/dist/index.d.mts +14 -14
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{kv-stores-Dijnf9sF.d.mts → kv-stores-B-1WZ2Fh.d.mts} +36 -6
- package/dist/kv-stores-B-1WZ2Fh.d.mts.map +1 -0
- package/dist/{kv-stores-D2MKbvcu.mjs → kv-stores-BDcvhJBw.mjs} +3 -3
- package/dist/{kv-stores-D2MKbvcu.mjs.map → kv-stores-BDcvhJBw.mjs.map} +1 -1
- package/dist/{metadata-Ceheg5tj.mjs → metadata-BClL-Eb0.mjs} +2 -2
- package/dist/{metadata-Ceheg5tj.mjs.map → metadata-BClL-Eb0.mjs.map} +1 -1
- package/dist/{module-registry-RHo93bJa.mjs → module-registry-kVXS3peW.mjs} +2 -2
- package/dist/{module-registry-RHo93bJa.mjs.map → module-registry-kVXS3peW.mjs.map} +1 -1
- package/dist/observers-C5Jid4FV.mjs +12 -0
- package/dist/observers-C5Jid4FV.mjs.map +1 -0
- package/dist/{query-CWwMw9E6.mjs → query-1Q69dPhY.mjs} +2 -2
- package/dist/{query-CWwMw9E6.mjs.map → query-1Q69dPhY.mjs.map} +1 -1
- package/dist/{rate-limit-D5FHbYKG.d.mts → rate-limit-Bwed3jOV.d.mts} +2 -2
- package/dist/{rate-limit-D5FHbYKG.d.mts.map → rate-limit-Bwed3jOV.d.mts.map} +1 -1
- package/dist/{respond-CI4CCFG4.mjs → respond-DBRvpEI5.mjs} +2 -2
- package/dist/{respond-CI4CCFG4.mjs.map → respond-DBRvpEI5.mjs.map} +1 -1
- package/dist/{route-flag-BMlwj3Nk.d.mts → route-flag-B1lQht6v.d.mts} +14 -2
- package/dist/{route-flag-BMlwj3Nk.d.mts.map → route-flag-B1lQht6v.d.mts.map} +1 -1
- package/dist/{route-flag-B7t2wGJ2.mjs → route-flag-BCBTtP_4.mjs} +3 -3
- package/dist/route-flag-BCBTtP_4.mjs.map +1 -0
- package/dist/{route-policy-MQkq3QEO.mjs → route-policy-FeJHstvF.mjs} +2 -2
- package/dist/route-policy-FeJHstvF.mjs.map +1 -0
- package/dist/{router-builder-CK7_6DLH.mjs → router-builder-Brup4e82.mjs} +3 -3
- package/dist/{router-builder-CK7_6DLH.mjs.map → router-builder-Brup4e82.mjs.map} +1 -1
- package/dist/{router-builder-DPrRk4Pv.d.mts → router-builder-CUs9Gq4X.d.mts} +4 -4
- package/dist/{router-builder-DPrRk4Pv.d.mts.map → router-builder-CUs9Gq4X.d.mts.map} +1 -1
- package/dist/{runtime-B3V_u8FS.mjs → runtime-CV0SS-X5.mjs} +3 -3
- package/dist/runtime-CV0SS-X5.mjs.map +1 -0
- package/dist/{upload-DwGoAQ6R.d.mts → upload-BukLMawc.d.mts} +3 -3
- package/dist/{upload-DwGoAQ6R.d.mts.map → upload-BukLMawc.d.mts.map} +1 -1
- package/dist/{upload-config-C49VFeoh.mjs → upload-config-BBp5QfWQ.mjs} +2 -2
- package/dist/{upload-config-C49VFeoh.mjs.map → upload-config-BBp5QfWQ.mjs.map} +1 -1
- package/dist/web.d.mts +23 -8
- package/dist/web.d.mts.map +1 -1
- package/dist/web.mjs +2 -2
- package/dist/web.mjs.map +1 -1
- package/package.json +17 -12
- package/dist/adapter-989ssCBx.d.mts.map +0 -1
- package/dist/application-BFKE8ac5.mjs +0 -12
- package/dist/application-BFKE8ac5.mjs.map +0 -1
- package/dist/application-BRvykQ6h.d.mts.map +0 -1
- package/dist/bootstrap-CPRgpW3i.mjs +0 -12
- package/dist/bootstrap-CPRgpW3i.mjs.map +0 -1
- package/dist/container-Cj2hqlfm.mjs +0 -117
- package/dist/container-Cj2hqlfm.mjs.map +0 -1
- package/dist/context-B7N5oE7S.mjs +0 -12
- package/dist/context-B7N5oE7S.mjs.map +0 -1
- package/dist/cron-DOOtTY4q.d.mts +0 -30
- package/dist/cron-DOOtTY4q.d.mts.map +0 -1
- package/dist/csrf-DPPIM72I.mjs.map +0 -1
- package/dist/handler-B4MQmJR5.mjs.map +0 -1
- package/dist/kv-stores-Dijnf9sF.d.mts.map +0 -1
- package/dist/route-flag-B7t2wGJ2.mjs.map +0 -1
- package/dist/route-policy-MQkq3QEO.mjs.map +0 -1
- package/dist/runtime-B3V_u8FS.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v8.
|
|
2
|
+
* @forinda/kickjs v8.7.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{Logger}from"../../core/logger.mjs";import"../../core-
|
|
11
|
+
import{Logger}from"../../core/logger.mjs";import"../../core-Chc61ahe.mjs";import{t as defineAdapter}from"../../define-adapter-BJunWqi0.mjs";import{existsSync,readFileSync,readdirSync}from"node:fs";import{dirname,isAbsolute,join,resolve}from"node:path";const log=Logger.for(`SpaAdapter`);function nearestPackageRoot(from){let dir=from;for(;;){if(existsSync(join(dir,`package.json`)))return dir;let parent=dirname(dir);if(parent===dir)return null;dir=parent}}function resolveClientDir(clientDir,cwd=process.cwd(),entry=process.argv[1]){if(isAbsolute(clientDir))return resolve(clientDir);let fromCwd=resolve(cwd,clientDir);if(existsSync(fromCwd))return fromCwd;if(entry){let root=nearestPackageRoot(dirname(resolve(entry)));if(root){let fromEntry=resolve(root,clientDir);if(existsSync(fromEntry))return fromEntry}}return fromCwd}function pathnameOf(url){if(!url)return`/`;let q=url.indexOf(`?`),h=url.indexOf(`#`),end=Math.min(q===-1?url.length:q,h===-1?url.length:h);return url.slice(0,end)||`/`}function isUnder(pathname,prefix){if(!prefix||prefix===`/`)return!0;let p=prefix.endsWith(`/`)?prefix.slice(0,-1):prefix;return pathname===p||pathname.startsWith(`${p}/`)}const HTML_TYPES=[`text/html`,`application/xhtml+xml`];function qualityOf(params){let q=params.find(param=>param.startsWith(`q=`));if(!q)return 1;let value=Number.parseFloat(q.slice(2));return Number.isNaN(value)?1:value}function acceptsHtml(accept){let raw=Array.isArray(accept)?accept.join(`,`):accept??``;if(!raw)return!1;let ranges=raw.split(`,`).map(part=>part.split(`;`).map(t=>t.trim().toLowerCase())).filter(([type])=>!!type);return HTML_TYPES.some(htmlType=>{let[group]=htmlType.split(`/`),exact=ranges.find(([type])=>type===htmlType),wildcard=ranges.find(([type])=>type===`${group}/*`),match=exact??wildcard;return match?qualityOf(match.slice(1))>0:!1})}const SpaAdapter=defineAdapter({name:`SpaAdapter`,defaults:{clientDir:`dist/client`,apiPrefix:`/api`,exclude:[],cacheControl:`public, max-age=31536000, immutable`,indexCacheControl:`no-cache`,alwaysFallback:!1},build:config=>{let clientDir=resolveClientDir(config.clientDir??`dist/client`),rawPrefix=config.apiPrefix??`/api`,apiPrefixes=Array.isArray(rawPrefix)?rawPrefix:[rawPrefix],excludePaths=config.exclude??[],cacheControl=config.cacheControl??`public, max-age=31536000, immutable`,indexCacheControl=config.indexCacheControl??`no-cache`,alwaysFallback=config.alwaysFallback??!1,indexHtml=null,assetPaths=new Set;function snapshotClientDir(){let paths=new Set,walk=(absDir,prefix)=>{for(let entry of readdirSync(absDir,{withFileTypes:!0})){let child=`${prefix}/${entry.name}`;entry.isDirectory()?walk(join(absDir,entry.name),child):entry.isFile()&&paths.add(child)}};try{walk(clientDir,``)}catch{return new Set}return paths}let classifyPath=pathname=>{let decoded;try{decoded=decodeURIComponent(pathname)}catch{return null}if(decoded.includes(`\0`))return null;let asIndex=decoded.endsWith(`/`)?`${decoded}index.html`:null;return asIndex&&assetPaths.has(asIndex)?`index`:assetPaths.has(decoded)?decoded.endsWith(`.html`)?`index`:`file`:null},isReserved=pathname=>apiPrefixes.some(p=>isUnder(pathname,p))||excludePaths.some(p=>isUnder(pathname,p));return{beforeMount({http}){if(!existsSync(clientDir)){log.warn(`SPA client directory not found: ${clientDir}`),log.warn(`Build your frontend first, or set clientDir to the correct path (relative paths resolve from ${process.cwd()}).`);return}let indexPath=join(clientDir,`index.html`);if(!existsSync(indexPath)){log.warn(`index.html not found in ${clientDir}`);return}indexHtml=readFileSync(indexPath,`utf-8`),assetPaths=snapshotClientDir(),cacheControl!==!1&&http.use(((req,res,next)=>{let pathname=pathnameOf(req.url),kind=isReserved(pathname)?null:classifyPath(pathname);kind&&res.setHeader(`Cache-Control`,kind===`index`?indexCacheControl:cacheControl),next()})),http.serveStatic(`/`,clientDir),log.debug(`Serving SPA from ${clientDir}`)},beforeStart({http}){indexHtml&&http.use(((req,res,next)=>{let method=(req.method??`GET`).toUpperCase();if(method!==`GET`&&method!==`HEAD`)return next();let pathname=pathnameOf(req.url);if(isReserved(pathname))return next();let accept=req.headers?.accept;if(!alwaysFallback&&!acceptsHtml(accept))return next();let body=indexHtml;res.setHeader(`Content-Type`,`text/html; charset=utf-8`),res.setHeader(`Cache-Control`,indexCacheControl),res.setHeader(`Content-Length`,String(Buffer.byteLength(body))),res.statusCode=200,res.end(method===`HEAD`?void 0:body)}))}}}});export{SpaAdapter,resolveClientDir};
|
|
12
12
|
//# sourceMappingURL=spa.mjs.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
|
|
2
|
-
import { a as RawUploadPart, c as buildFileTypeFilter, i as upload, l as resolveMimeTypes, n as buildUploadMiddleware, o as UploadedFileLike, r as cleanupFiles, s as applyUploadConfig, t as UploadOptions } from "../../upload-
|
|
2
|
+
import { a as RawUploadPart, c as buildFileTypeFilter, i as upload, l as resolveMimeTypes, n as buildUploadMiddleware, o as UploadedFileLike, r as cleanupFiles, s as applyUploadConfig, t as UploadOptions } from "../../upload-BukLMawc.mjs";
|
|
3
3
|
export { type RawUploadPart, UploadOptions, type UploadedFileLike, applyUploadConfig, buildFileTypeFilter, buildUploadMiddleware, cleanupFiles, resolveMimeTypes, upload };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v8.
|
|
2
|
+
* @forinda/kickjs v8.7.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{HttpException,HttpStatus}from"../../core/errors.mjs";import{n as buildFileTypeFilter,r as resolveMimeTypes,t as applyUploadConfig}from"../../upload-config-
|
|
11
|
+
import{HttpException,HttpStatus}from"../../core/errors.mjs";import{n as buildFileTypeFilter,r as resolveMimeTypes,t as applyUploadConfig}from"../../upload-config-BBp5QfWQ.mjs";import{createRequire}from"node:module";import{unlink}from"node:fs/promises";const requireFromHere=createRequire(import.meta.url);let _multer;function loadMulter(){if(_multer)return _multer;try{return _multer=requireFromHere(`multer`),_multer}catch{throw Error(`@forinda/kickjs: file uploads require the 'multer' package, which is not installed.
|
|
12
12
|
Install it as a peer dependency: pnpm add multer (or npm i multer / yarn add multer).`)}}function translateMulterErrors(mw,options){let maxSize=options.maxSize??5242880;return(req,res,next)=>{mw(req,res,err=>{if(err&&err.code===`LIMIT_FILE_SIZE`){let name=err.field??`file`;return next(new HttpException(HttpStatus.PAYLOAD_TOO_LARGE,`File ${name} exceeds the ${maxSize}-byte limit`))}next(err)})}}function createMulter(options){let limits={fileSize:options.maxSize??5242880},fileFilter;if(options.allowedTypes){let typeAllowed=buildFileTypeFilter(options.allowedTypes,options.customMimeMap);fileFilter=(_req,file,cb)=>{typeAllowed(file.mimetype,file.originalname)?cb(null,!0):cb(new HttpException(HttpStatus.UNSUPPORTED_MEDIA_TYPE,`File type ${file.mimetype} is not allowed`))}}let multerOptions={limits,...fileFilter?{fileFilter}:{},...options.storage?{storage:options.storage}:{},...options.dest?{dest:options.dest}:{}};return loadMulter()(multerOptions)}function single(fieldName,options={}){return translateMulterErrors(createMulter(options).single(fieldName),options)}function array(fieldName,maxCount=10,options={}){return translateMulterErrors(createMulter(options).array(fieldName,maxCount),options)}function none(options={}){return translateMulterErrors(createMulter(options).none(),options)}function cleanupFiles(){return(req,res,next)=>{res.on(`finish`,async()=>{let files=[];if(req.file?.path&&files.push(req.file),Array.isArray(req.files))for(let f of req.files)f?.path&&files.push(f);for(let file of files)try{await unlink(file.path)}catch{}}),next()}}function buildUploadMiddleware(config){let options={};config.maxSize&&(options.maxSize=config.maxSize),config.allowedTypes&&(options.allowedTypes=config.allowedTypes),config.customMimeMap&&(options.customMimeMap=config.customMimeMap);let fieldName=config.fieldName??`file`;switch(config.mode){case`single`:return single(fieldName,options);case`array`:return array(fieldName,config.maxCount??10,options);case`none`:return none(options)}}const upload={single,array,none};export{applyUploadConfig,buildFileTypeFilter,buildUploadMiddleware,cleanupFiles,resolveMimeTypes,upload};
|
|
13
13
|
//# sourceMappingURL=upload.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v8.
|
|
2
|
+
* @forinda/kickjs v8.7.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{Logger}from"../../core/logger.mjs";import"../../core-
|
|
11
|
+
import{Logger}from"../../core/logger.mjs";import"../../core-Chc61ahe.mjs";import{t as defineAdapter}from"../../define-adapter-BJunWqi0.mjs";import{resolve}from"node:path";const log=Logger.for(`ViewEngine`),ViewAdapter=defineAdapter({name:`ViewAdapter`,build:options=>({beforeMount({app}){let{engine,ext,viewsDir=`src/views`}=options;engine.__express?app.engine(ext,engine.__express):typeof engine.renderFile==`function`?app.engine(ext,(path,opts,callback)=>{engine.renderFile(path,opts,callback)}):(typeof engine==`function`||log.warn(`Engine for .${ext} does not have __express or renderFile. Trying as-is.`),app.engine(ext,engine)),app.set(`view engine`,ext),app.set(`views`,resolve(viewsDir)),log.debug(`View engine: ${ext} (${resolve(viewsDir)})`)}})});export{ViewAdapter};
|
|
12
12
|
//# sourceMappingURL=views.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v8.
|
|
2
|
+
* @forinda/kickjs v8.7.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{a as parseQuery,c as resetQueryParsingDefaults,i as parsePagination,l as setQueryParsingDefaults,n as getQueryParsingDefaults,o as parseSearchQuery,r as parseFilters,s as parseSort,t as buildQueryParams,u as FILTER_OPERATORS}from"../../query-
|
|
11
|
+
import{a as parseQuery,c as resetQueryParsingDefaults,i as parsePagination,l as setQueryParsingDefaults,n as getQueryParsingDefaults,o as parseSearchQuery,r as parseFilters,s as parseSort,t as buildQueryParams,u as FILTER_OPERATORS}from"../../query-1Q69dPhY.mjs";export{FILTER_OPERATORS,buildQueryParams,getQueryParsingDefaults,parseFilters,parsePagination,parseQuery,parseSearchQuery,parseSort,resetQueryParsingDefaults,setQueryParsingDefaults};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
|
|
2
|
-
import { i as buildRouteTable, n as _setExternalContributorSources, r as assertRouteUnique, t as BuildRoutesOptions } from "../router-builder-
|
|
2
|
+
import { i as buildRouteTable, n as _setExternalContributorSources, r as assertRouteUnique, t as BuildRoutesOptions } from "../router-builder-CUs9Gq4X.mjs";
|
|
3
3
|
export { BuildRoutesOptions, _setExternalContributorSources, assertRouteUnique, buildRouteTable };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v8.
|
|
2
|
+
* @forinda/kickjs v8.7.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{n as assertRouteUnique,r as buildRouteTable,t as _setExternalContributorSources}from"../router-builder-
|
|
11
|
+
import{n as assertRouteUnique,r as buildRouteTable,t as _setExternalContributorSources}from"../router-builder-Brup4e82.mjs";export{_setExternalContributorSources,assertRouteUnique,buildRouteTable};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.d.mts","names":[],"sources":["../../../src/http/runtimes/fastify.ts"],"mappings":";;;;UAgDU;EACR,KAAK,qBAAqB;EAC1B,KAAK,oBAAoB;EACzB,OAAO,cAAc,iBAAiB;EACtC,KAAK,sBAAsB;EAC3B;WACS;WACA,KAAK;;UAEN;EACR,KAAK;EACL;EACA;EACA;;EAEA,UAAU;;EAEV,cAAc,sBAAsB;EACpC;;KAEG,kBAAkB,SAAS,oBAAoB,OAAO,4BAA4B;UAC7E;EACR,SAAS,iBAAiB,iBAAiB,gBAAgB;EAC3D,IAAI,eAAe;EACnB,MAAM;IAAQ;IAAgB;IAAa,SAAS;;EACpD,mBAAmB,SAAS;EAC5B,gBACE,UAAU,cAAc,KAAK,oBAAoB,OAAO;EAE1D,SAAS;EACT,QAAQ,KAAK,iBAAiB,KAAK;;;;;;EAMnC,sBACE,kBAAkB,mBAClB;IAAQ;KACR,QAAQ;;;KAQP,qBACH,KAAK,oBACL,eAAe,QACf,OAAO,KAAK,cAAc;UAYlB;EACR;EACA;EACA;EACA;EACA;EACA,iBAAiB,QAAQ;;;;;;;wBA6LX,kBAAkB,YAAY;;;;;;;;
|
|
1
|
+
{"version":3,"file":"fastify.d.mts","names":[],"sources":["../../../src/http/runtimes/fastify.ts"],"mappings":";;;;UAgDU;EACR,KAAK,qBAAqB;EAC1B,KAAK,oBAAoB;EACzB,OAAO,cAAc,iBAAiB;EACtC,KAAK,sBAAsB;EAC3B;WACS;WACA,KAAK;;UAEN;EACR,KAAK;EACL;EACA;EACA;;EAEA,UAAU;;EAEV,cAAc,sBAAsB;EACpC;;KAEG,kBAAkB,SAAS,oBAAoB,OAAO,4BAA4B;UAC7E;EACR,SAAS,iBAAiB,iBAAiB,gBAAgB;EAC3D,IAAI,eAAe;EACnB,MAAM;IAAQ;IAAgB;IAAa,SAAS;;EACpD,mBAAmB,SAAS;EAC5B,gBACE,UAAU,cAAc,KAAK,oBAAoB,OAAO;EAE1D,SAAS;EACT,QAAQ,KAAK,iBAAiB,KAAK;;;;;;EAMnC,sBACE,kBAAkB,mBAClB;IAAQ;KACR,QAAQ;;;KAQP,qBACH,KAAK,oBACL,eAAe,QACf,OAAO,KAAK,cAAc;UAYlB;EACR;EACA;EACA;EACA;EACA;EACA,iBAAiB,QAAQ;;;;;;;wBA6LX,kBAAkB,YAAY;;;;;;;;iBA8M7B;EACf,2BAA2B;EAC3B,4BAA4B;EAC5B,uBAAuB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v8.
|
|
2
|
+
* @forinda/kickjs v8.7.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{HttpException}from"../../core/errors.mjs";import{a as createRequestStore,f as unsupportedMediaTypeError,n as applyHandlerResult,o as disposeRequestStore,t as publishMatchedRoute}from"../../runtime-
|
|
12
|
-
Install it as a peer dependency: pnpm add @fastify/multipart.`);let rawParts=[],fields={};for await(let part of request.parts())part.type===`file`&&part.toBuffer?rawParts.push({fieldname:part.fieldname,filename:part.filename??``,mimetype:part.mimetype??`application/octet-stream`,buffer:await part.toBuffer()}):fields[part.fieldname]=part.value;let{file,files}=applyUploadConfig(rawParts,entry.meta.upload);raw.file=file,raw.files=files,raw.body={...raw.body,...fields}}let headerId=raw.headers[`x-request-id`],requestId=(Array.isArray(headerId)?headerId[0]:headerId)||raw.requestId,store=createRequestStore(requestId);raw.requestId=store.requestId,reply.raw.once(`close`,()=>disposeRequestStore(store)),await requestStore.run(store,async()=>{publishMatchedRoute(raw,entry);let ctx=new RequestContext(raw,reply,NOOP_NEXT,replyDriver(reply));if(entry.earlyContributorRunner&&(await entry.earlyContributorRunner(ctx),reply.sent))return;validator&&await new Promise((resolve,reject)=>{validator(raw,void 0,err=>err?reject(err):resolve())});for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||reply.sent)return}if(entry.contributorRunner&&await entry.contributorRunner(ctx),reply.sent)return;let result=await entry.handler(ctx);reply.sent||applyHandlerResult(ctx,result)})}}function fastifyRuntime(){return{name:`fastify`,createApp(options={}){let app=loadPeer(`fastify`)({logger:!1,routerOptions:{ignoreTrailingSlash:!0},trustProxy:typeof options.trustProxy!=`function`&&options.trustProxy});app[QUEUE]=[];let addParser=app.addContentTypeParser?.bind(app);return addParser&&(addParser(/^application\/[\w.+-]+\+json$/,{parseAs:`string`},(_req,body,done)=>{if(body===``)return done(null,void 0);try{done(null,JSON.parse(body))}catch(err){done(HttpException.badRequest(err instanceof Error?err.message:`Request body could not be parsed`),void 0)}}),addParser(`application/x-www-form-urlencoded`,{parseAs:`string`},(_req,body,done)=>{done(null,Object.fromEntries(new URLSearchParams(body)))}),addParser(/^text\//,{parseAs:`string`},(_req,body,done)=>{done(null,body)}),addParser(`*`,{parseAs:`string`},(req,body,done)=>{if(!body)return done(null,void 0);done(unsupportedMediaTypeError(req.headers?.[`content-type`]),void 0)})),app},nodeHandler(app){let state=app;state[READY]||(state[READY]=(async()=>{if(state[NEEDS_MULTIPART]){let multipart=loadPeer(`@fastify/multipart`);await app.register(multipart,{limits:{fileSize:2**53-1}})}let middie=loadPeer(`@fastify/middie`);await app.register(middie);for(let{mw,opts}of state[QUEUE]??[])opts?.path===void 0?app.use(mw):app.use(opts.path,mw);await app.ready()})());let readyP=state[READY];return(req,res,next)=>{next&&(req[NEXT]=next),readyP.then(()=>app.routing(req,res)).catch(err=>{next&&next(err)})}},mountRoutes(app,table){for(let{mountPath,routes}of table)for(let entry of routes){entry.meta.upload&&(app[NEEDS_MULTIPART]=!0);let url=joinPath(mountPath,entry.path);app.route({method:entry.method,url,handler:routeHandler(entry)})}},useConnect(app,mw,opts){app[QUEUE]?.push({mw,opts})},serveStatic(app,prefix,dir){let serveStatic=loadPeer(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app.setNotFoundHandler((request,reply)=>{let next=request.raw[NEXT];if(next){reply.hijack(),next();return}mw(request.raw,replyDriver(reply),NOOP_NEXT)})},setErrorHandler(app,mw){app.setErrorHandler((err,request,reply)=>{mw(err,request.raw,replyDriver(reply),NOOP_NEXT)})},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{fastifyRuntime};
|
|
11
|
+
import{HttpException}from"../../core/errors.mjs";import{a as createRequestStore,f as unsupportedMediaTypeError,n as applyHandlerResult,o as disposeRequestStore,t as publishMatchedRoute}from"../../runtime-CV0SS-X5.mjs";import{c as requestStore,t as RequestContext}from"../../context-CHuuc9r6.mjs";import{validate}from"../middleware/validate.mjs";import{t as applyUploadConfig}from"../../upload-config-BBp5QfWQ.mjs";import"../middleware/upload.mjs";import{createRequire}from"node:module";const peerRequire=createRequire(import.meta.url),loadPeer=name=>{let mod=peerRequire(name);return mod&&mod.__esModule&&mod.default?mod.default:mod.default??mod},QUEUE=Symbol(`kickjs.fastify.connectQueue`),READY=Symbol(`kickjs.fastify.ready`),NEXT=Symbol(`kickjs.fastify.next`),NEEDS_MULTIPART=Symbol(`kickjs.fastify.needsMultipart`),NOOP_NEXT=()=>{};var FastifyReplyDriver=class{reply;constructor(reply){this.reply=reply}status(code){return this.reply.code(code),this}json(data){return this.reply.send(data),this}send(data){return this.reply.send(data),this}type(contentType){return this.reply.type(contentType),this}setHeader(name,value){return this.reply.header(name,value),this}render(){throw Error(`ctx.render() is not supported on the Fastify runtime (no view engine).`)}writeHead(statusCode,headers){return this.reply.hijack(),this.reply.raw.writeHead(statusCode,headers),this}flushHeaders(){return this.reply.raw.flushHeaders?.(),this}write(chunk){return this.reply.raw.write(chunk)}end(data){return this.reply.raw.end(data),this}once(event,listener){return this.reply.raw.once(event,listener),this}get headersSent(){return this.reply.sent||this.reply.raw.headersSent}};function replyDriver(reply){return new FastifyReplyDriver(reply)}function routeHandler(entry){let validator=entry.meta.validation?validate(entry.meta.validation):void 0;return async(request,reply)=>{let raw=request.raw;if(raw.body=request.body,raw.params=request.params,raw.query=request.query,entry.meta.upload&&entry.meta.upload.mode!==`none`){if(typeof request.parts!=`function`)throw Error(`@forinda/kickjs: @FileUpload on the Fastify runtime requires '@fastify/multipart'.
|
|
12
|
+
Install it as a peer dependency: pnpm add @fastify/multipart.`);let rawParts=[],fields={};for await(let part of request.parts())part.type===`file`&&part.toBuffer?rawParts.push({fieldname:part.fieldname,filename:part.filename??``,mimetype:part.mimetype??`application/octet-stream`,buffer:await part.toBuffer()}):fields[part.fieldname]=part.value;let{file,files}=applyUploadConfig(rawParts,entry.meta.upload);raw.file=file,raw.files=files,raw.body={...raw.body,...fields}}let headerId=raw.headers[`x-request-id`],requestId=(Array.isArray(headerId)?headerId[0]:headerId)||raw.requestId,store=createRequestStore(requestId);raw.requestId=store.requestId,reply.raw.once(`close`,()=>disposeRequestStore(store)),await requestStore.run(store,async()=>{publishMatchedRoute(raw,entry);let ctx=new RequestContext(raw,reply,NOOP_NEXT,replyDriver(reply));if(entry.earlyContributorRunner&&(await entry.earlyContributorRunner(ctx),reply.sent))return;validator&&await new Promise((resolve,reject)=>{validator(raw,void 0,err=>err?reject(err):resolve())});for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||reply.sent)return}if(entry.contributorRunner&&await entry.contributorRunner(ctx),reply.sent)return;let result=await entry.handler(ctx);reply.sent||applyHandlerResult(ctx,result)})}}function fastifyRuntime(){return{name:`fastify`,createApp(options={}){let app=loadPeer(`fastify`)({logger:!1,routerOptions:{ignoreTrailingSlash:!0},trustProxy:typeof options.trustProxy!=`function`&&options.trustProxy});app[QUEUE]=[];let addParser=app.addContentTypeParser?.bind(app);return addParser&&(addParser(/^application\/[\w.+-]+\+json$/,{parseAs:`string`},(_req,body,done)=>{if(body===``)return done(null,void 0);try{done(null,JSON.parse(body))}catch(err){done(HttpException.badRequest(err instanceof Error?err.message:`Request body could not be parsed`),void 0)}}),addParser(`application/x-www-form-urlencoded`,{parseAs:`string`},(_req,body,done)=>{done(null,Object.fromEntries(new URLSearchParams(body)))}),addParser(/^text\//,{parseAs:`string`},(_req,body,done)=>{done(null,body)}),addParser(`*`,{parseAs:`string`},(req,body,done)=>{if(!body)return done(null,void 0);done(unsupportedMediaTypeError(req.headers?.[`content-type`]),void 0)})),app},nodeHandler(app){let state=app;state[READY]||(state[READY]=(async()=>{if(state[NEEDS_MULTIPART]){let multipart=loadPeer(`@fastify/multipart`);await app.register(multipart,{limits:{fileSize:2**53-1}})}let middie=loadPeer(`@fastify/middie`);await app.register(middie);for(let{mw,opts}of state[QUEUE]??[])opts?.path===void 0?app.use(mw):app.use(opts.path,mw);await app.ready()})());let readyP=state[READY];return(req,res,next)=>{next&&(req[NEXT]=next),readyP.then(()=>app.routing(req,res)).catch(err=>{next&&next(err)})}},mountRoutes(app,table){for(let{mountPath,routes}of table)for(let entry of routes){entry.meta.upload&&(app[NEEDS_MULTIPART]=!0);let url=joinPath(mountPath,entry.path);app.route({method:entry.method,url,handler:routeHandler(entry)})}},useConnect(app,mw,opts){app[QUEUE]?.push({mw,opts})},serveStatic(app,prefix,dir){let serveStatic=loadPeer(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app.setNotFoundHandler((request,reply)=>{let next=request.raw[NEXT];if(next){reply.hijack(),next();return}mw(request.raw,replyDriver(reply),NOOP_NEXT)})},setErrorHandler(app,mw){app.setErrorHandler((err,request,reply)=>{mw(err,request.raw,replyDriver(reply),NOOP_NEXT)})},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0,http2:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{fastifyRuntime};
|
|
13
13
|
//# sourceMappingURL=fastify.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.mjs","names":[],"sources":["../../../src/http/runtimes/fastify.ts"],"sourcesContent":["// The Fastify runtime — `@forinda/kickjs/fastify`.\n//\n// Implements the same HttpRuntime contract as expressRuntime, over Fastify 5.\n// KickJS still owns decorators → RouteTable, the contributor pipeline, and the\n// RequestContext surface; this runtime materializes routes as NATIVE Fastify\n// routes and wraps `reply` in a RuntimeResponse so `ctx.json` / `ctx.html` /\n// `ctx.sse` work unchanged.\n//\n// `fastify`, `@fastify/middie` are optional peers — install them alongside\n// `@forinda/kickjs` to use this subpath.\n//\n// Lifecycle note: Fastify registration (`register` / `use` / `route`) is\n// deferred until `ready()`. Connect middleware needs `@fastify/middie`\n// registered first, so `useConnect` only QUEUES middleware; `nodeHandler`\n// registers middie, then the queued middleware, then awaits `ready()`.\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createRequire } from 'node:module'\n\nimport { HttpException } from '../../core/errors'\nimport { unsupportedMediaTypeError } from '../body-policy'\nimport { RequestContext } from '../context'\nimport { publishMatchedRoute } from '../runtime'\nimport { applyHandlerResult } from '../reply'\nimport { requestStore } from '../request-store'\nimport { createRequestStore, disposeRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { applyUploadConfig, type RawUploadPart } from '../middleware/upload'\nimport type {\n ConnectMiddleware,\n HttpRuntime,\n RouteEntry,\n RouteTable,\n RuntimeAppOptions,\n RuntimeResponse,\n UseConnectOptions,\n} from '../runtime'\n\n// ESM-safe require for the optional peers (fastify / @fastify/middie /\n// serve-static), resolved from wherever this module is installed.\nconst peerRequire = createRequire(import.meta.url)\nconst loadPeer = (name: string): any => {\n const mod = peerRequire(name)\n return mod && mod.__esModule && mod.default ? mod.default : (mod.default ?? mod)\n}\n\n// Minimal structural surface we use from Fastify — avoids a hard type dep so\n// the root package never needs `fastify` installed to typecheck.\ninterface FastifyReplyLike {\n code(statusCode: number): FastifyReplyLike\n send(payload?: unknown): FastifyReplyLike\n header(name: string, value: unknown): FastifyReplyLike\n type(contentType: string): FastifyReplyLike\n hijack(): void\n readonly sent: boolean\n readonly raw: ServerResponse\n}\ninterface FastifyRequestLike {\n raw: IncomingMessage\n body?: unknown\n params?: unknown\n query?: unknown\n /** Request headers, as Fastify normalises them (lowercased keys). */\n headers?: Record<string, string | string[] | undefined>\n /** Present once @fastify/multipart is registered — async iterator of parts. */\n parts?: () => AsyncIterableIterator<MultipartPart>\n isMultipart?: () => boolean\n}\ntype FastifyHandler = (request: FastifyRequestLike, reply: FastifyReplyLike) => void | Promise<void>\ninterface FastifyAppLike {\n register(plugin: unknown, opts?: unknown): Promise<void> | FastifyAppLike\n use(path: unknown, mw?: unknown): unknown\n route(opts: { method: string; url: string; handler: FastifyHandler }): unknown\n setNotFoundHandler(handler: FastifyHandler): unknown\n setErrorHandler(\n handler: (err: unknown, req: FastifyRequestLike, reply: FastifyReplyLike) => void,\n ): unknown\n ready(): Promise<void>\n routing(req: IncomingMessage, res: ServerResponse): void\n /**\n * Fastify's content-type parser registry. Typed here rather than reached for\n * with a cast so the callback shape is checked at the call site — these\n * parsers decide what `ctx.body` is on this runtime.\n */\n addContentTypeParser?(\n matcher: string | RegExp | string[],\n opts: { parseAs: 'string' | 'buffer' },\n parser: FastifyBodyParser,\n ): unknown\n}\n\n/** The `done` callback a Fastify content-type parser calls. */\ntype FastifyParserDone = (err: Error | null, body?: unknown) => void\n\n/** `done`-style callback Fastify hands a content-type parser. */\ntype FastifyBodyParser = (\n req: FastifyRequestLike,\n body: string | Buffer,\n done: (err: Error | null, body?: unknown) => void,\n) => void\n\nconst QUEUE = Symbol('kickjs.fastify.connectQueue')\nconst READY = Symbol('kickjs.fastify.ready')\nconst NEXT = Symbol('kickjs.fastify.next')\n// Set during mountRoutes when any route carries @FileUpload metadata, so\n// nodeHandler knows to register @fastify/multipart before ready().\nconst NEEDS_MULTIPART = Symbol('kickjs.fastify.needsMultipart')\nconst NOOP_NEXT = (): void => {}\n\n// A @fastify/multipart part as we consume it from `request.parts()`.\ninterface MultipartPart {\n type: 'file' | 'field'\n fieldname: string\n filename?: string\n mimetype?: string\n value?: unknown\n toBuffer?: () => Promise<Buffer>\n}\n\n/**\n * Wrap a Fastify reply so the RequestContext response helpers drive it.\n * A class so the ~12 driver methods live on a shared prototype — the previous\n * object-literal version re-allocated every method closure on each request.\n */\nclass FastifyReplyDriver implements RuntimeResponse {\n constructor(private readonly reply: FastifyReplyLike) {}\n status(code: number): this {\n this.reply.code(code)\n return this\n }\n json(data: unknown): this {\n this.reply.send(data)\n return this\n }\n send(data: unknown): this {\n this.reply.send(data)\n return this\n }\n type(contentType: string): this {\n this.reply.type(contentType)\n return this\n }\n setHeader(name: string, value: unknown): this {\n this.reply.header(name, value)\n return this\n }\n render(): never {\n throw new Error('ctx.render() is not supported on the Fastify runtime (no view engine).')\n }\n writeHead(statusCode: number, headers?: Record<string, string | number | string[]>): this {\n // Streaming / SSE: take over the raw socket so Fastify doesn't try to\n // serialize a reply we're writing by hand.\n this.reply.hijack()\n this.reply.raw.writeHead(statusCode, headers)\n return this\n }\n flushHeaders(): this {\n ;(this.reply.raw as ServerResponse & { flushHeaders?: () => void }).flushHeaders?.()\n return this\n }\n write(chunk: unknown): boolean {\n return this.reply.raw.write(chunk as never)\n }\n end(data?: unknown): this {\n this.reply.raw.end(data as never)\n return this\n }\n once(event: string, listener: (...args: unknown[]) => void): this {\n this.reply.raw.once(event, listener)\n return this\n }\n get headersSent(): boolean {\n return this.reply.sent || this.reply.raw.headersSent\n }\n}\n\nfunction replyDriver(reply: FastifyReplyLike): RuntimeResponse {\n return new FastifyReplyDriver(reply) as unknown as RuntimeResponse\n}\n\n/** Build the Fastify per-route handler that runs the kickjs pipeline. */\nfunction routeHandler(entry: RouteEntry): FastifyHandler {\n // Validation middleware is built ONCE per route (parity with the Express\n // materializer) — `validate()` constructs a fresh closure, so calling it\n // per request was pure allocation waste.\n const validator = entry.meta.validation ? validate(entry.meta.validation) : undefined\n return async (request, reply) => {\n // Fastify runs the route handler OUTSIDE the connect-middleware chain, so\n // (unlike Express) the requestScopeMiddleware ALS frame isn't active here.\n // Establish it around the pipeline so REQUEST-scoped DI, contributors, and\n // ctx.set/get work. Reuse the inbound x-request-id, or the id a connect\n // middleware (requestId / requestScope, run earlier via middie) already\n // stamped on the raw request, before generating a fresh one.\n // Use the raw node request as `ctx.req`: it natively provides the stream\n // events (`on('close')` / `once`) that SSE and `ctx.signal` need — Fastify's\n // request object doesn't. Copy Fastify's parsed body/params/query onto it so\n // `ctx.body` / `ctx.params` / `ctx.query` keep working.\n const raw = request.raw as IncomingMessage & {\n requestId?: string\n body?: unknown\n params?: unknown\n query?: unknown\n file?: unknown\n files?: unknown\n }\n raw.body = request.body\n raw.params = request.params\n raw.query = request.query\n\n // @FileUpload: buffer multipart parts into the multer-shaped file(s) that\n // `ctx.file` / `ctx.files` expose. Fastify doesn't parse multipart natively\n // — @fastify/multipart (registered in nodeHandler when any route needs it)\n // provides `request.parts()`. Non-file fields are merged onto `raw.body` so\n // validation / `ctx.body` see them, matching multer's req.body behavior.\n if (entry.meta.upload && entry.meta.upload.mode !== 'none') {\n if (typeof request.parts !== 'function') {\n throw new Error(\n \"@forinda/kickjs: @FileUpload on the Fastify runtime requires '@fastify/multipart'.\\n\" +\n 'Install it as a peer dependency: pnpm add @fastify/multipart.',\n )\n }\n const rawParts: RawUploadPart[] = []\n const fields: Record<string, unknown> = {}\n for await (const part of request.parts()) {\n if (part.type === 'file' && part.toBuffer) {\n rawParts.push({\n fieldname: part.fieldname,\n filename: part.filename ?? '',\n mimetype: part.mimetype ?? 'application/octet-stream',\n buffer: await part.toBuffer(),\n })\n } else {\n fields[part.fieldname] = part.value\n }\n }\n const { file, files } = applyUploadConfig(rawParts, entry.meta.upload)\n raw.file = file\n raw.files = files\n raw.body = { ...(raw.body as Record<string, unknown> | undefined), ...fields }\n }\n\n const headerId = raw.headers['x-request-id']\n const requestId = (Array.isArray(headerId) ? headerId[0] : headerId) || raw.requestId\n const store = createRequestStore(requestId)\n // Propagate the resolved id back onto the raw request so anything reading\n // `req.requestId` inside the frame (e.g. request-logger) matches the store\n // and the X-Request-Id response header — parity with Express.\n raw.requestId = store.requestId\n\n // @PreDestroy teardown for REQUEST-scoped instances once the response\n // closes (finished or aborted) — parity with requestScopeMiddleware.\n reply.raw.once('close', () => disposeRequestStore(store))\n\n await requestStore.run(store, async () => {\n publishMatchedRoute(raw, entry)\n const ctx = new RequestContext(raw as never, reply as never, NOOP_NEXT, replyDriver(reply))\n\n // `beforeValidation` contributors (#677) — ahead of validation and middleware.\n if (entry.earlyContributorRunner) {\n await entry.earlyContributorRunner(ctx)\n if (reply.sent) return\n }\n\n // Validation (from @Get(path, schema) / route.validation). `validator` is a\n // connect-style middleware built once at route registration; it mutates\n // req.body/query/params to the parsed value and calls next(err) on failure\n // — it never touches `res`, so it runs cleanly here. A rejection\n // propagates to the error handler (422).\n if (validator) {\n await new Promise<void>((resolve, reject) => {\n validator(raw as never, undefined as never, (err?: unknown) =>\n err ? reject(err) : resolve(),\n )\n })\n }\n\n // Class + method middleware — `(ctx, next)`; await each, stop if it ended\n // the response or called next(err).\n for (const mw of entry.middlewares) {\n let advanced = false\n await new Promise<void>((resolve, reject) => {\n const next = (err?: unknown): void => {\n advanced = true\n if (err) reject(err)\n else resolve()\n }\n Promise.resolve(mw(ctx, next)).catch(reject)\n })\n if (!advanced || reply.sent) return\n }\n\n if (entry.contributorRunner) await entry.contributorRunner(ctx)\n if (reply.sent) return\n const result = await entry.handler(ctx)\n // Return-value handlers (reply.ts): auto-send when nothing was written.\n if (!reply.sent) applyHandlerResult(ctx, result)\n })\n }\n}\n\n/**\n * The Fastify HTTP runtime. Pass to `bootstrap({ runtime: fastifyRuntime() })`.\n * Per spec §10: Fastify's built-in pino logger is disabled (`logger: false`) so\n * the kickjs `requestLogger` middleware stays the single log format.\n */\nexport function fastifyRuntime(): HttpRuntime<FastifyAppLike> {\n return {\n name: 'fastify',\n\n createApp(options: RuntimeAppOptions = {}): FastifyAppLike {\n const Fastify = loadPeer('fastify')\n const app = Fastify({\n logger: false,\n // Match Express / h3 routing leniency: `/api/v1/hello` and\n // `/api/v1/hello/` resolve to the same route. Without this, Fastify's\n // strict router 404s a trailing-slash request — e.g. a controller's\n // root `@Get('/')` (mounted at the prefix) misses `${prefix}/`. Set under\n // `routerOptions` (Fastify 5+; top-level `ignoreTrailingSlash` is\n // deprecated, removed in Fastify 6).\n routerOptions: { ignoreTrailingSlash: true },\n // trustProxy passed through for now; core X-Forwarded-For\n // normalization (spec §10 Q1) is a follow-up.\n trustProxy: typeof options.trustProxy === 'function' ? false : options.trustProxy,\n }) as FastifyAppLike & {\n [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }>\n }\n app[QUEUE] = []\n\n // Fastify ships parsers for `application/json` and `text/plain` only, and\n // answers 415 for everything else. That made a form post work on Express\n // and h3 but fail on Fastify alone (#590). Register the rest of the\n // framework's supported set so all engines agree.\n //\n // Deliberately NOT a `'*'` catch-all: that would swallow multipart, and\n // `@fastify/multipart` is itself the multipart content-type parser,\n // registered conditionally in `nodeHandler` when a route needs it.\n const addParser = app.addContentTypeParser?.bind(app)\n if (addParser) {\n // `application/*+json` — RFC 6838 §4.2.8 guarantees the bytes are JSON.\n addParser(\n /^application\\/[\\w.+-]+\\+json$/,\n { parseAs: 'string' },\n (_req: FastifyRequestLike, body: string | Buffer, done: FastifyParserDone) => {\n if (body === '') return done(null, undefined)\n try {\n done(null, JSON.parse(body as string))\n } catch (err) {\n // A bare SyntaxError carries no status, so the shared error handler\n // would call it a 500 — Fastify's own JSON parser answers 400\n // (FST_ERR_CTP_INVALID_JSON_BODY), and so must this one.\n done(\n HttpException.badRequest(\n err instanceof Error ? err.message : 'Request body could not be parsed',\n ),\n undefined,\n )\n }\n },\n )\n addParser(\n 'application/x-www-form-urlencoded',\n { parseAs: 'string' },\n (_req: FastifyRequestLike, body: string | Buffer, done: FastifyParserDone) => {\n done(null, Object.fromEntries(new URLSearchParams(body as string)))\n },\n )\n // `text/*` beyond Fastify's built-in `text/plain`. Raw string, never\n // JSON: `text/plain` is CORS-safelisted, so parsing it as JSON would\n // re-open no-preflight CSRF against a JSON API.\n addParser(\n /^text\\//,\n { parseAs: 'string' },\n (_req: FastifyRequestLike, body: string | Buffer, done: FastifyParserDone) => {\n done(null, body)\n },\n )\n // Fastify already answers 415 for a type it has no parser for, but with\n // its own message and no `Accept`. `'*'` is Fastify's documented\n // fallback — consulted only when nothing more specific matches — so it\n // does not shadow the parsers above, nor `@fastify/multipart`, which\n // registers `multipart/form-data` when an upload route needs it.\n addParser(\n '*',\n { parseAs: 'string' },\n (req: FastifyRequestLike, body: string | Buffer, done: FastifyParserDone) => {\n // No body is not an unsupported body. Fastify calls the parser even\n // for an empty payload, so without this a bodyless POST carrying an\n // unrelated content type would be rejected.\n if (!body) return done(null, undefined)\n done(unsupportedMediaTypeError(req.headers?.['content-type']), undefined)\n },\n )\n }\n\n return app\n },\n\n nodeHandler(app) {\n // Register middie + queued connect middleware + finalize `ready()` exactly\n // ONCE per app, even though Application may call nodeHandler per request.\n const state = app as {\n [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }>\n [READY]?: Promise<void>\n [NEEDS_MULTIPART]?: boolean\n }\n if (!state[READY]) {\n state[READY] = (async () => {\n // Register @fastify/multipart first when any route uses @FileUpload so\n // `request.parts()` is available. Our applyUploadConfig enforces the\n // real per-file maxSize, so give the plugin a generous fileSize ceiling\n // (its 1MB default would silently truncate larger uploads first).\n if (state[NEEDS_MULTIPART]) {\n const multipart = loadPeer('@fastify/multipart')\n await app.register(multipart, { limits: { fileSize: Number.MAX_SAFE_INTEGER } })\n }\n const middie = loadPeer('@fastify/middie')\n await app.register(middie)\n for (const { mw, opts } of state[QUEUE] ?? []) {\n if (opts?.path !== undefined)\n (app.use as (p: unknown, m: unknown) => void)(opts.path, mw)\n else (app.use as (m: unknown) => void)(mw)\n }\n await app.ready()\n })()\n }\n const readyP = state[READY]\n\n return (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => {\n if (next) (req as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT] = next\n readyP\n .then(() => app.routing(req, res))\n .catch((err) => {\n if (next) next(err)\n })\n }\n },\n\n mountRoutes(app, table: RouteTable) {\n for (const { mountPath, routes } of table) {\n for (const entry of routes) {\n if (entry.meta.upload) (app as { [NEEDS_MULTIPART]?: boolean })[NEEDS_MULTIPART] = true\n const url = joinPath(mountPath, entry.path)\n app.route({ method: entry.method, url, handler: routeHandler(entry) })\n }\n }\n },\n\n useConnect(app, mw: ConnectMiddleware, opts?: UseConnectOptions) {\n const queue = (\n app as { [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }> }\n )[QUEUE]\n queue?.push({ mw, opts })\n },\n\n serveStatic(app, prefix, dir) {\n // Serve via `serve-static` (the standalone connect middleware behind\n // express.static) so the Fastify runtime carries no `express` dependency —\n // queued like any other connect middleware, bridged through middie.\n const serveStatic = loadPeer('serve-static')\n this.useConnect(app, serveStatic(dir) as ConnectMiddleware, { path: prefix })\n },\n\n setNotFound(app, mw: ConnectMiddleware) {\n app.setNotFoundHandler((request, reply) => {\n const next = (request.raw as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT]\n if (next) {\n // Vite dev fall-through: hand the request back to the outer chain.\n reply.hijack()\n next()\n return\n }\n // Pass the reply DRIVER (not raw res) so the connect-style\n // notFoundHandler's `res.status().json()` lands on the Fastify reply.\n ;(mw as (req: unknown, res: unknown, next: () => void) => void)(\n request.raw,\n replyDriver(reply),\n NOOP_NEXT,\n )\n })\n },\n\n setErrorHandler(app, mw: ConnectMiddleware) {\n app.setErrorHandler((err, request, reply) => {\n // Reply driver (not raw res) so the connect-style errorHandler's\n // `res.status().json()` / `setHeader` land on the Fastify reply.\n ;(mw as (e: unknown, req: unknown, res: unknown, next: () => void) => void)(\n err,\n request.raw,\n replyDriver(reply),\n NOOP_NEXT,\n )\n })\n },\n\n capabilities: {\n render: false,\n uploads: true,\n connectMiddleware: true,\n nativeBodyParsing: true,\n },\n }\n}\n\n/**\n * The Fastify runtime's engine-native types — what the runtime-typed escape\n * hatches resolve to once the `kick/runtime` typegen emits a `KickRuntimeRegister`\n * augmentation pointing here (spec §4.3b). `AdapterContext.app` / `getRuntimeApp()`\n * become `FastifyInstance`; `ctx.req` / `ctx.res` the Fastify request / reply.\n * Mirrors {@link import('../runtime').ExpressRuntimeTypes} for the default engine.\n */\nexport interface FastifyRuntimeTypes {\n request: import('fastify').FastifyRequest\n response: import('fastify').FastifyReply\n app: import('fastify').FastifyInstance\n}\n\n/** Join a mount prefix and a route path into one URL, collapsing slashes. */\nfunction joinPath(mountPath: string, path: string): string {\n const a = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n // A root route path ('/' or '') maps to the mount prefix itself — otherwise a\n // controller `@Post('/')` would register at `${prefix}/`, and a request to\n // `${prefix}` (no trailing slash) wouldn't match under Fastify's strict router.\n if (path === '/' || path === '') return a === '' ? '/' : a\n const b = path.startsWith('/') ? path : `/${path}`\n return `${a}${b}`\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fastify.mjs","names":[],"sources":["../../../src/http/runtimes/fastify.ts"],"sourcesContent":["// The Fastify runtime — `@forinda/kickjs/fastify`.\n//\n// Implements the same HttpRuntime contract as expressRuntime, over Fastify 5.\n// KickJS still owns decorators → RouteTable, the contributor pipeline, and the\n// RequestContext surface; this runtime materializes routes as NATIVE Fastify\n// routes and wraps `reply` in a RuntimeResponse so `ctx.json` / `ctx.html` /\n// `ctx.sse` work unchanged.\n//\n// `fastify`, `@fastify/middie` are optional peers — install them alongside\n// `@forinda/kickjs` to use this subpath.\n//\n// Lifecycle note: Fastify registration (`register` / `use` / `route`) is\n// deferred until `ready()`. Connect middleware needs `@fastify/middie`\n// registered first, so `useConnect` only QUEUES middleware; `nodeHandler`\n// registers middie, then the queued middleware, then awaits `ready()`.\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createRequire } from 'node:module'\n\nimport { HttpException } from '../../core/errors'\nimport { unsupportedMediaTypeError } from '../body-policy'\nimport { RequestContext } from '../context'\nimport { publishMatchedRoute } from '../runtime'\nimport { applyHandlerResult } from '../reply'\nimport { requestStore } from '../request-store'\nimport { createRequestStore, disposeRequestStore } from '../middleware/request-scope'\nimport { validate } from '../middleware/validate'\nimport { applyUploadConfig, type RawUploadPart } from '../middleware/upload'\nimport type {\n ConnectMiddleware,\n HttpRuntime,\n RouteEntry,\n RouteTable,\n RuntimeAppOptions,\n RuntimeResponse,\n UseConnectOptions,\n} from '../runtime'\n\n// ESM-safe require for the optional peers (fastify / @fastify/middie /\n// serve-static), resolved from wherever this module is installed.\nconst peerRequire = createRequire(import.meta.url)\nconst loadPeer = (name: string): any => {\n const mod = peerRequire(name)\n return mod && mod.__esModule && mod.default ? mod.default : (mod.default ?? mod)\n}\n\n// Minimal structural surface we use from Fastify — avoids a hard type dep so\n// the root package never needs `fastify` installed to typecheck.\ninterface FastifyReplyLike {\n code(statusCode: number): FastifyReplyLike\n send(payload?: unknown): FastifyReplyLike\n header(name: string, value: unknown): FastifyReplyLike\n type(contentType: string): FastifyReplyLike\n hijack(): void\n readonly sent: boolean\n readonly raw: ServerResponse\n}\ninterface FastifyRequestLike {\n raw: IncomingMessage\n body?: unknown\n params?: unknown\n query?: unknown\n /** Request headers, as Fastify normalises them (lowercased keys). */\n headers?: Record<string, string | string[] | undefined>\n /** Present once @fastify/multipart is registered — async iterator of parts. */\n parts?: () => AsyncIterableIterator<MultipartPart>\n isMultipart?: () => boolean\n}\ntype FastifyHandler = (request: FastifyRequestLike, reply: FastifyReplyLike) => void | Promise<void>\ninterface FastifyAppLike {\n register(plugin: unknown, opts?: unknown): Promise<void> | FastifyAppLike\n use(path: unknown, mw?: unknown): unknown\n route(opts: { method: string; url: string; handler: FastifyHandler }): unknown\n setNotFoundHandler(handler: FastifyHandler): unknown\n setErrorHandler(\n handler: (err: unknown, req: FastifyRequestLike, reply: FastifyReplyLike) => void,\n ): unknown\n ready(): Promise<void>\n routing(req: IncomingMessage, res: ServerResponse): void\n /**\n * Fastify's content-type parser registry. Typed here rather than reached for\n * with a cast so the callback shape is checked at the call site — these\n * parsers decide what `ctx.body` is on this runtime.\n */\n addContentTypeParser?(\n matcher: string | RegExp | string[],\n opts: { parseAs: 'string' | 'buffer' },\n parser: FastifyBodyParser,\n ): unknown\n}\n\n/** The `done` callback a Fastify content-type parser calls. */\ntype FastifyParserDone = (err: Error | null, body?: unknown) => void\n\n/** `done`-style callback Fastify hands a content-type parser. */\ntype FastifyBodyParser = (\n req: FastifyRequestLike,\n body: string | Buffer,\n done: (err: Error | null, body?: unknown) => void,\n) => void\n\nconst QUEUE = Symbol('kickjs.fastify.connectQueue')\nconst READY = Symbol('kickjs.fastify.ready')\nconst NEXT = Symbol('kickjs.fastify.next')\n// Set during mountRoutes when any route carries @FileUpload metadata, so\n// nodeHandler knows to register @fastify/multipart before ready().\nconst NEEDS_MULTIPART = Symbol('kickjs.fastify.needsMultipart')\nconst NOOP_NEXT = (): void => {}\n\n// A @fastify/multipart part as we consume it from `request.parts()`.\ninterface MultipartPart {\n type: 'file' | 'field'\n fieldname: string\n filename?: string\n mimetype?: string\n value?: unknown\n toBuffer?: () => Promise<Buffer>\n}\n\n/**\n * Wrap a Fastify reply so the RequestContext response helpers drive it.\n * A class so the ~12 driver methods live on a shared prototype — the previous\n * object-literal version re-allocated every method closure on each request.\n */\nclass FastifyReplyDriver implements RuntimeResponse {\n constructor(private readonly reply: FastifyReplyLike) {}\n status(code: number): this {\n this.reply.code(code)\n return this\n }\n json(data: unknown): this {\n this.reply.send(data)\n return this\n }\n send(data: unknown): this {\n this.reply.send(data)\n return this\n }\n type(contentType: string): this {\n this.reply.type(contentType)\n return this\n }\n setHeader(name: string, value: unknown): this {\n this.reply.header(name, value)\n return this\n }\n render(): never {\n throw new Error('ctx.render() is not supported on the Fastify runtime (no view engine).')\n }\n writeHead(statusCode: number, headers?: Record<string, string | number | string[]>): this {\n // Streaming / SSE: take over the raw socket so Fastify doesn't try to\n // serialize a reply we're writing by hand.\n this.reply.hijack()\n this.reply.raw.writeHead(statusCode, headers)\n return this\n }\n flushHeaders(): this {\n ;(this.reply.raw as ServerResponse & { flushHeaders?: () => void }).flushHeaders?.()\n return this\n }\n write(chunk: unknown): boolean {\n return this.reply.raw.write(chunk as never)\n }\n end(data?: unknown): this {\n this.reply.raw.end(data as never)\n return this\n }\n once(event: string, listener: (...args: unknown[]) => void): this {\n this.reply.raw.once(event, listener)\n return this\n }\n get headersSent(): boolean {\n return this.reply.sent || this.reply.raw.headersSent\n }\n}\n\nfunction replyDriver(reply: FastifyReplyLike): RuntimeResponse {\n return new FastifyReplyDriver(reply) as unknown as RuntimeResponse\n}\n\n/** Build the Fastify per-route handler that runs the kickjs pipeline. */\nfunction routeHandler(entry: RouteEntry): FastifyHandler {\n // Validation middleware is built ONCE per route (parity with the Express\n // materializer) — `validate()` constructs a fresh closure, so calling it\n // per request was pure allocation waste.\n const validator = entry.meta.validation ? validate(entry.meta.validation) : undefined\n return async (request, reply) => {\n // Fastify runs the route handler OUTSIDE the connect-middleware chain, so\n // (unlike Express) the requestScopeMiddleware ALS frame isn't active here.\n // Establish it around the pipeline so REQUEST-scoped DI, contributors, and\n // ctx.set/get work. Reuse the inbound x-request-id, or the id a connect\n // middleware (requestId / requestScope, run earlier via middie) already\n // stamped on the raw request, before generating a fresh one.\n // Use the raw node request as `ctx.req`: it natively provides the stream\n // events (`on('close')` / `once`) that SSE and `ctx.signal` need — Fastify's\n // request object doesn't. Copy Fastify's parsed body/params/query onto it so\n // `ctx.body` / `ctx.params` / `ctx.query` keep working.\n const raw = request.raw as IncomingMessage & {\n requestId?: string\n body?: unknown\n params?: unknown\n query?: unknown\n file?: unknown\n files?: unknown\n }\n raw.body = request.body\n raw.params = request.params\n raw.query = request.query\n\n // @FileUpload: buffer multipart parts into the multer-shaped file(s) that\n // `ctx.file` / `ctx.files` expose. Fastify doesn't parse multipart natively\n // — @fastify/multipart (registered in nodeHandler when any route needs it)\n // provides `request.parts()`. Non-file fields are merged onto `raw.body` so\n // validation / `ctx.body` see them, matching multer's req.body behavior.\n if (entry.meta.upload && entry.meta.upload.mode !== 'none') {\n if (typeof request.parts !== 'function') {\n throw new Error(\n \"@forinda/kickjs: @FileUpload on the Fastify runtime requires '@fastify/multipart'.\\n\" +\n 'Install it as a peer dependency: pnpm add @fastify/multipart.',\n )\n }\n const rawParts: RawUploadPart[] = []\n const fields: Record<string, unknown> = {}\n for await (const part of request.parts()) {\n if (part.type === 'file' && part.toBuffer) {\n rawParts.push({\n fieldname: part.fieldname,\n filename: part.filename ?? '',\n mimetype: part.mimetype ?? 'application/octet-stream',\n buffer: await part.toBuffer(),\n })\n } else {\n fields[part.fieldname] = part.value\n }\n }\n const { file, files } = applyUploadConfig(rawParts, entry.meta.upload)\n raw.file = file\n raw.files = files\n raw.body = { ...(raw.body as Record<string, unknown> | undefined), ...fields }\n }\n\n const headerId = raw.headers['x-request-id']\n const requestId = (Array.isArray(headerId) ? headerId[0] : headerId) || raw.requestId\n const store = createRequestStore(requestId)\n // Propagate the resolved id back onto the raw request so anything reading\n // `req.requestId` inside the frame (e.g. request-logger) matches the store\n // and the X-Request-Id response header — parity with Express.\n raw.requestId = store.requestId\n\n // @PreDestroy teardown for REQUEST-scoped instances once the response\n // closes (finished or aborted) — parity with requestScopeMiddleware.\n reply.raw.once('close', () => disposeRequestStore(store))\n\n await requestStore.run(store, async () => {\n publishMatchedRoute(raw, entry)\n const ctx = new RequestContext(raw as never, reply as never, NOOP_NEXT, replyDriver(reply))\n\n // `beforeValidation` contributors (#677) — ahead of validation and middleware.\n if (entry.earlyContributorRunner) {\n await entry.earlyContributorRunner(ctx)\n if (reply.sent) return\n }\n\n // Validation (from @Get(path, schema) / route.validation). `validator` is a\n // connect-style middleware built once at route registration; it mutates\n // req.body/query/params to the parsed value and calls next(err) on failure\n // — it never touches `res`, so it runs cleanly here. A rejection\n // propagates to the error handler (422).\n if (validator) {\n await new Promise<void>((resolve, reject) => {\n validator(raw as never, undefined as never, (err?: unknown) =>\n err ? reject(err) : resolve(),\n )\n })\n }\n\n // Class + method middleware — `(ctx, next)`; await each, stop if it ended\n // the response or called next(err).\n for (const mw of entry.middlewares) {\n let advanced = false\n await new Promise<void>((resolve, reject) => {\n const next = (err?: unknown): void => {\n advanced = true\n if (err) reject(err)\n else resolve()\n }\n Promise.resolve(mw(ctx, next)).catch(reject)\n })\n if (!advanced || reply.sent) return\n }\n\n if (entry.contributorRunner) await entry.contributorRunner(ctx)\n if (reply.sent) return\n const result = await entry.handler(ctx)\n // Return-value handlers (reply.ts): auto-send when nothing was written.\n if (!reply.sent) applyHandlerResult(ctx, result)\n })\n }\n}\n\n/**\n * The Fastify HTTP runtime. Pass to `bootstrap({ runtime: fastifyRuntime() })`.\n * Per spec §10: Fastify's built-in pino logger is disabled (`logger: false`) so\n * the kickjs `requestLogger` middleware stays the single log format.\n */\nexport function fastifyRuntime(): HttpRuntime<FastifyAppLike> {\n return {\n name: 'fastify',\n\n createApp(options: RuntimeAppOptions = {}): FastifyAppLike {\n const Fastify = loadPeer('fastify')\n const app = Fastify({\n logger: false,\n // Match Express / h3 routing leniency: `/api/v1/hello` and\n // `/api/v1/hello/` resolve to the same route. Without this, Fastify's\n // strict router 404s a trailing-slash request — e.g. a controller's\n // root `@Get('/')` (mounted at the prefix) misses `${prefix}/`. Set under\n // `routerOptions` (Fastify 5+; top-level `ignoreTrailingSlash` is\n // deprecated, removed in Fastify 6).\n routerOptions: { ignoreTrailingSlash: true },\n // trustProxy passed through for now; core X-Forwarded-For\n // normalization (spec §10 Q1) is a follow-up.\n trustProxy: typeof options.trustProxy === 'function' ? false : options.trustProxy,\n }) as FastifyAppLike & {\n [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }>\n }\n app[QUEUE] = []\n\n // Fastify ships parsers for `application/json` and `text/plain` only, and\n // answers 415 for everything else. That made a form post work on Express\n // and h3 but fail on Fastify alone (#590). Register the rest of the\n // framework's supported set so all engines agree.\n //\n // Deliberately NOT a `'*'` catch-all: that would swallow multipart, and\n // `@fastify/multipart` is itself the multipart content-type parser,\n // registered conditionally in `nodeHandler` when a route needs it.\n const addParser = app.addContentTypeParser?.bind(app)\n if (addParser) {\n // `application/*+json` — RFC 6838 §4.2.8 guarantees the bytes are JSON.\n addParser(\n /^application\\/[\\w.+-]+\\+json$/,\n { parseAs: 'string' },\n (_req: FastifyRequestLike, body: string | Buffer, done: FastifyParserDone) => {\n if (body === '') return done(null, undefined)\n try {\n done(null, JSON.parse(body as string))\n } catch (err) {\n // A bare SyntaxError carries no status, so the shared error handler\n // would call it a 500 — Fastify's own JSON parser answers 400\n // (FST_ERR_CTP_INVALID_JSON_BODY), and so must this one.\n done(\n HttpException.badRequest(\n err instanceof Error ? err.message : 'Request body could not be parsed',\n ),\n undefined,\n )\n }\n },\n )\n addParser(\n 'application/x-www-form-urlencoded',\n { parseAs: 'string' },\n (_req: FastifyRequestLike, body: string | Buffer, done: FastifyParserDone) => {\n done(null, Object.fromEntries(new URLSearchParams(body as string)))\n },\n )\n // `text/*` beyond Fastify's built-in `text/plain`. Raw string, never\n // JSON: `text/plain` is CORS-safelisted, so parsing it as JSON would\n // re-open no-preflight CSRF against a JSON API.\n addParser(\n /^text\\//,\n { parseAs: 'string' },\n (_req: FastifyRequestLike, body: string | Buffer, done: FastifyParserDone) => {\n done(null, body)\n },\n )\n // Fastify already answers 415 for a type it has no parser for, but with\n // its own message and no `Accept`. `'*'` is Fastify's documented\n // fallback — consulted only when nothing more specific matches — so it\n // does not shadow the parsers above, nor `@fastify/multipart`, which\n // registers `multipart/form-data` when an upload route needs it.\n addParser(\n '*',\n { parseAs: 'string' },\n (req: FastifyRequestLike, body: string | Buffer, done: FastifyParserDone) => {\n // No body is not an unsupported body. Fastify calls the parser even\n // for an empty payload, so without this a bodyless POST carrying an\n // unrelated content type would be rejected.\n if (!body) return done(null, undefined)\n done(unsupportedMediaTypeError(req.headers?.['content-type']), undefined)\n },\n )\n }\n\n return app\n },\n\n nodeHandler(app) {\n // Register middie + queued connect middleware + finalize `ready()` exactly\n // ONCE per app, even though Application may call nodeHandler per request.\n const state = app as {\n [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }>\n [READY]?: Promise<void>\n [NEEDS_MULTIPART]?: boolean\n }\n if (!state[READY]) {\n state[READY] = (async () => {\n // Register @fastify/multipart first when any route uses @FileUpload so\n // `request.parts()` is available. Our applyUploadConfig enforces the\n // real per-file maxSize, so give the plugin a generous fileSize ceiling\n // (its 1MB default would silently truncate larger uploads first).\n if (state[NEEDS_MULTIPART]) {\n const multipart = loadPeer('@fastify/multipart')\n await app.register(multipart, { limits: { fileSize: Number.MAX_SAFE_INTEGER } })\n }\n const middie = loadPeer('@fastify/middie')\n await app.register(middie)\n for (const { mw, opts } of state[QUEUE] ?? []) {\n if (opts?.path !== undefined)\n (app.use as (p: unknown, m: unknown) => void)(opts.path, mw)\n else (app.use as (m: unknown) => void)(mw)\n }\n await app.ready()\n })()\n }\n const readyP = state[READY]\n\n return (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => {\n if (next) (req as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT] = next\n readyP\n .then(() => app.routing(req, res))\n .catch((err) => {\n if (next) next(err)\n })\n }\n },\n\n mountRoutes(app, table: RouteTable) {\n for (const { mountPath, routes } of table) {\n for (const entry of routes) {\n if (entry.meta.upload) (app as { [NEEDS_MULTIPART]?: boolean })[NEEDS_MULTIPART] = true\n const url = joinPath(mountPath, entry.path)\n app.route({ method: entry.method, url, handler: routeHandler(entry) })\n }\n }\n },\n\n useConnect(app, mw: ConnectMiddleware, opts?: UseConnectOptions) {\n const queue = (\n app as { [QUEUE]?: Array<{ mw: ConnectMiddleware; opts?: UseConnectOptions }> }\n )[QUEUE]\n queue?.push({ mw, opts })\n },\n\n serveStatic(app, prefix, dir) {\n // Serve via `serve-static` (the standalone connect middleware behind\n // express.static) so the Fastify runtime carries no `express` dependency —\n // queued like any other connect middleware, bridged through middie.\n const serveStatic = loadPeer('serve-static')\n this.useConnect(app, serveStatic(dir) as ConnectMiddleware, { path: prefix })\n },\n\n setNotFound(app, mw: ConnectMiddleware) {\n app.setNotFoundHandler((request, reply) => {\n const next = (request.raw as IncomingMessage & { [NEXT]?: (err?: unknown) => void })[NEXT]\n if (next) {\n // Vite dev fall-through: hand the request back to the outer chain.\n reply.hijack()\n next()\n return\n }\n // Pass the reply DRIVER (not raw res) so the connect-style\n // notFoundHandler's `res.status().json()` lands on the Fastify reply.\n ;(mw as (req: unknown, res: unknown, next: () => void) => void)(\n request.raw,\n replyDriver(reply),\n NOOP_NEXT,\n )\n })\n },\n\n setErrorHandler(app, mw: ConnectMiddleware) {\n app.setErrorHandler((err, request, reply) => {\n // Reply driver (not raw res) so the connect-style errorHandler's\n // `res.status().json()` / `setHeader` land on the Fastify reply.\n ;(mw as (e: unknown, req: unknown, res: unknown, next: () => void) => void)(\n err,\n request.raw,\n replyDriver(reply),\n NOOP_NEXT,\n )\n })\n },\n\n capabilities: {\n render: false,\n uploads: true,\n connectMiddleware: true,\n nativeBodyParsing: true,\n http2: true,\n },\n }\n}\n\n/**\n * The Fastify runtime's engine-native types — what the runtime-typed escape\n * hatches resolve to once the `kick/runtime` typegen emits a `KickRuntimeRegister`\n * augmentation pointing here (spec §4.3b). `AdapterContext.app` / `getRuntimeApp()`\n * become `FastifyInstance`; `ctx.req` / `ctx.res` the Fastify request / reply.\n * Mirrors {@link import('../runtime').ExpressRuntimeTypes} for the default engine.\n */\nexport interface FastifyRuntimeTypes {\n request: import('fastify').FastifyRequest\n response: import('fastify').FastifyReply\n app: import('fastify').FastifyInstance\n}\n\n/** Join a mount prefix and a route path into one URL, collapsing slashes. */\nfunction joinPath(mountPath: string, path: string): string {\n const a = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n // A root route path ('/' or '') maps to the mount prefix itself — otherwise a\n // controller `@Post('/')` would register at `${prefix}/`, and a request to\n // `${prefix}` (no trailing slash) wouldn't match under Fastify's strict router.\n if (path === '/' || path === '') return a === '' ? '/' : a\n const b = path.startsWith('/') ? path : `/${path}`\n return `${a}${b}`\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"h3-web.d.mts","names":[],"sources":["../../../src/http/runtimes/h3-web.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"h3-web.d.mts","names":[],"sources":["../../../src/http/runtimes/h3-web.ts"],"mappings":";;;;UAyCU;EACR,GAAG,gBAAgB,cAAc,UAAU,OAAO;EAClD,IAAI,cAAc,UAAU,OAAO;EACnC,OAAO;EACP,MAAM,SAAS,UAAU,QAAQ;;UAEzB;EACR,KAAK;IAAY;MAAY;QAAS,KAAK;QAAiB,KAAK;;;;EACjE,KAAK;EACL;IAAW,SAAS;;;iBASL;;;;;;;;EAQf;;;;;;wBA4Rc,aAAa,UAAS,sBAA2B,YAAY;;;;;iBAiH5D;EACf,SAAS;EACT,UAAU;EACV"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v8.
|
|
2
|
+
* @forinda/kickjs v8.7.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{createLogger,describeError}from"../../core/logger.mjs";import{n as WebRequestShim,r as WebResponseDriver,t as compileWebRoute}from"../../handler-
|
|
11
|
+
import{createLogger,describeError}from"../../core/logger.mjs";import{n as WebRequestShim,r as WebResponseDriver,t as compileWebRoute}from"../../handler-MSvgGVk0.mjs";import{createRequire}from"node:module";const log=createLogger(`H3WebRuntime`),peerRequire=createRequire(import.meta.url),NOTFOUND_MW=Symbol(`kickjs.h3web.notFoundMw`),ERROR_MW=Symbol(`kickjs.h3web.errorMw`),ASSEMBLED=Symbol(`kickjs.h3web.assembled`),NOOP_NEXT=()=>{};function loadH3v2(injected){let m=injected??peerRequire(`h3`),resolved=m&&m.__esModule&&m.default?{...m.default,...m}:m;if(typeof resolved.H3!=`function`)throw Error("@forinda/kickjs: h3WebRuntime() requires h3 v2 (the web-standards line, npm dist-tag 'latest'), but the installed h3 has no H3 class — that is the v1 line. Either `pnpm add h3@latest` for this runtime, or keep h3 v1 and use h3Runtime() from '@forinda/kickjs/h3'.");return resolved}async function runConnectTerminal(mw,event,err){let req=new WebRequestShim(event.req,event.url),driver=new WebResponseDriver(event.req.signal);return mw&&(err===void 0?mw(req,driver,NOOP_NEXT):mw(err,req,driver,NOOP_NEXT)),driver.settled||(err===void 0?driver.status(404).json({error:`Not Found`}):(log.error(err,`h3-web: error handler did not settle the response — ${describeError(err)}`),driver.status(500).json({error:`Internal Server Error`,...typeof process<`u`&&process.env?.NODE_ENV===`production`?{}:{message:describeError(err)}}))),driver.ready}const FETCH_CONNECT=new WeakMap;function fetchConnectState(event){let state=FETCH_CONNECT.get(event);return state||(state={req:new WebRequestShim(event.req,event.url),headers:new Headers},FETCH_CONNECT.set(event,state)),state}function withConnectHeaders(event,response){let state=FETCH_CONNECT.get(event);if(!state)return response;let missing=[...state.headers].filter(([name])=>name===`set-cookie`||!response.headers.has(name));if(state.headers=new Headers,missing.length===0)return response;try{for(let[name,value]of missing)response.headers.append(name,value);return response}catch{let headers=new Headers(response.headers);for(let[name,value]of missing)headers.append(name,value);return new Response(response.body,{status:response.status,statusText:response.statusText,headers})}}var FetchConnectResponse=class extends WebResponseDriver{prepared;code=200;listeners=[];constructor(prepared,signal){super(signal),this.prepared=prepared}get statusCode(){return this.code}set statusCode(code){this.status(code)}status(code){return this.code=code,super.status(code)}writeHead(code,headers){return this.code=code,super.writeHead(code,headers)}setHeader(name,value){if(Array.isArray(value)){this.prepared.delete(name);for(let v of value)this.prepared.append(name,String(v))}else this.prepared.set(name,String(value));return this}getHeader(name){return this.prepared.get(name)??void 0}hasHeader(name){return this.prepared.has(name)}removeHeader(name){this.prepared.delete(name)}once(event,listener){return this.listeners.push([event,listener]),this}on(event,listener){return this.once(event,listener)}removeListener(event,listener){return this.listeners=this.listeners.filter(([e,l])=>e!==event||l!==listener),this}off(event,listener){return this.removeListener(event,listener)}done(){for(let event of[`finish`,`close`]){for(let[e,listener]of this.listeners)e===event&&listener();this.listeners=this.listeners.filter(([e])=>e!==event)}}};function runConnectOnFetch(mw,event,next){let state=fetchConnectState(event),res=new FetchConnectResponse(state.headers,event.req.signal);return new Promise((resolve,reject)=>{let respond=response=>{res.done(),resolve(withConnectHeaders(event,response))},fail=err=>{res.done(),reject(err)};res.ready.then(respond);let connectNext=err=>{if(err)return fail(err);if(res.settled)return;let id=state.req.requestId;if(id&&event.req.headers.get(`x-request-id`)!==id)try{event.req.headers.set(`x-request-id`,id)}catch{}let downstream;try{downstream=next()}catch(e){return fail(e)}Promise.resolve(downstream).then(value=>{if(value instanceof Response)return respond(value);res.done(),resolve(value)},fail)};try{mw(state.req,res,connectNext)}catch(err){fail(err)}})}function assembleCatchAll(app){let state=app;state[ASSEMBLED]||(state[ASSEMBLED]=!0,app.all(`/**`,event=>{let mw=state[NOTFOUND_MW];return runConnectTerminal(mw,event)}))}function h3WebRuntime(options={}){let{H3,toNodeHandler,fromNodeHandler}=loadH3v2(options.h3);return{name:`h3-web`,createApp(_options={}){let holder={},app=new H3({onError:(error,event)=>{let mw=holder.app?.[ERROR_MW];return runConnectTerminal(mw,event,error).then(response=>withConnectHeaders(event,response))}});return holder.app=app,app},nodeHandler(app){assembleCatchAll(app);let listener=toNodeHandler(app);return(req,res,next)=>{try{let result=listener(req,res);result&&typeof result.then==`function`&&result.catch(err=>next?.(err))}catch(err){next?.(err)}}},mountRoutes(app,table){for(let{mountPath,routes}of table)for(let entry of routes){let url=joinPath(mountPath,entry.path),run=compileWebRoute(entry,{onError:async(err,req,driver)=>{let mw=app[ERROR_MW];mw&&mw(err,req,driver,NOOP_NEXT)}});app.on(entry.method,url,event=>run({request:event.req,url:event.url,params:event.context.params??{}}))}},useConnect(app,mw,opts){let viaNode=fromNodeHandler(mw),handler=(event,next)=>event.req.runtime?.node?.res?viaNode(event):runConnectOnFetch(mw,event,next);opts?.path===void 0?app.use(handler):app.use(joinPath(String(opts.path),`/**`),handler)},serveStatic(app,prefix,dir){let serveStatic=peerRequire(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app[NOTFOUND_MW]=mw,assembleCatchAll(app)},setErrorHandler(app,mw){app[ERROR_MW]=mw},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{h3WebRuntime};
|
|
12
12
|
//# sourceMappingURL=h3-web.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"h3-web.mjs","names":[],"sources":["../../../src/http/runtimes/h3-web.ts"],"sourcesContent":["// The h3 v2 (web-standards) runtime — `@forinda/kickjs/h3-web`.\n//\n// ADDITIVE runtime: the h3 v1 runtime (`./h3.ts`, `@forinda/kickjs/h3`)\n// stays untouched so existing adopters keep working (locked decision,\n// web-standards-edge-design.md §3.1). This runtime targets h3 >= 2 — the\n// web-standard rebase where `event.req` is a WHATWG Request, handlers\n// return values/Responses, and `app.fetch(request)` is the universal entry.\n//\n// The request pipeline runs through the shared web driver pair\n// (`../web/driver`, `../web/handler`) — the same code path the\n// `@forinda/kickjs/web` edge entry uses, so node bootstrap and edge deploys\n// exercise identical request semantics.\n//\n// Known limitation vs the v1 runtime: no Vite dev-server fall-through —\n// unmatched requests are answered by the kick notFound handler (h3 v2 owns\n// the full request; there is no node `next()` bail-out mid-engine). Use the\n// v1 h3 runtime or Express for Vite-integrated dev setups.\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createRequire } from 'node:module'\n\nimport type {\n ConnectMiddleware,\n HttpRuntime,\n RouteTable,\n RuntimeAppOptions,\n UseConnectOptions,\n} from '../runtime'\nimport { compileWebRoute, type WebRouteHooks } from '../web/handler'\nimport { WebRequestShim, WebResponseDriver } from '../web/driver'\nimport { createLogger, describeError } from '../../core/logger'\n\nconst log = createLogger('H3WebRuntime')\n\nconst peerRequire = createRequire(import.meta.url)\n\nconst NOTFOUND_MW = Symbol('kickjs.h3web.notFoundMw')\nconst ERROR_MW = Symbol('kickjs.h3web.errorMw')\nconst ASSEMBLED = Symbol('kickjs.h3web.assembled')\nconst NOOP_NEXT = (): void => {}\n\n// Minimal structural surface of h3 v2 we depend on.\ninterface H3AppLike {\n on(method: string, path: string, handler: (event: H3EventLike) => unknown): unknown\n all(path: string, handler: (event: H3EventLike) => unknown): unknown\n use(...args: unknown[]): unknown\n fetch(request: Request): Promise<Response>\n}\ninterface H3EventLike {\n req: Request & { runtime?: { node?: { req: IncomingMessage; res: ServerResponse } } }\n url: URL\n context: { params?: Record<string, string> }\n}\n\ninterface H3v2Module {\n H3: new (config?: unknown) => H3AppLike\n toNodeHandler: (app: H3AppLike) => (req: IncomingMessage, res: ServerResponse) => void\n fromNodeHandler: (mw: unknown) => unknown\n}\n\nexport interface H3WebRuntimeOptions {\n /**\n * Pre-imported h3 v2 module. Two audiences:\n * - Edge/worker bundlers with no `createRequire` at runtime:\n * `h3WebRuntime({ h3: await import('h3') })`\n * - Tests running both h3 majors side by side via an npm alias.\n * Omitted → the peer is loaded via `createRequire` (node bootstrap path).\n */\n h3?: unknown\n}\n\n/** Resolve h3 (injected or peer-required) and fail fast unless it is v2. */\nfunction loadH3v2(injected?: unknown): H3v2Module {\n const mod = injected ?? peerRequire('h3')\n const m = mod as { __esModule?: boolean; default?: Record<string, unknown> } & Record<\n string,\n unknown\n >\n const resolved = (m && m.__esModule && m.default\n ? { ...m.default, ...m }\n : m) as unknown as H3v2Module\n if (typeof resolved.H3 !== 'function') {\n throw new Error(\n \"@forinda/kickjs: h3WebRuntime() requires h3 v2 (the web-standards line, npm dist-tag 'latest'), \" +\n 'but the installed h3 has no H3 class — that is the v1 line. ' +\n \"Either `pnpm add h3@latest` for this runtime, or keep h3 v1 and use h3Runtime() from '@forinda/kickjs/h3'.\",\n )\n }\n return resolved\n}\n\n/**\n * Bridge a connect-style terminal middleware (kick's notFound / error\n * handlers) onto the web driver pair, returning the produced Response.\n */\nasync function runConnectTerminal(\n mw: ConnectMiddleware | undefined,\n event: H3EventLike,\n err?: unknown,\n): Promise<Response> {\n const req = new WebRequestShim(event.req, event.url)\n const driver = new WebResponseDriver(event.req.signal)\n if (mw) {\n if (err !== undefined) {\n ;(mw as (e: unknown, rq: unknown, rs: unknown, n: () => void) => void)(\n err,\n req,\n driver,\n NOOP_NEXT,\n )\n } else {\n ;(mw as (rq: unknown, rs: unknown, n: () => void) => void)(req, driver, NOOP_NEXT)\n }\n }\n if (!driver.settled) {\n if (err !== undefined) {\n // The configured error handler ran but didn't settle the response\n // (or there wasn't one). Emitting a bare 500 with no log entry\n // makes this failure invisible on both sides — log it here, since\n // by definition nothing downstream will.\n log.error(err, `h3-web: error handler did not settle the response — ${describeError(err)}`)\n driver.status(500).json({\n error: 'Internal Server Error',\n ...(typeof process !== 'undefined' && process.env?.NODE_ENV === 'production'\n ? {}\n : { message: describeError(err) }),\n })\n } else {\n driver.status(404).json({ error: 'Not Found' })\n }\n }\n return driver.ready\n}\n\n// ── Connect middleware on the fetch path ─────────────────────────────────\n//\n// `app.fetch(request)` (edge, Bun, Deno, a serverless function) carries no\n// node req/res, and h3's `fromNodeHandler` throws without one. Connect\n// middleware there runs against the web driver pair instead: one request shim\n// per event (so `req.requestId` set by one middleware is seen by the next) and\n// one header bag per event that is merged into whatever Response comes back.\n// The merge is ours rather than h3's `event.res.headers` because h3 skips\n// prepared headers on non-2xx responses, and a 404/422/500 must still carry\n// helmet's headers and the request id, exactly as on node.\n\ninterface FetchConnectState {\n req: WebRequestShim\n headers: Headers\n}\n\nconst FETCH_CONNECT = new WeakMap<object, FetchConnectState>()\n\nfunction fetchConnectState(event: H3EventLike): FetchConnectState {\n let state = FETCH_CONNECT.get(event)\n if (!state) {\n state = { req: new WebRequestShim(event.req, event.url), headers: new Headers() }\n FETCH_CONNECT.set(event, state)\n }\n return state\n}\n\n/**\n * Add the headers connect middleware set to `response`. The response's own\n * headers win — a later `setHeader` overrides an earlier one on node too.\n * Consumed once, so every middleware layer can call it on the way out.\n */\nfunction withConnectHeaders(event: H3EventLike, response: Response): Response {\n const state = FETCH_CONNECT.get(event)\n if (!state) return response\n const missing = [...state.headers].filter(\n ([name]) => name === 'set-cookie' || !response.headers.has(name),\n )\n state.headers = new Headers()\n if (missing.length === 0) return response\n try {\n for (const [name, value] of missing) response.headers.append(name, value)\n return response\n } catch {\n // Immutable headers (a Response from fetch) — rebuild around the body.\n const headers = new Headers(response.headers)\n for (const [name, value] of missing) headers.append(name, value)\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n\n/**\n * The node `res` surface connect middleware uses, over the web driver:\n * headers go to the per-event bag, `statusCode`, and `finish`/`close`\n * emitted once the response exists.\n */\nclass FetchConnectResponse extends WebResponseDriver {\n private code = 200\n private listeners: Array<[string, (...args: unknown[]) => void]> = []\n\n constructor(\n private readonly prepared: Headers,\n signal: AbortSignal,\n ) {\n super(signal)\n }\n\n get statusCode(): number {\n return this.code\n }\n\n set statusCode(code: number) {\n this.status(code)\n }\n\n override status(code: number): this {\n this.code = code\n return super.status(code)\n }\n\n override writeHead(code: number, headers?: Record<string, string | number | string[]>): this {\n this.code = code\n return super.writeHead(code, headers)\n }\n\n override setHeader(name: string, value: unknown): this {\n if (Array.isArray(value)) {\n this.prepared.delete(name)\n for (const v of value) this.prepared.append(name, String(v))\n } else {\n this.prepared.set(name, String(value))\n }\n return this\n }\n\n getHeader(name: string): string | undefined {\n return this.prepared.get(name) ?? undefined\n }\n\n hasHeader(name: string): boolean {\n return this.prepared.has(name)\n }\n\n removeHeader(name: string): void {\n this.prepared.delete(name)\n }\n\n override once(event: string, listener: (...args: unknown[]) => void): this {\n this.listeners.push([event, listener])\n return this\n }\n\n on(event: string, listener: (...args: unknown[]) => void): this {\n return this.once(event, listener)\n }\n\n removeListener(event: string, listener: (...args: unknown[]) => void): this {\n this.listeners = this.listeners.filter(([e, l]) => e !== event || l !== listener)\n return this\n }\n\n off(event: string, listener: (...args: unknown[]) => void): this {\n return this.removeListener(event, listener)\n }\n\n /** The response exists (or the request failed): fire `finish` then `close`. */\n done(): void {\n for (const event of ['finish', 'close']) {\n for (const [e, listener] of this.listeners) if (e === event) listener()\n this.listeners = this.listeners.filter(([e]) => e !== event)\n }\n }\n}\n\nfunction runConnectOnFetch(\n mw: ConnectMiddleware,\n event: H3EventLike,\n next: () => unknown,\n): Promise<unknown> {\n const state = fetchConnectState(event)\n const res = new FetchConnectResponse(state.headers, event.req.signal)\n\n return new Promise((resolve, reject) => {\n const respond = (response: Response): void => {\n res.done()\n resolve(withConnectHeaders(event, response))\n }\n const fail = (err: unknown): void => {\n res.done()\n reject(err)\n }\n\n // The middleware answered itself (`res.end`, `res.json`, `writeHead`).\n void res.ready.then(respond)\n\n const connectNext = (err?: unknown): void => {\n if (err) return fail(err)\n if (res.settled) return\n // The route builds its request store from the inbound header; hand it the\n // id requestScope/requestId settled on so body, logs and header agree.\n const id = state.req.requestId\n if (id && event.req.headers.get('x-request-id') !== id) {\n try {\n event.req.headers.set('x-request-id', id)\n } catch {\n // Immutable request headers on some runtimes — the route mints its own id.\n }\n }\n let downstream: unknown\n try {\n // Synchronous, so downstream work starts inside any AsyncLocalStorage\n // scope this middleware opened (requestScopeMiddleware).\n downstream = next()\n } catch (e) {\n return fail(e)\n }\n Promise.resolve(downstream).then((value) => {\n if (value instanceof Response) return respond(value)\n res.done()\n resolve(value)\n }, fail)\n }\n\n try {\n ;(mw as (rq: unknown, rs: unknown, n: (err?: unknown) => void) => void)(\n state.req,\n res,\n connectNext,\n )\n } catch (err) {\n fail(err)\n }\n })\n}\n\n/**\n * Terminal catch-all AFTER all mounted routes: kick's own notFound handler\n * answers unmatched paths (h3 v2's default 404 never fires). rou3 ranks `/**`\n * below every concrete route, so registration time does not matter.\n */\nfunction assembleCatchAll(app: H3AppLike): void {\n const state = app as unknown as Record<symbol, unknown>\n if (state[ASSEMBLED]) return\n state[ASSEMBLED] = true\n app.all('/**', (event: H3EventLike) => {\n const mw = state[NOTFOUND_MW] as ConnectMiddleware | undefined\n return runConnectTerminal(mw, event)\n })\n}\n\n/**\n * The h3 v2 HTTP runtime. Pass to `bootstrap({ runtime: h3WebRuntime() })`.\n * Requires the h3 v2 peer; fails fast with guidance when v1 is installed.\n */\nexport function h3WebRuntime(options: H3WebRuntimeOptions = {}): HttpRuntime<H3AppLike> {\n const { H3, toNodeHandler, fromNodeHandler } = loadH3v2(options.h3)\n\n return {\n name: 'h3-web',\n\n createApp(_options: RuntimeAppOptions = {}): H3AppLike {\n const holder: { app?: H3AppLike } = {}\n const app = new H3({\n // Engine-level errors (middleware throws outside the kick pipeline)\n // dispatch to the kick error handler once setErrorHandler stashes it.\n onError: (error: unknown, event: H3EventLike) => {\n const mw = (holder.app as unknown as Record<symbol, ConnectMiddleware | undefined>)?.[\n ERROR_MW\n ]\n return runConnectTerminal(mw, event, error).then((response) =>\n withConnectHeaders(event, response),\n )\n },\n })\n holder.app = app\n return app\n },\n\n nodeHandler(app) {\n assembleCatchAll(app)\n const listener = toNodeHandler(app)\n return (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => {\n try {\n const result = listener(req, res) as unknown\n if (result && typeof (result as Promise<unknown>).then === 'function') {\n void (result as Promise<unknown>).catch((err) => next?.(err))\n }\n } catch (err) {\n next?.(err)\n }\n }\n },\n\n mountRoutes(app, table: RouteTable) {\n for (const { mountPath, routes } of table) {\n for (const entry of routes) {\n const url = joinPath(mountPath, entry.path)\n const hooks: WebRouteHooks = {\n onError: async (err, req, driver) => {\n const mw = (app as unknown as Record<symbol, ConnectMiddleware | undefined>)[ERROR_MW]\n if (!mw) return\n ;(mw as (e: unknown, rq: unknown, rs: unknown, n: () => void) => void)(\n err,\n req,\n driver,\n NOOP_NEXT,\n )\n },\n }\n const run = compileWebRoute(entry, hooks)\n app.on(entry.method, url, (event: H3EventLike) =>\n run({\n request: event.req,\n url: event.url,\n params: event.context.params ?? {},\n }),\n )\n }\n }\n },\n\n useConnect(app, mw: ConnectMiddleware, opts?: UseConnectOptions) {\n // Behind a node server (toNodeHandler) the event carries node req/res and\n // h3's own bridge runs the middleware unchanged. Called through\n // `app.fetch(request)` there is none and fromNodeHandler would throw, so\n // the middleware runs against the web driver pair instead.\n const viaNode = fromNodeHandler(mw) as (event: H3EventLike) => unknown\n const handler = (event: H3EventLike, next: () => unknown) =>\n event.req.runtime?.node?.res ? viaNode(event) : runConnectOnFetch(mw, event, next)\n if (opts?.path !== undefined) {\n // v2 `use` is exact-match; `/**` covers the subtree like v1's prefix.\n app.use(joinPath(String(opts.path), '/**'), handler)\n } else {\n app.use(handler)\n }\n },\n\n serveStatic(app, prefix, dir) {\n // serve-static bridged like any other connect middleware (node only).\n const serveStatic = peerRequire('serve-static')\n this.useConnect(app, serveStatic(dir) as ConnectMiddleware, { path: prefix })\n },\n\n setNotFound(app, mw: ConnectMiddleware) {\n ;(app as unknown as Record<symbol, ConnectMiddleware>)[NOTFOUND_MW] = mw\n // Here as well as in nodeHandler: an app only ever called through\n // `app.fetch` never reaches nodeHandler, and would answer h3's own 404.\n assembleCatchAll(app)\n },\n\n setErrorHandler(app, mw: ConnectMiddleware) {\n ;(app as unknown as Record<symbol, ConnectMiddleware>)[ERROR_MW] = mw\n },\n\n capabilities: {\n render: false,\n uploads: true,\n connectMiddleware: true,\n nativeBodyParsing: true,\n },\n }\n}\n\n/**\n * h3-web runtime engine types for the `kick/runtime` typegen augmentation —\n * mirrors {@link import('./h3').H3RuntimeTypes} for the v1 runtime.\n */\nexport interface H3WebRuntimeTypes {\n request: Request\n response: Response\n app: unknown\n}\n\n/** Join a mount prefix and a route path into one URL, collapsing slashes. */\nfunction joinPath(mountPath: string, path: string): string {\n const a = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n if (path === '/' || path === '') return a === '' ? '/' : a\n const b = path.startsWith('/') ? path : `/${path}`\n return `${a}${b}`\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"h3-web.mjs","names":[],"sources":["../../../src/http/runtimes/h3-web.ts"],"sourcesContent":["// The h3 v2 (web-standards) runtime — `@forinda/kickjs/h3-web`.\n//\n// ADDITIVE runtime: the h3 v1 runtime (`./h3.ts`, `@forinda/kickjs/h3`)\n// stays untouched so existing adopters keep working. This runtime targets h3 >= 2 — the\n// web-standard rebase where `event.req` is a WHATWG Request, handlers\n// return values/Responses, and `app.fetch(request)` is the universal entry.\n//\n// The request pipeline runs through the shared web driver pair\n// (`../web/driver`, `../web/handler`) — the same code path the\n// `@forinda/kickjs/web` edge entry uses, so node bootstrap and edge deploys\n// exercise identical request semantics.\n//\n// Known limitation vs the v1 runtime: no Vite dev-server fall-through —\n// unmatched requests are answered by the kick notFound handler (h3 v2 owns\n// the full request; there is no node `next()` bail-out mid-engine). Use the\n// v1 h3 runtime or Express for Vite-integrated dev setups.\n\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createRequire } from 'node:module'\n\nimport type {\n ConnectMiddleware,\n HttpRuntime,\n RouteTable,\n RuntimeAppOptions,\n UseConnectOptions,\n} from '../runtime'\nimport { compileWebRoute, type WebRouteHooks } from '../web/handler'\nimport { WebRequestShim, WebResponseDriver } from '../web/driver'\nimport { createLogger, describeError } from '../../core/logger'\n\nconst log = createLogger('H3WebRuntime')\n\nconst peerRequire = createRequire(import.meta.url)\n\nconst NOTFOUND_MW = Symbol('kickjs.h3web.notFoundMw')\nconst ERROR_MW = Symbol('kickjs.h3web.errorMw')\nconst ASSEMBLED = Symbol('kickjs.h3web.assembled')\nconst NOOP_NEXT = (): void => {}\n\n// Minimal structural surface of h3 v2 we depend on.\ninterface H3AppLike {\n on(method: string, path: string, handler: (event: H3EventLike) => unknown): unknown\n all(path: string, handler: (event: H3EventLike) => unknown): unknown\n use(...args: unknown[]): unknown\n fetch(request: Request): Promise<Response>\n}\ninterface H3EventLike {\n req: Request & { runtime?: { node?: { req: IncomingMessage; res: ServerResponse } } }\n url: URL\n context: { params?: Record<string, string> }\n}\n\ninterface H3v2Module {\n H3: new (config?: unknown) => H3AppLike\n toNodeHandler: (app: H3AppLike) => (req: IncomingMessage, res: ServerResponse) => void\n fromNodeHandler: (mw: unknown) => unknown\n}\n\nexport interface H3WebRuntimeOptions {\n /**\n * Pre-imported h3 v2 module. Two audiences:\n * - Edge/worker bundlers with no `createRequire` at runtime:\n * `h3WebRuntime({ h3: await import('h3') })`\n * - Tests running both h3 majors side by side via an npm alias.\n * Omitted → the peer is loaded via `createRequire` (node bootstrap path).\n */\n h3?: unknown\n}\n\n/** Resolve h3 (injected or peer-required) and fail fast unless it is v2. */\nfunction loadH3v2(injected?: unknown): H3v2Module {\n const mod = injected ?? peerRequire('h3')\n const m = mod as { __esModule?: boolean; default?: Record<string, unknown> } & Record<\n string,\n unknown\n >\n const resolved = (m && m.__esModule && m.default\n ? { ...m.default, ...m }\n : m) as unknown as H3v2Module\n if (typeof resolved.H3 !== 'function') {\n throw new Error(\n \"@forinda/kickjs: h3WebRuntime() requires h3 v2 (the web-standards line, npm dist-tag 'latest'), \" +\n 'but the installed h3 has no H3 class — that is the v1 line. ' +\n \"Either `pnpm add h3@latest` for this runtime, or keep h3 v1 and use h3Runtime() from '@forinda/kickjs/h3'.\",\n )\n }\n return resolved\n}\n\n/**\n * Bridge a connect-style terminal middleware (kick's notFound / error\n * handlers) onto the web driver pair, returning the produced Response.\n */\nasync function runConnectTerminal(\n mw: ConnectMiddleware | undefined,\n event: H3EventLike,\n err?: unknown,\n): Promise<Response> {\n const req = new WebRequestShim(event.req, event.url)\n const driver = new WebResponseDriver(event.req.signal)\n if (mw) {\n if (err !== undefined) {\n ;(mw as (e: unknown, rq: unknown, rs: unknown, n: () => void) => void)(\n err,\n req,\n driver,\n NOOP_NEXT,\n )\n } else {\n ;(mw as (rq: unknown, rs: unknown, n: () => void) => void)(req, driver, NOOP_NEXT)\n }\n }\n if (!driver.settled) {\n if (err !== undefined) {\n // The configured error handler ran but didn't settle the response\n // (or there wasn't one). Emitting a bare 500 with no log entry\n // makes this failure invisible on both sides — log it here, since\n // by definition nothing downstream will.\n log.error(err, `h3-web: error handler did not settle the response — ${describeError(err)}`)\n driver.status(500).json({\n error: 'Internal Server Error',\n ...(typeof process !== 'undefined' && process.env?.NODE_ENV === 'production'\n ? {}\n : { message: describeError(err) }),\n })\n } else {\n driver.status(404).json({ error: 'Not Found' })\n }\n }\n return driver.ready\n}\n\n// ── Connect middleware on the fetch path ─────────────────────────────────\n//\n// `app.fetch(request)` (edge, Bun, Deno, a serverless function) carries no\n// node req/res, and h3's `fromNodeHandler` throws without one. Connect\n// middleware there runs against the web driver pair instead: one request shim\n// per event (so `req.requestId` set by one middleware is seen by the next) and\n// one header bag per event that is merged into whatever Response comes back.\n// The merge is ours rather than h3's `event.res.headers` because h3 skips\n// prepared headers on non-2xx responses, and a 404/422/500 must still carry\n// helmet's headers and the request id, exactly as on node.\n\ninterface FetchConnectState {\n req: WebRequestShim\n headers: Headers\n}\n\nconst FETCH_CONNECT = new WeakMap<object, FetchConnectState>()\n\nfunction fetchConnectState(event: H3EventLike): FetchConnectState {\n let state = FETCH_CONNECT.get(event)\n if (!state) {\n state = { req: new WebRequestShim(event.req, event.url), headers: new Headers() }\n FETCH_CONNECT.set(event, state)\n }\n return state\n}\n\n/**\n * Add the headers connect middleware set to `response`. The response's own\n * headers win — a later `setHeader` overrides an earlier one on node too.\n * Consumed once, so every middleware layer can call it on the way out.\n */\nfunction withConnectHeaders(event: H3EventLike, response: Response): Response {\n const state = FETCH_CONNECT.get(event)\n if (!state) return response\n const missing = [...state.headers].filter(\n ([name]) => name === 'set-cookie' || !response.headers.has(name),\n )\n state.headers = new Headers()\n if (missing.length === 0) return response\n try {\n for (const [name, value] of missing) response.headers.append(name, value)\n return response\n } catch {\n // Immutable headers (a Response from fetch) — rebuild around the body.\n const headers = new Headers(response.headers)\n for (const [name, value] of missing) headers.append(name, value)\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n\n/**\n * The node `res` surface connect middleware uses, over the web driver:\n * headers go to the per-event bag, `statusCode`, and `finish`/`close`\n * emitted once the response exists.\n */\nclass FetchConnectResponse extends WebResponseDriver {\n private code = 200\n private listeners: Array<[string, (...args: unknown[]) => void]> = []\n\n constructor(\n private readonly prepared: Headers,\n signal: AbortSignal,\n ) {\n super(signal)\n }\n\n get statusCode(): number {\n return this.code\n }\n\n set statusCode(code: number) {\n this.status(code)\n }\n\n override status(code: number): this {\n this.code = code\n return super.status(code)\n }\n\n override writeHead(code: number, headers?: Record<string, string | number | string[]>): this {\n this.code = code\n return super.writeHead(code, headers)\n }\n\n override setHeader(name: string, value: unknown): this {\n if (Array.isArray(value)) {\n this.prepared.delete(name)\n for (const v of value) this.prepared.append(name, String(v))\n } else {\n this.prepared.set(name, String(value))\n }\n return this\n }\n\n getHeader(name: string): string | undefined {\n return this.prepared.get(name) ?? undefined\n }\n\n hasHeader(name: string): boolean {\n return this.prepared.has(name)\n }\n\n removeHeader(name: string): void {\n this.prepared.delete(name)\n }\n\n override once(event: string, listener: (...args: unknown[]) => void): this {\n this.listeners.push([event, listener])\n return this\n }\n\n on(event: string, listener: (...args: unknown[]) => void): this {\n return this.once(event, listener)\n }\n\n removeListener(event: string, listener: (...args: unknown[]) => void): this {\n this.listeners = this.listeners.filter(([e, l]) => e !== event || l !== listener)\n return this\n }\n\n off(event: string, listener: (...args: unknown[]) => void): this {\n return this.removeListener(event, listener)\n }\n\n /** The response exists (or the request failed): fire `finish` then `close`. */\n done(): void {\n for (const event of ['finish', 'close']) {\n for (const [e, listener] of this.listeners) if (e === event) listener()\n this.listeners = this.listeners.filter(([e]) => e !== event)\n }\n }\n}\n\nfunction runConnectOnFetch(\n mw: ConnectMiddleware,\n event: H3EventLike,\n next: () => unknown,\n): Promise<unknown> {\n const state = fetchConnectState(event)\n const res = new FetchConnectResponse(state.headers, event.req.signal)\n\n return new Promise((resolve, reject) => {\n const respond = (response: Response): void => {\n res.done()\n resolve(withConnectHeaders(event, response))\n }\n const fail = (err: unknown): void => {\n res.done()\n reject(err)\n }\n\n // The middleware answered itself (`res.end`, `res.json`, `writeHead`).\n void res.ready.then(respond)\n\n const connectNext = (err?: unknown): void => {\n if (err) return fail(err)\n if (res.settled) return\n // The route builds its request store from the inbound header; hand it the\n // id requestScope/requestId settled on so body, logs and header agree.\n const id = state.req.requestId\n if (id && event.req.headers.get('x-request-id') !== id) {\n try {\n event.req.headers.set('x-request-id', id)\n } catch {\n // Immutable request headers on some runtimes — the route mints its own id.\n }\n }\n let downstream: unknown\n try {\n // Synchronous, so downstream work starts inside any AsyncLocalStorage\n // scope this middleware opened (requestScopeMiddleware).\n downstream = next()\n } catch (e) {\n return fail(e)\n }\n Promise.resolve(downstream).then((value) => {\n if (value instanceof Response) return respond(value)\n res.done()\n resolve(value)\n }, fail)\n }\n\n try {\n ;(mw as (rq: unknown, rs: unknown, n: (err?: unknown) => void) => void)(\n state.req,\n res,\n connectNext,\n )\n } catch (err) {\n fail(err)\n }\n })\n}\n\n/**\n * Terminal catch-all AFTER all mounted routes: kick's own notFound handler\n * answers unmatched paths (h3 v2's default 404 never fires). rou3 ranks `/**`\n * below every concrete route, so registration time does not matter.\n */\nfunction assembleCatchAll(app: H3AppLike): void {\n const state = app as unknown as Record<symbol, unknown>\n if (state[ASSEMBLED]) return\n state[ASSEMBLED] = true\n app.all('/**', (event: H3EventLike) => {\n const mw = state[NOTFOUND_MW] as ConnectMiddleware | undefined\n return runConnectTerminal(mw, event)\n })\n}\n\n/**\n * The h3 v2 HTTP runtime. Pass to `bootstrap({ runtime: h3WebRuntime() })`.\n * Requires the h3 v2 peer; fails fast with guidance when v1 is installed.\n */\nexport function h3WebRuntime(options: H3WebRuntimeOptions = {}): HttpRuntime<H3AppLike> {\n const { H3, toNodeHandler, fromNodeHandler } = loadH3v2(options.h3)\n\n return {\n name: 'h3-web',\n\n createApp(_options: RuntimeAppOptions = {}): H3AppLike {\n const holder: { app?: H3AppLike } = {}\n const app = new H3({\n // Engine-level errors (middleware throws outside the kick pipeline)\n // dispatch to the kick error handler once setErrorHandler stashes it.\n onError: (error: unknown, event: H3EventLike) => {\n const mw = (holder.app as unknown as Record<symbol, ConnectMiddleware | undefined>)?.[\n ERROR_MW\n ]\n return runConnectTerminal(mw, event, error).then((response) =>\n withConnectHeaders(event, response),\n )\n },\n })\n holder.app = app\n return app\n },\n\n nodeHandler(app) {\n assembleCatchAll(app)\n const listener = toNodeHandler(app)\n return (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => {\n try {\n const result = listener(req, res) as unknown\n if (result && typeof (result as Promise<unknown>).then === 'function') {\n void (result as Promise<unknown>).catch((err) => next?.(err))\n }\n } catch (err) {\n next?.(err)\n }\n }\n },\n\n mountRoutes(app, table: RouteTable) {\n for (const { mountPath, routes } of table) {\n for (const entry of routes) {\n const url = joinPath(mountPath, entry.path)\n const hooks: WebRouteHooks = {\n onError: async (err, req, driver) => {\n const mw = (app as unknown as Record<symbol, ConnectMiddleware | undefined>)[ERROR_MW]\n if (!mw) return\n ;(mw as (e: unknown, rq: unknown, rs: unknown, n: () => void) => void)(\n err,\n req,\n driver,\n NOOP_NEXT,\n )\n },\n }\n const run = compileWebRoute(entry, hooks)\n app.on(entry.method, url, (event: H3EventLike) =>\n run({\n request: event.req,\n url: event.url,\n params: event.context.params ?? {},\n }),\n )\n }\n }\n },\n\n useConnect(app, mw: ConnectMiddleware, opts?: UseConnectOptions) {\n // Behind a node server (toNodeHandler) the event carries node req/res and\n // h3's own bridge runs the middleware unchanged. Called through\n // `app.fetch(request)` there is none and fromNodeHandler would throw, so\n // the middleware runs against the web driver pair instead.\n const viaNode = fromNodeHandler(mw) as (event: H3EventLike) => unknown\n const handler = (event: H3EventLike, next: () => unknown) =>\n event.req.runtime?.node?.res ? viaNode(event) : runConnectOnFetch(mw, event, next)\n if (opts?.path !== undefined) {\n // v2 `use` is exact-match; `/**` covers the subtree like v1's prefix.\n app.use(joinPath(String(opts.path), '/**'), handler)\n } else {\n app.use(handler)\n }\n },\n\n serveStatic(app, prefix, dir) {\n // serve-static bridged like any other connect middleware (node only).\n const serveStatic = peerRequire('serve-static')\n this.useConnect(app, serveStatic(dir) as ConnectMiddleware, { path: prefix })\n },\n\n setNotFound(app, mw: ConnectMiddleware) {\n ;(app as unknown as Record<symbol, ConnectMiddleware>)[NOTFOUND_MW] = mw\n // Here as well as in nodeHandler: an app only ever called through\n // `app.fetch` never reaches nodeHandler, and would answer h3's own 404.\n assembleCatchAll(app)\n },\n\n setErrorHandler(app, mw: ConnectMiddleware) {\n ;(app as unknown as Record<symbol, ConnectMiddleware>)[ERROR_MW] = mw\n },\n\n capabilities: {\n render: false,\n uploads: true,\n connectMiddleware: true,\n nativeBodyParsing: true,\n },\n }\n}\n\n/**\n * h3-web runtime engine types for the `kick/runtime` typegen augmentation —\n * mirrors {@link import('./h3').H3RuntimeTypes} for the v1 runtime.\n */\nexport interface H3WebRuntimeTypes {\n request: Request\n response: Response\n app: unknown\n}\n\n/** Join a mount prefix and a route path into one URL, collapsing slashes. */\nfunction joinPath(mountPath: string, path: string): string {\n const a = mountPath.endsWith('/') ? mountPath.slice(0, -1) : mountPath\n if (path === '/' || path === '') return a === '' ? '/' : a\n const b = path.startsWith('/') ? path : `/${path}`\n return `${a}${b}`\n}\n"],"mappings":"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"}
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
2
|
+
import { p as HttpRuntime } from "../../adapter-DK8WDonT.mjs";
|
|
3
3
|
//#region src/http/runtimes/h3.d.ts
|
|
4
4
|
interface H3AppLike {
|
|
5
5
|
use(arg1: unknown, arg2?: unknown): unknown;
|
|
6
6
|
handler: unknown;
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
9
|
-
* The h3 HTTP runtime. Pass to `bootstrap({ runtime: h3Runtime() })`.
|
|
10
|
-
* h3 parses bodies itself (`readBody`), so the Application skips its default
|
|
11
|
-
* `express.json()` (see `nativeBodyParsing`).
|
|
12
|
-
*/
|
|
13
8
|
export declare function h3Runtime(): HttpRuntime<H3AppLike>;
|
|
14
9
|
/**
|
|
15
10
|
* The h3 runtime's engine-native types — what the runtime-typed escape hatches
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"h3.d.mts","names":[],"sources":["../../../src/http/runtimes/h3.ts"],"mappings":";;;UA+CU;EACR,IAAI,eAAe;EACnB
|
|
1
|
+
{"version":3,"file":"h3.d.mts","names":[],"sources":["../../../src/http/runtimes/h3.ts"],"mappings":";;;UA+CU;EACR,IAAI,eAAe;EACnB;;wBAoSc,aAAa,YAAY;;;;;;;;KAiJpC;EAA0C,WAAW;IAAM;KAC3D;EAA4C,iBAAiB,wBAAwB;IACtF;iBAGa;EACf,SAAS;EACT,UAAU;EACV,KAAK"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @forinda/kickjs v8.
|
|
2
|
+
* @forinda/kickjs v8.7.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Felix Orinda
|
|
5
5
|
*
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import{HttpException}from"../../core/errors.mjs";import{a as createRequestStore,f as unsupportedMediaTypeError,n as applyHandlerResult,o as disposeRequestStore,t as publishMatchedRoute,u as classifyMediaType}from"../../runtime-
|
|
11
|
+
import{HttpException}from"../../core/errors.mjs";import{a as createRequestStore,f as unsupportedMediaTypeError,n as applyHandlerResult,o as disposeRequestStore,t as publishMatchedRoute,u as classifyMediaType}from"../../runtime-CV0SS-X5.mjs";import{c as requestStore,t as RequestContext}from"../../context-CHuuc9r6.mjs";import{validate}from"../middleware/validate.mjs";import{t as applyUploadConfig}from"../../upload-config-BBp5QfWQ.mjs";import"../middleware/upload.mjs";import{createRequire}from"node:module";const peerRequire=createRequire(import.meta.url),h3=()=>peerRequire(`h3`),NEXT=Symbol(`kickjs.h3.next`),ERROR_MW=Symbol(`kickjs.h3.errorMw`),ROUTER=Symbol(`kickjs.h3.router`),NOTFOUND_MW=Symbol(`kickjs.h3.notFoundMw`),ASSEMBLED=Symbol(`kickjs.h3.assembled`),NOOP_NEXT=()=>{},BODY_METHODS=new Set([`POST`,`PUT`,`PATCH`,`DELETE`]);var NodeResDriver=class{res;constructor(res){this.res=res}status(code){return this.res.statusCode=code,this}json(data){return!this.res.headersSent&&!this.res.getHeader(`content-type`)&&this.res.setHeader(`content-type`,`application/json; charset=utf-8`),this.res.end(JSON.stringify(data)),this}send(data){return this.res.end(data),this}type(contentType){return this.res.setHeader(`content-type`,contentType),this}setHeader(name,value){return this.res.setHeader(name,value),this}render(){throw Error(`ctx.render() is not supported on the h3 runtime (no view engine).`)}writeHead(statusCode,headers){return this.res.writeHead(statusCode,headers),this}flushHeaders(){return this.res.flushHeaders(),this}write(chunk){return this.res.write(chunk)}end(data){return this.res.end(data),this}once(event,listener){return this.res.once(event,listener),this}get headersSent(){return this.res.headersSent}};function resDriver(res){return new NodeResDriver(res)}const TRUST_PROXY=Symbol(`kickjs.h3.trustProxy`);function makeEventHandler(entry,trustProxy){let{getRouterParams,getQuery,readRawBody,readMultipartFormData}=h3(),upload=entry.meta.upload,validator=entry.meta.validation?validate(entry.meta.validation):void 0;return async event=>{let req=event.node.req,res=event.node.res;if(trustProxy&&!req.ip){let forwarded=req.headers[`x-forwarded-for`],first=(Array.isArray(forwarded)?forwarded[0]:forwarded)?.split(`,`)[0]?.trim();first&&(req.ip=first)}if(req.params=getRouterParams(event)??{},req.query=getQuery(event)??{},req.httpVersionMajor===2&&req.headers[`content-length`]===void 0&&BODY_METHODS.has(req.method??`GET`)&&(req.rawBody=await readStreamBuffer(req)),upload&&upload.mode!==`none`){let form=await readMultipartFormData(event).catch(()=>void 0),rawParts=[],fields={};for(let part of form??[])part.filename?rawParts.push({fieldname:part.name??``,filename:part.filename,mimetype:part.type??`application/octet-stream`,buffer:part.data}):part.name&&(fields[part.name]=part.data.toString(`utf-8`));let{file,files}=applyUploadConfig(rawParts,upload);req.file=file,req.files=files,req.body=fields}else if(BODY_METHODS.has(req.method??`GET`)){let{"content-length":length,"transfer-encoding":encoding}=req.headers;if(encoding!==void 0||length!==void 0&&length!==`0`||req.httpVersionMajor===2&&length!==`0`){let kind=classifyMediaType(req.headers[`content-type`]);if(kind!==`multipart`){let raw=await readRawBody(event,`utf8`);if(raw){if(kind===`unsupported`)throw unsupportedMediaTypeError(req.headers[`content-type`]);if(kind===`json`)try{req.body=JSON.parse(raw)}catch(err){throw HttpException.badRequest(err instanceof Error?err.message:`Request body could not be parsed`)}else req.body=kind===`urlencoded`?Object.fromEntries(new URLSearchParams(raw)):raw}}}}let headerId=req.headers[`x-request-id`],requestId=(Array.isArray(headerId)?headerId[0]:headerId)||req.requestId,store=createRequestStore(requestId);req.requestId=store.requestId,res.once(`close`,()=>disposeRequestStore(store)),await requestStore.run(store,async()=>{publishMatchedRoute(req,entry);let ctx=new RequestContext(req,res,NOOP_NEXT,resDriver(res));if(entry.earlyContributorRunner&&(await entry.earlyContributorRunner(ctx),res.writableEnded))return;validator&&await new Promise((resolve,reject)=>{validator(req,void 0,err=>err?reject(err):resolve())});for(let mw of entry.middlewares){let advanced=!1;if(await new Promise((resolve,reject)=>{Promise.resolve(mw(ctx,err=>{advanced=!0,err?reject(err):resolve()})).catch(reject)}),!advanced||res.writableEnded)return}if(entry.contributorRunner&&await entry.contributorRunner(ctx),res.writableEnded)return;let result=await entry.handler(ctx);!res.writableEnded&&!res.headersSent&&applyHandlerResult(ctx,result)})}}function unwrapH3Error(error){if(!(error instanceof Error))return error;let{cause}=error;return cause instanceof Error?cause:error}async function readStreamBuffer(req){let chunks=[];for await(let chunk of req)chunks.push(typeof chunk==`string`?Buffer.from(chunk):chunk);return Buffer.concat(chunks)}function h3Runtime(){return{name:`h3`,createApp(options={}){let{createApp}=h3(),app=createApp({onError:(error,event)=>{let res=event.node.res;if(res.writableEnded)return;let state=app,status=error?.statusCode,thrownByHandler=error?.cause instanceof Error;if(status===404&&!thrownByHandler){let next=event.node.req[NEXT];if(next){next();return}let nf=state[NOTFOUND_MW];if(nf){nf(event.node.req,resDriver(res),NOOP_NEXT);return}}let mw=state[ERROR_MW];mw&&mw(unwrapH3Error(error),event.node.req,resDriver(res),NOOP_NEXT)}});return app[TRUST_PROXY]=options.trustProxy,app},nodeHandler(app){let{toNodeListener}=h3(),state=app;state[ASSEMBLED]||(state[ASSEMBLED]=!0,state[ROUTER]&&app.use(state[ROUTER]));let listener=toNodeListener(app);return(req,res,next)=>{next&&(req[NEXT]=next),Promise.resolve(listener(req,res)).catch(err=>{next&&next(err)})}},mountRoutes(app,table){let{createRouter,eventHandler}=h3(),trustProxy=!!app[TRUST_PROXY],state=app;state[ROUTER]||(state[ROUTER]=createRouter());let router=state[ROUTER];for(let{mountPath,routes}of table)for(let entry of routes){let url=joinPath(mountPath,entry.path);router[entry.method.toLowerCase()](url,eventHandler(makeEventHandler(entry,trustProxy)))}},useConnect(app,mw,opts){let{fromNodeMiddleware}=h3(),handler=fromNodeMiddleware(mw);opts?.path===void 0?app.use(handler):app.use(opts.path,handler)},serveStatic(app,prefix,dir){let serveStatic=peerRequire(`serve-static`);this.useConnect(app,serveStatic(dir),{path:prefix})},setNotFound(app,mw){app[NOTFOUND_MW]=mw},setErrorHandler(app,mw){app[ERROR_MW]=mw},capabilities:{render:!1,uploads:!0,connectMiddleware:!0,nativeBodyParsing:!0,http2:!0}}}function joinPath(mountPath,path){let a=mountPath.endsWith(`/`)?mountPath.slice(0,-1):mountPath;return path===`/`||path===``?a===``?`/`:a:`${a}${path.startsWith(`/`)?path:`/${path}`}`}export{h3Runtime};
|
|
12
12
|
//# sourceMappingURL=h3.mjs.map
|