@niledatabase/express 5.2.0-alpha.0 → 5.2.0-alpha.3

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/index.d.mts CHANGED
@@ -1,11 +1,7 @@
1
- import { Server, ExtensionState } from '@niledatabase/server';
2
- import { Express, Request, Response as Response$1, NextFunction } from 'express';
1
+ import { Extension } from '@niledatabase/server';
2
+ import { Express, Request, Response, NextFunction } from 'express';
3
3
 
4
4
  declare function cleaner(val: string): string;
5
- declare const express: (app?: Express) => (instance: Server) => {
6
- id: string;
7
- onConfigure: () => void;
8
- onHandleRequest: (params: [Request, Response$1, NextFunction]) => Promise<void | Response | ExtensionState.onHandleRequest>;
9
- };
5
+ declare const express: (app: Express) => Extension<[Request, Response, NextFunction]>;
10
6
 
11
7
  export { cleaner, express };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,7 @@
1
- import { Server, ExtensionState } from '@niledatabase/server';
2
- import { Express, Request, Response as Response$1, NextFunction } from 'express';
1
+ import { Extension } from '@niledatabase/server';
2
+ import { Express, Request, Response, NextFunction } from 'express';
3
3
 
4
4
  declare function cleaner(val: string): string;
5
- declare const express: (app?: Express) => (instance: Server) => {
6
- id: string;
7
- onConfigure: () => void;
8
- onHandleRequest: (params: [Request, Response$1, NextFunction]) => Promise<void | Response | ExtensionState.onHandleRequest>;
9
- };
5
+ declare const express: (app: Express) => Extension<[Request, Response, NextFunction]>;
10
6
 
11
7
  export { cleaner, express };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var y=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var U=(s,r)=>{for(var n in r)y(s,n,{get:r[n],enumerable:!0})},b=(s,r,n,i)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of O(r))!C.call(s,o)&&o!==n&&y(s,o,{get:()=>r[o],enumerable:!(i=I(r,o))||i.enumerable});return s};var L=s=>b(y({},"__esModule",{value:!0}),s);var $={};U($,{cleaner:()=>E,express:()=>A});module.exports=L($);var S=require("async_hooks"),q=require("@niledatabase/server");function E(s){return s.replaceAll(/\{([^}]+)\}/g,":$1")}var w=new S.AsyncLocalStorage,A=s=>{let r=!1;return n=>{let{error:i,debug:o}=n.logger("[EXTENSION][express]"),m=e=>{w.getStore()?.set("context",e),n.withContext(e)};function T(e){s&&(o("routes configured"),s.get(e.paths.get,e.handlers.GET),s.post(e.paths.post,e.handlers.POST),s.put(e.paths.put,e.handlers.PUT),s.delete(e.paths.delete,e.handlers.DELETE))}return!r&&s&&(o("initializing express extension with middleware"),s.use((e,t,a)=>{w.run(new Map,()=>a())}),s.param("tenantId",(e,t,a,g)=>{o(`tenantId param set: ${g}`),m({tenantId:g,headers:new Headers(H(e.headers))}),a()}),s.use((e,t,a)=>{m({headers:new Headers(H(e.headers))}),a()})),r=!0,{id:"express",onConfigure:()=>{let{paths:e}=n,t={get:e.get.map(E),post:e.post.map(E),put:e.put.map(E),delete:e.delete.map(E)};o(`paths configured ${JSON.stringify(t)}`),n.paths=t,T(n)},onHandleRequest:async e=>{let[t,a,g]=e;o("handling response");let u="",f=t.method,c={method:f,headers:new Headers};if(t instanceof Request)u=t.url,c.headers=t.headers;else try{u=t.protocol+"://"+t.get("host")+t.originalUrl}catch{}try{new URL(u)}catch{throw new Error("Invalid URL \u2014 are you running Express?")}t.headers?.cookie&&c.headers.set("cookie",t.headers.cookie),["POST","PUT"].includes(f)&&t.body&&(c.body=JSON.stringify(t.body));let P=new Request(u,c);o(`[${f}] proxy: ${u} ${JSON.stringify(c)}`);let p,N={headers:new Headers(t.headers),tenantId:t.params?.tenantId||void 0};try{p=await n.withContext(N,async d=>await d.handlers[f](P,{disableExtensions:["express"]}))}catch(d){return i(d),g()}let l;try{l=await p.clone().json()}catch{l=await p.text()}let h={};return p.headers.forEach((d,x)=>{if(!["content-length","transfer-encoding"].includes(x.toLowerCase()))if(h[x]){let R=h[x];h[x]=Array.isArray(R)?[...R,d]:[R,d]}else h[x]=d}),a?(a.headersSent||(o("sending response"),a.status(p.status).set(h),typeof l=="string"?a.send(l):a.json(l??{})),q.ExtensionState.onHandleRequest):p}}}};function H(s){let r={};for(let[n,i]of Object.entries(s))typeof i=="string"?r[n]=i:Array.isArray(i)&&(r[n]=i.join(","));return r}0&&(module.exports={cleaner,express});
1
+ "use strict";var R=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var O=(s,e)=>{for(var a in e)R(s,a,{get:e[a],enumerable:!0})},P=(s,e,a,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of T(e))!I.call(s,r)&&r!==a&&R(s,r,{get:()=>e[r],enumerable:!(n=N(e,r))||n.enumerable});return s};var b=s=>P(R({},"__esModule",{value:!0}),s);var U={};O(U,{cleaner:()=>f,express:()=>C});module.exports=b(U);var q=require("async_hooks"),S=require("@niledatabase/server");function f(s){return s.replaceAll(/\{([^}]+)\}/g,":$1")}var w=new q.AsyncLocalStorage,C=s=>{let e;return()=>({id:"express",onConfigure:a=>{e=a;let{debug:n}=e.logger("[EXTENSION][express]"),{paths:r}=e,t={get:r.get.map(f),post:r.post.map(f),put:r.put.map(f),delete:r.delete.map(f)};if(n(`paths configured ${JSON.stringify(t)}`),e.paths=t,s){n("initializing express extension with middleware"),s.use((o,c,i)=>{w.run(new Map,()=>i())});let d=o=>{w.getStore()?.set("context",o),e.withContext(o)};s.param("tenantId",(o,c,i,E)=>{n(`tenantId param set: ${E}`),d({tenantId:E,headers:new Headers(m(o.headers))}),i()}),s.use((o,c,i)=>{d({headers:new Headers(m(o.headers))}),i()}),n("routes configured");let u=e.handlers;s.get(t.get,u.GET),s.post(t.post,u.POST),s.put(t.put,u.PUT),s.delete(t.delete,u.DELETE)}},onHandleRequest:async a=>{if(!e)return;let{error:n,debug:r}=e.logger("[EXTENSION][express]");if(!a)return;let[t,d,u]=a;r("handling response");let o="",c=t.method,i={method:c,headers:new Headers};if(t instanceof Request)o=t.url,i.headers=t.headers;else try{o=t.protocol+"://"+t.get("host")+t.originalUrl}catch{}try{new URL(o)}catch{throw new Error("Invalid URL \u2014 are you running Express?")}t.headers?.cookie&&i.headers.set("cookie",t.headers.cookie),["POST","PUT"].includes(c)&&t.body&&(i.body=JSON.stringify(t.body));let E=new Request(o,i);r(`[${c}] proxy: ${o} ${JSON.stringify(i)}`);let x,H={headers:new Headers(t.headers),tenantId:t.params?.tenantId||void 0};try{x=await e.withContext(H,async p=>await p.handlers[c](E,{disableExtensions:["express"]}))}catch(p){return n(p),u()}let l;try{l=await x.clone().json()}catch{l=await x.text()}let g={};return x.headers.forEach((p,h)=>{if(!["content-length","transfer-encoding"].includes(h.toLowerCase()))if(g[h]){let y=g[h];g[h]=Array.isArray(y)?[...y,p]:[y,p]}else g[h]=p}),d?(d.headersSent||(r("sending response"),d.status(x.status).set(g),typeof l=="string"?d.send(l):d.json(l??{})),S.ExtensionState.onHandleRequest):x}})};function m(s){let e={};for(let[a,n]of Object.entries(s))typeof n=="string"?e[a]=n:Array.isArray(n)&&(e[a]=n.join(","));return e}0&&(module.exports={cleaner,express});
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'async_hooks';\nimport { IncomingHttpHeaders } from 'http';\n\nimport { PartialContext, ExtensionState, Server } from '@niledatabase/server';\nimport type {\n Request as ExpressRequest,\n Response as ExpressResponse,\n NextFunction,\n Express,\n} from 'express';\n\nexport function cleaner(val: string) {\n return val.replaceAll(/\\{([^}]+)\\}/g, ':$1');\n}\n\ntype ExpressRouteHandler = (\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction\n) => void | Promise<void>;\n\ntype ExpressRouteFunctions = {\n GET: ExpressRouteHandler;\n POST: ExpressRouteHandler;\n PUT: ExpressRouteHandler;\n DELETE: ExpressRouteHandler;\n};\n\ntype NileWithExpress = Server & {\n handlers: ExpressRouteFunctions;\n};\n\n// AsyncLocalStorage to persist context per request\nconst contextStore = new AsyncLocalStorage<Map<string, unknown>>();\n\n// Express extension factory\nexport const express = (app?: Express) => {\n let init = false;\n\n return (instance: Server) => {\n const { error, debug } = instance.logger('[EXTENSION][express]');\n\n // Internal context helpers - I think we delete this now?\n const setRequestContext = (context: PartialContext) => {\n contextStore.getStore()?.set('context', context);\n\n instance.withContext(context);\n };\n\n function doConfigure(server: NileWithExpress) {\n if (app) {\n debug('routes configured');\n app.get(server.paths.get, server.handlers.GET);\n app.post(server.paths.post, server.handlers.POST);\n app.put(server.paths.put, server.handlers.PUT);\n app.delete(server.paths.delete, server.handlers.DELETE);\n }\n }\n\n if (!init && app) {\n debug('initializing express extension with middleware');\n\n app.use((req, res, next) => {\n contextStore.run(new Map(), () => next());\n });\n\n app.param('tenantId', (req, res, next, tenantId) => {\n debug(`tenantId param set: ${tenantId}`);\n setRequestContext({\n tenantId,\n headers: new Headers(normalizeHeaders(req.headers)),\n });\n next();\n });\n\n app.use((req, res, next) => {\n setRequestContext({\n headers: new Headers(normalizeHeaders(req.headers)),\n });\n next();\n });\n }\n\n init = true;\n\n return {\n id: 'express',\n\n onConfigure: () => {\n const { paths: rawPaths } = instance;\n const paths = {\n get: rawPaths.get.map(cleaner),\n post: rawPaths.post.map(cleaner),\n put: rawPaths.put.map(cleaner),\n delete: rawPaths.delete.map(cleaner),\n };\n debug(`paths configured ${JSON.stringify(paths)}`);\n instance.paths = paths;\n doConfigure(instance as NileWithExpress);\n },\n\n onHandleRequest: async (\n params: [ExpressRequest, ExpressResponse, NextFunction]\n ) => {\n // there are two cases here.\n // One is a server side request (normal Request/Response, the other is GET/POST/PUT/DELETE)\n const [req, res, next] = params;\n debug('handling response');\n\n let reqUrl = '';\n const method = req.method;\n const init: RequestInit = { method, headers: new Headers() };\n if (req instanceof Request) {\n reqUrl = req.url;\n init.headers = req.headers;\n } else {\n try {\n reqUrl = req.protocol + '://' + req.get('host') + req.originalUrl;\n } catch {\n // handled later\n }\n }\n\n try {\n new URL(reqUrl);\n } catch {\n throw new Error('Invalid URL — are you running Express?');\n }\n\n if (req.headers?.cookie) {\n (init.headers as Headers).set('cookie', req.headers.cookie);\n }\n\n if (['POST', 'PUT'].includes(method) && req.body) {\n init.body = JSON.stringify(req.body);\n }\n\n const proxyRequest = new Request(reqUrl, init);\n debug(`[${method}] proxy: ${reqUrl} ${JSON.stringify(init)}`);\n\n let response: Response;\n const context = {\n headers: new Headers(req.headers as HeadersInit),\n tenantId: req.params?.tenantId || undefined,\n };\n try {\n response = await instance.withContext(context, async (ctx) => {\n return (await ctx.handlers[\n method as 'GET' | 'POST' | 'PUT' | 'DELETE'\n ](proxyRequest, { disableExtensions: ['express'] })) as Response;\n });\n } catch (e) {\n error(e);\n return next();\n }\n\n let body;\n try {\n body = await response.clone().json();\n } catch {\n body = await response.text();\n }\n\n const newHeaders: Record<string, string | string[]> = {};\n response.headers.forEach((value, key) => {\n if (\n !['content-length', 'transfer-encoding'].includes(key.toLowerCase())\n ) {\n if (newHeaders[key]) {\n const prev = newHeaders[key];\n newHeaders[key] = Array.isArray(prev)\n ? [...prev, value]\n : [prev, value];\n } else {\n newHeaders[key] = value;\n }\n }\n });\n\n if (!res) {\n return response;\n }\n\n if (!res.headersSent) {\n debug('sending response');\n res.status(response.status).set(newHeaders);\n typeof body === 'string' ? res.send(body) : res.json(body ?? {});\n }\n\n return ExtensionState.onHandleRequest;\n },\n };\n };\n};\n\nfunction normalizeHeaders(headers: IncomingHttpHeaders): HeadersInit {\n const normalized: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (typeof value === 'string') {\n normalized[key] = value;\n } else if (Array.isArray(value)) {\n normalized[key] = value.join(','); // Join multi-values with commas\n }\n }\n return normalized;\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,YAAAC,IAAA,eAAAC,EAAAJ,GAAA,IAAAK,EAAkC,uBAGlCC,EAAuD,gCAQhD,SAASJ,EAAQK,EAAa,CACnC,OAAOA,EAAI,WAAW,eAAgB,KAAK,CAC7C,CAoBA,IAAMC,EAAe,IAAI,oBAGZL,EAAWM,GAAkB,CACxC,IAAIC,EAAO,GAEX,OAAQC,GAAqB,CAC3B,GAAM,CAAE,MAAAC,EAAO,MAAAC,CAAM,EAAIF,EAAS,OAAO,sBAAsB,EAGzDG,EAAqBC,GAA4B,CACrDP,EAAa,SAAS,GAAG,IAAI,UAAWO,CAAO,EAE/CJ,EAAS,YAAYI,CAAO,CAC9B,EAEA,SAASC,EAAYC,EAAyB,CACxCR,IACFI,EAAM,mBAAmB,EACzBJ,EAAI,IAAIQ,EAAO,MAAM,IAAKA,EAAO,SAAS,GAAG,EAC7CR,EAAI,KAAKQ,EAAO,MAAM,KAAMA,EAAO,SAAS,IAAI,EAChDR,EAAI,IAAIQ,EAAO,MAAM,IAAKA,EAAO,SAAS,GAAG,EAC7CR,EAAI,OAAOQ,EAAO,MAAM,OAAQA,EAAO,SAAS,MAAM,EAE1D,CAEA,MAAI,CAACP,GAAQD,IACXI,EAAM,gDAAgD,EAEtDJ,EAAI,IAAI,CAACS,EAAKC,EAAKC,IAAS,CAC1BZ,EAAa,IAAI,IAAI,IAAO,IAAMY,EAAK,CAAC,CAC1C,CAAC,EAEDX,EAAI,MAAM,WAAY,CAACS,EAAKC,EAAKC,EAAMC,IAAa,CAClDR,EAAM,uBAAuBQ,CAAQ,EAAE,EACvCP,EAAkB,CAChB,SAAAO,EACA,QAAS,IAAI,QAAQC,EAAiBJ,EAAI,OAAO,CAAC,CACpD,CAAC,EACDE,EAAK,CACP,CAAC,EAEDX,EAAI,IAAI,CAACS,EAAKC,EAAKC,IAAS,CAC1BN,EAAkB,CAChB,QAAS,IAAI,QAAQQ,EAAiBJ,EAAI,OAAO,CAAC,CACpD,CAAC,EACDE,EAAK,CACP,CAAC,GAGHV,EAAO,GAEA,CACL,GAAI,UAEJ,YAAa,IAAM,CACjB,GAAM,CAAE,MAAOa,CAAS,EAAIZ,EACtBa,EAAQ,CACZ,IAAKD,EAAS,IAAI,IAAIrB,CAAO,EAC7B,KAAMqB,EAAS,KAAK,IAAIrB,CAAO,EAC/B,IAAKqB,EAAS,IAAI,IAAIrB,CAAO,EAC7B,OAAQqB,EAAS,OAAO,IAAIrB,CAAO,CACrC,EACAW,EAAM,oBAAoB,KAAK,UAAUW,CAAK,CAAC,EAAE,EACjDb,EAAS,MAAQa,EACjBR,EAAYL,CAA2B,CACzC,EAEA,gBAAiB,MACfc,GACG,CAGH,GAAM,CAACP,EAAKC,EAAKC,CAAI,EAAIK,EACzBZ,EAAM,mBAAmB,EAEzB,IAAIa,EAAS,GACPC,EAAST,EAAI,OACbR,EAAoB,CAAE,OAAAiB,EAAQ,QAAS,IAAI,OAAU,EAC3D,GAAIT,aAAe,QACjBQ,EAASR,EAAI,IACbR,EAAK,QAAUQ,EAAI,YAEnB,IAAI,CACFQ,EAASR,EAAI,SAAW,MAAQA,EAAI,IAAI,MAAM,EAAIA,EAAI,WACxD,MAAQ,CAER,CAGF,GAAI,CACF,IAAI,IAAIQ,CAAM,CAChB,MAAQ,CACN,MAAM,IAAI,MAAM,6CAAwC,CAC1D,CAEIR,EAAI,SAAS,QACdR,EAAK,QAAoB,IAAI,SAAUQ,EAAI,QAAQ,MAAM,EAGxD,CAAC,OAAQ,KAAK,EAAE,SAASS,CAAM,GAAKT,EAAI,OAC1CR,EAAK,KAAO,KAAK,UAAUQ,EAAI,IAAI,GAGrC,IAAMU,EAAe,IAAI,QAAQF,EAAQhB,CAAI,EAC7CG,EAAM,IAAIc,CAAM,YAAYD,CAAM,IAAI,KAAK,UAAUhB,CAAI,CAAC,EAAE,EAE5D,IAAImB,EACEd,EAAU,CACd,QAAS,IAAI,QAAQG,EAAI,OAAsB,EAC/C,SAAUA,EAAI,QAAQ,UAAY,MACpC,EACA,GAAI,CACFW,EAAW,MAAMlB,EAAS,YAAYI,EAAS,MAAOe,GAC5C,MAAMA,EAAI,SAChBH,CACF,EAAEC,EAAc,CAAE,kBAAmB,CAAC,SAAS,CAAE,CAAC,CACnD,CACH,OAASG,EAAG,CACV,OAAAnB,EAAMmB,CAAC,EACAX,EAAK,CACd,CAEA,IAAIY,EACJ,GAAI,CACFA,EAAO,MAAMH,EAAS,MAAM,EAAE,KAAK,CACrC,MAAQ,CACNG,EAAO,MAAMH,EAAS,KAAK,CAC7B,CAEA,IAAMI,EAAgD,CAAC,EAgBvD,OAfAJ,EAAS,QAAQ,QAAQ,CAACK,EAAOC,IAAQ,CACvC,GACE,CAAC,CAAC,iBAAkB,mBAAmB,EAAE,SAASA,EAAI,YAAY,CAAC,EAEnE,GAAIF,EAAWE,CAAG,EAAG,CACnB,IAAMC,EAAOH,EAAWE,CAAG,EAC3BF,EAAWE,CAAG,EAAI,MAAM,QAAQC,CAAI,EAChC,CAAC,GAAGA,EAAMF,CAAK,EACf,CAACE,EAAMF,CAAK,CAClB,MACED,EAAWE,CAAG,EAAID,CAGxB,CAAC,EAEIf,GAIAA,EAAI,cACPN,EAAM,kBAAkB,EACxBM,EAAI,OAAOU,EAAS,MAAM,EAAE,IAAII,CAAU,EAC1C,OAAOD,GAAS,SAAWb,EAAI,KAAKa,CAAI,EAAIb,EAAI,KAAKa,GAAQ,CAAC,CAAC,GAG1D,iBAAe,iBATbH,CAUX,CACF,CACF,CACF,EAEA,SAASP,EAAiBe,EAA2C,CACnE,IAAMC,EAAqC,CAAC,EAC5C,OAAW,CAACH,EAAKD,CAAK,IAAK,OAAO,QAAQG,CAAO,EAC3C,OAAOH,GAAU,SACnBI,EAAWH,CAAG,EAAID,EACT,MAAM,QAAQA,CAAK,IAC5BI,EAAWH,CAAG,EAAID,EAAM,KAAK,GAAG,GAGpC,OAAOI,CACT","names":["index_exports","__export","cleaner","express","__toCommonJS","import_async_hooks","import_server","val","contextStore","app","init","instance","error","debug","setRequestContext","context","doConfigure","server","req","res","next","tenantId","normalizeHeaders","rawPaths","paths","params","reqUrl","method","proxyRequest","response","ctx","e","body","newHeaders","value","key","prev","headers","normalized"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'async_hooks';\nimport { IncomingHttpHeaders } from 'http';\n\nimport {\n ExtensionState,\n Server,\n Extension,\n PartialContext,\n} from '@niledatabase/server';\nimport type {\n Request as ExpressRequest,\n Response as ExpressResponse,\n NextFunction,\n Express,\n} from 'express';\n\nexport function cleaner(val: string) {\n return val.replaceAll(/\\{([^}]+)\\}/g, ':$1');\n}\n\nconst contextStore = new AsyncLocalStorage<Map<string, unknown>>();\n\nconst express = (\n app: Express\n): Extension<[ExpressRequest, ExpressResponse, NextFunction]> => {\n let instance: Server;\n\n return () => ({\n id: 'express',\n\n onConfigure: (server: Server) => {\n instance = server;\n const { debug } = instance.logger('[EXTENSION][express]');\n const { paths: rawPaths } = instance;\n const paths = {\n get: rawPaths.get.map(cleaner),\n post: rawPaths.post.map(cleaner),\n put: rawPaths.put.map(cleaner),\n delete: rawPaths.delete.map(cleaner),\n };\n debug(`paths configured ${JSON.stringify(paths)}`);\n instance.paths = paths;\n\n if (app) {\n debug('initializing express extension with middleware');\n\n app.use(\n (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {\n contextStore.run(new Map(), () => next());\n }\n );\n\n const setRequestContext = (context: PartialContext) => {\n contextStore.getStore()?.set('context', context);\n instance.withContext(context);\n };\n\n app.param(\n 'tenantId',\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (req: any, res: any, next: any, tenantId: string) => {\n debug(`tenantId param set: ${tenantId}`);\n setRequestContext({\n tenantId,\n headers: new Headers(normalizeHeaders(req.headers)),\n });\n next();\n }\n );\n\n app.use(\n (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {\n setRequestContext({\n headers: new Headers(normalizeHeaders(req.headers)),\n });\n next();\n }\n );\n\n debug('routes configured');\n // We cast handlers to any because Server handlers are Fetch-based but we mount them on Express\n // The Express extension assumes the Server handlers can handle the request or that\n // they are replaced/wrapped. In the original implementation, this was just passed through.\n // However, standard Nile handlers expect (req: Request). Express passes (req, res, next).\n // If this works, it's because Nile handlers might be handling the args or ignoring extra args?\n // Actually, the original code had 'server.handlers.GET' passed to 'app.get'.\n // We will do the same.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const handlers = instance.handlers as any;\n app.get(paths.get, handlers.GET);\n app.post(paths.post, handlers.POST);\n app.put(paths.put, handlers.PUT);\n app.delete(paths.delete, handlers.DELETE);\n }\n },\n\n onHandleRequest: async (\n params?: [ExpressRequest, ExpressResponse, NextFunction]\n ) => {\n if (!instance) {\n return;\n }\n const { error, debug } = instance.logger('[EXTENSION][express]');\n\n if (!params) {\n return;\n }\n const [req, res, next] = params;\n debug('handling response');\n\n let reqUrl = '';\n const method = req.method;\n const init: RequestInit = { method, headers: new Headers() };\n if (req instanceof Request) {\n reqUrl = req.url;\n init.headers = req.headers;\n } else {\n try {\n reqUrl = req.protocol + '://' + req.get('host') + req.originalUrl;\n } catch {\n // handled later\n }\n }\n\n try {\n new URL(reqUrl);\n } catch {\n throw new Error('Invalid URL — are you running Express?');\n }\n\n if (req.headers?.cookie) {\n (init.headers as Headers).set('cookie', req.headers.cookie);\n }\n\n if (['POST', 'PUT'].includes(method) && req.body) {\n init.body = JSON.stringify(req.body);\n }\n\n const proxyRequest = new Request(reqUrl, init);\n debug(`[${method}] proxy: ${reqUrl} ${JSON.stringify(init)}`);\n\n let response: Response;\n const context = {\n headers: new Headers(req.headers as HeadersInit),\n tenantId: req.params?.tenantId || undefined,\n };\n try {\n response = await instance.withContext(context, async (ctx) => {\n return (await ctx.handlers[\n method as 'GET' | 'POST' | 'PUT' | 'DELETE'\n ](proxyRequest, { disableExtensions: ['express'] })) as Response;\n });\n } catch (e) {\n error(e);\n return next();\n }\n\n let body;\n try {\n body = await response.clone().json();\n } catch {\n body = await response.text();\n }\n\n const newHeaders: Record<string, string | string[]> = {};\n response.headers.forEach((value, key) => {\n if (\n !['content-length', 'transfer-encoding'].includes(key.toLowerCase())\n ) {\n if (newHeaders[key]) {\n const prev = newHeaders[key];\n newHeaders[key] = Array.isArray(prev)\n ? [...prev, value]\n : [prev, value];\n } else {\n newHeaders[key] = value;\n }\n }\n });\n\n if (!res) {\n return response;\n }\n\n if (!res.headersSent) {\n debug('sending response');\n res.status(response.status).set(newHeaders);\n typeof body === 'string' ? res.send(body) : res.json(body ?? {});\n }\n\n return ExtensionState.onHandleRequest;\n },\n });\n};\nexport { express };\n\nfunction normalizeHeaders(headers: IncomingHttpHeaders): HeadersInit {\n const normalized: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (typeof value === 'string') {\n normalized[key] = value;\n } else if (Array.isArray(value)) {\n normalized[key] = value.join(',');\n }\n }\n return normalized;\n}\n"],"mappings":"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","names":["index_exports","__export","cleaner","express","__toCommonJS","import_async_hooks","import_server","val","contextStore","app","instance","server","debug","rawPaths","paths","req","res","next","setRequestContext","context","tenantId","normalizeHeaders","handlers","params","error","reqUrl","method","init","proxyRequest","response","ctx","e","body","newHeaders","value","key","prev","normalizeHeaders","headers","normalized","key","value"]}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{AsyncLocalStorage as T}from"async_hooks";import{ExtensionState as P}from"@niledatabase/server";function f(t){return t.replaceAll(/\{([^}]+)\}/g,":$1")}var m=new T,U=t=>{let i=!1;return n=>{let{error:d,debug:o}=n.logger("[EXTENSION][express]"),y=e=>{m.getStore()?.set("context",e),n.withContext(e)};function H(e){t&&(o("routes configured"),t.get(e.paths.get,e.handlers.GET),t.post(e.paths.post,e.handlers.POST),t.put(e.paths.put,e.handlers.PUT),t.delete(e.paths.delete,e.handlers.DELETE))}return!i&&t&&(o("initializing express extension with middleware"),t.use((e,s,r)=>{m.run(new Map,()=>r())}),t.param("tenantId",(e,s,r,E)=>{o(`tenantId param set: ${E}`),y({tenantId:E,headers:new Headers(w(e.headers))}),r()}),t.use((e,s,r)=>{y({headers:new Headers(w(e.headers))}),r()})),i=!0,{id:"express",onConfigure:()=>{let{paths:e}=n,s={get:e.get.map(f),post:e.post.map(f),put:e.put.map(f),delete:e.delete.map(f)};o(`paths configured ${JSON.stringify(s)}`),n.paths=s,H(n)},onHandleRequest:async e=>{let[s,r,E]=e;o("handling response");let u="",g=s.method,c={method:g,headers:new Headers};if(s instanceof Request)u=s.url,c.headers=s.headers;else try{u=s.protocol+"://"+s.get("host")+s.originalUrl}catch{}try{new URL(u)}catch{throw new Error("Invalid URL \u2014 are you running Express?")}s.headers?.cookie&&c.headers.set("cookie",s.headers.cookie),["POST","PUT"].includes(g)&&s.body&&(c.body=JSON.stringify(s.body));let S=new Request(u,c);o(`[${g}] proxy: ${u} ${JSON.stringify(c)}`);let p,q={headers:new Headers(s.headers),tenantId:s.params?.tenantId||void 0};try{p=await n.withContext(q,async a=>await a.handlers[g](S,{disableExtensions:["express"]}))}catch(a){return d(a),E()}let l;try{l=await p.clone().json()}catch{l=await p.text()}let h={};return p.headers.forEach((a,x)=>{if(!["content-length","transfer-encoding"].includes(x.toLowerCase()))if(h[x]){let R=h[x];h[x]=Array.isArray(R)?[...R,a]:[R,a]}else h[x]=a}),r?(r.headersSent||(o("sending response"),r.status(p.status).set(h),typeof l=="string"?r.send(l):r.json(l??{})),P.onHandleRequest):p}}}};function w(t){let i={};for(let[n,d]of Object.entries(t))typeof d=="string"?i[n]=d:Array.isArray(d)&&(i[n]=d.join(","));return i}export{f as cleaner,U as express};
1
+ import{AsyncLocalStorage as q}from"async_hooks";import{ExtensionState as S}from"@niledatabase/server";function E(n){return n.replaceAll(/\{([^}]+)\}/g,":$1")}var R=new q,P=n=>{let s;return()=>({id:"express",onConfigure:d=>{s=d;let{debug:r}=s.logger("[EXTENSION][express]"),{paths:a}=s,e={get:a.get.map(E),post:a.post.map(E),put:a.put.map(E),delete:a.delete.map(E)};if(r(`paths configured ${JSON.stringify(e)}`),s.paths=e,n){r("initializing express extension with middleware"),n.use((t,c,o)=>{R.run(new Map,()=>o())});let i=t=>{R.getStore()?.set("context",t),s.withContext(t)};n.param("tenantId",(t,c,o,f)=>{r(`tenantId param set: ${f}`),i({tenantId:f,headers:new Headers(w(t.headers))}),o()}),n.use((t,c,o)=>{i({headers:new Headers(w(t.headers))}),o()}),r("routes configured");let u=s.handlers;n.get(e.get,u.GET),n.post(e.post,u.POST),n.put(e.put,u.PUT),n.delete(e.delete,u.DELETE)}},onHandleRequest:async d=>{if(!s)return;let{error:r,debug:a}=s.logger("[EXTENSION][express]");if(!d)return;let[e,i,u]=d;a("handling response");let t="",c=e.method,o={method:c,headers:new Headers};if(e instanceof Request)t=e.url,o.headers=e.headers;else try{t=e.protocol+"://"+e.get("host")+e.originalUrl}catch{}try{new URL(t)}catch{throw new Error("Invalid URL \u2014 are you running Express?")}e.headers?.cookie&&o.headers.set("cookie",e.headers.cookie),["POST","PUT"].includes(c)&&e.body&&(o.body=JSON.stringify(e.body));let f=new Request(t,o);a(`[${c}] proxy: ${t} ${JSON.stringify(o)}`);let x,m={headers:new Headers(e.headers),tenantId:e.params?.tenantId||void 0};try{x=await s.withContext(m,async p=>await p.handlers[c](f,{disableExtensions:["express"]}))}catch(p){return r(p),u()}let l;try{l=await x.clone().json()}catch{l=await x.text()}let g={};return x.headers.forEach((p,h)=>{if(!["content-length","transfer-encoding"].includes(h.toLowerCase()))if(g[h]){let y=g[h];g[h]=Array.isArray(y)?[...y,p]:[y,p]}else g[h]=p}),i?(i.headersSent||(a("sending response"),i.status(x.status).set(g),typeof l=="string"?i.send(l):i.json(l??{})),S.onHandleRequest):x}})};function w(n){let s={};for(let[d,r]of Object.entries(n))typeof r=="string"?s[d]=r:Array.isArray(r)&&(s[d]=r.join(","));return s}export{E as cleaner,P as express};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'async_hooks';\nimport { IncomingHttpHeaders } from 'http';\n\nimport { PartialContext, ExtensionState, Server } from '@niledatabase/server';\nimport type {\n Request as ExpressRequest,\n Response as ExpressResponse,\n NextFunction,\n Express,\n} from 'express';\n\nexport function cleaner(val: string) {\n return val.replaceAll(/\\{([^}]+)\\}/g, ':$1');\n}\n\ntype ExpressRouteHandler = (\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction\n) => void | Promise<void>;\n\ntype ExpressRouteFunctions = {\n GET: ExpressRouteHandler;\n POST: ExpressRouteHandler;\n PUT: ExpressRouteHandler;\n DELETE: ExpressRouteHandler;\n};\n\ntype NileWithExpress = Server & {\n handlers: ExpressRouteFunctions;\n};\n\n// AsyncLocalStorage to persist context per request\nconst contextStore = new AsyncLocalStorage<Map<string, unknown>>();\n\n// Express extension factory\nexport const express = (app?: Express) => {\n let init = false;\n\n return (instance: Server) => {\n const { error, debug } = instance.logger('[EXTENSION][express]');\n\n // Internal context helpers - I think we delete this now?\n const setRequestContext = (context: PartialContext) => {\n contextStore.getStore()?.set('context', context);\n\n instance.withContext(context);\n };\n\n function doConfigure(server: NileWithExpress) {\n if (app) {\n debug('routes configured');\n app.get(server.paths.get, server.handlers.GET);\n app.post(server.paths.post, server.handlers.POST);\n app.put(server.paths.put, server.handlers.PUT);\n app.delete(server.paths.delete, server.handlers.DELETE);\n }\n }\n\n if (!init && app) {\n debug('initializing express extension with middleware');\n\n app.use((req, res, next) => {\n contextStore.run(new Map(), () => next());\n });\n\n app.param('tenantId', (req, res, next, tenantId) => {\n debug(`tenantId param set: ${tenantId}`);\n setRequestContext({\n tenantId,\n headers: new Headers(normalizeHeaders(req.headers)),\n });\n next();\n });\n\n app.use((req, res, next) => {\n setRequestContext({\n headers: new Headers(normalizeHeaders(req.headers)),\n });\n next();\n });\n }\n\n init = true;\n\n return {\n id: 'express',\n\n onConfigure: () => {\n const { paths: rawPaths } = instance;\n const paths = {\n get: rawPaths.get.map(cleaner),\n post: rawPaths.post.map(cleaner),\n put: rawPaths.put.map(cleaner),\n delete: rawPaths.delete.map(cleaner),\n };\n debug(`paths configured ${JSON.stringify(paths)}`);\n instance.paths = paths;\n doConfigure(instance as NileWithExpress);\n },\n\n onHandleRequest: async (\n params: [ExpressRequest, ExpressResponse, NextFunction]\n ) => {\n // there are two cases here.\n // One is a server side request (normal Request/Response, the other is GET/POST/PUT/DELETE)\n const [req, res, next] = params;\n debug('handling response');\n\n let reqUrl = '';\n const method = req.method;\n const init: RequestInit = { method, headers: new Headers() };\n if (req instanceof Request) {\n reqUrl = req.url;\n init.headers = req.headers;\n } else {\n try {\n reqUrl = req.protocol + '://' + req.get('host') + req.originalUrl;\n } catch {\n // handled later\n }\n }\n\n try {\n new URL(reqUrl);\n } catch {\n throw new Error('Invalid URL — are you running Express?');\n }\n\n if (req.headers?.cookie) {\n (init.headers as Headers).set('cookie', req.headers.cookie);\n }\n\n if (['POST', 'PUT'].includes(method) && req.body) {\n init.body = JSON.stringify(req.body);\n }\n\n const proxyRequest = new Request(reqUrl, init);\n debug(`[${method}] proxy: ${reqUrl} ${JSON.stringify(init)}`);\n\n let response: Response;\n const context = {\n headers: new Headers(req.headers as HeadersInit),\n tenantId: req.params?.tenantId || undefined,\n };\n try {\n response = await instance.withContext(context, async (ctx) => {\n return (await ctx.handlers[\n method as 'GET' | 'POST' | 'PUT' | 'DELETE'\n ](proxyRequest, { disableExtensions: ['express'] })) as Response;\n });\n } catch (e) {\n error(e);\n return next();\n }\n\n let body;\n try {\n body = await response.clone().json();\n } catch {\n body = await response.text();\n }\n\n const newHeaders: Record<string, string | string[]> = {};\n response.headers.forEach((value, key) => {\n if (\n !['content-length', 'transfer-encoding'].includes(key.toLowerCase())\n ) {\n if (newHeaders[key]) {\n const prev = newHeaders[key];\n newHeaders[key] = Array.isArray(prev)\n ? [...prev, value]\n : [prev, value];\n } else {\n newHeaders[key] = value;\n }\n }\n });\n\n if (!res) {\n return response;\n }\n\n if (!res.headersSent) {\n debug('sending response');\n res.status(response.status).set(newHeaders);\n typeof body === 'string' ? res.send(body) : res.json(body ?? {});\n }\n\n return ExtensionState.onHandleRequest;\n },\n };\n };\n};\n\nfunction normalizeHeaders(headers: IncomingHttpHeaders): HeadersInit {\n const normalized: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (typeof value === 'string') {\n normalized[key] = value;\n } else if (Array.isArray(value)) {\n normalized[key] = value.join(','); // Join multi-values with commas\n }\n }\n return normalized;\n}\n"],"mappings":"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","names":["AsyncLocalStorage","ExtensionState","cleaner","val","contextStore","express","app","init","instance","error","debug","setRequestContext","context","doConfigure","server","req","res","next","tenantId","normalizeHeaders","rawPaths","paths","params","reqUrl","method","proxyRequest","response","ctx","e","body","newHeaders","value","key","prev","headers","normalized"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'async_hooks';\nimport { IncomingHttpHeaders } from 'http';\n\nimport {\n ExtensionState,\n Server,\n Extension,\n PartialContext,\n} from '@niledatabase/server';\nimport type {\n Request as ExpressRequest,\n Response as ExpressResponse,\n NextFunction,\n Express,\n} from 'express';\n\nexport function cleaner(val: string) {\n return val.replaceAll(/\\{([^}]+)\\}/g, ':$1');\n}\n\nconst contextStore = new AsyncLocalStorage<Map<string, unknown>>();\n\nconst express = (\n app: Express\n): Extension<[ExpressRequest, ExpressResponse, NextFunction]> => {\n let instance: Server;\n\n return () => ({\n id: 'express',\n\n onConfigure: (server: Server) => {\n instance = server;\n const { debug } = instance.logger('[EXTENSION][express]');\n const { paths: rawPaths } = instance;\n const paths = {\n get: rawPaths.get.map(cleaner),\n post: rawPaths.post.map(cleaner),\n put: rawPaths.put.map(cleaner),\n delete: rawPaths.delete.map(cleaner),\n };\n debug(`paths configured ${JSON.stringify(paths)}`);\n instance.paths = paths;\n\n if (app) {\n debug('initializing express extension with middleware');\n\n app.use(\n (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {\n contextStore.run(new Map(), () => next());\n }\n );\n\n const setRequestContext = (context: PartialContext) => {\n contextStore.getStore()?.set('context', context);\n instance.withContext(context);\n };\n\n app.param(\n 'tenantId',\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (req: any, res: any, next: any, tenantId: string) => {\n debug(`tenantId param set: ${tenantId}`);\n setRequestContext({\n tenantId,\n headers: new Headers(normalizeHeaders(req.headers)),\n });\n next();\n }\n );\n\n app.use(\n (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {\n setRequestContext({\n headers: new Headers(normalizeHeaders(req.headers)),\n });\n next();\n }\n );\n\n debug('routes configured');\n // We cast handlers to any because Server handlers are Fetch-based but we mount them on Express\n // The Express extension assumes the Server handlers can handle the request or that\n // they are replaced/wrapped. In the original implementation, this was just passed through.\n // However, standard Nile handlers expect (req: Request). Express passes (req, res, next).\n // If this works, it's because Nile handlers might be handling the args or ignoring extra args?\n // Actually, the original code had 'server.handlers.GET' passed to 'app.get'.\n // We will do the same.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const handlers = instance.handlers as any;\n app.get(paths.get, handlers.GET);\n app.post(paths.post, handlers.POST);\n app.put(paths.put, handlers.PUT);\n app.delete(paths.delete, handlers.DELETE);\n }\n },\n\n onHandleRequest: async (\n params?: [ExpressRequest, ExpressResponse, NextFunction]\n ) => {\n if (!instance) {\n return;\n }\n const { error, debug } = instance.logger('[EXTENSION][express]');\n\n if (!params) {\n return;\n }\n const [req, res, next] = params;\n debug('handling response');\n\n let reqUrl = '';\n const method = req.method;\n const init: RequestInit = { method, headers: new Headers() };\n if (req instanceof Request) {\n reqUrl = req.url;\n init.headers = req.headers;\n } else {\n try {\n reqUrl = req.protocol + '://' + req.get('host') + req.originalUrl;\n } catch {\n // handled later\n }\n }\n\n try {\n new URL(reqUrl);\n } catch {\n throw new Error('Invalid URL — are you running Express?');\n }\n\n if (req.headers?.cookie) {\n (init.headers as Headers).set('cookie', req.headers.cookie);\n }\n\n if (['POST', 'PUT'].includes(method) && req.body) {\n init.body = JSON.stringify(req.body);\n }\n\n const proxyRequest = new Request(reqUrl, init);\n debug(`[${method}] proxy: ${reqUrl} ${JSON.stringify(init)}`);\n\n let response: Response;\n const context = {\n headers: new Headers(req.headers as HeadersInit),\n tenantId: req.params?.tenantId || undefined,\n };\n try {\n response = await instance.withContext(context, async (ctx) => {\n return (await ctx.handlers[\n method as 'GET' | 'POST' | 'PUT' | 'DELETE'\n ](proxyRequest, { disableExtensions: ['express'] })) as Response;\n });\n } catch (e) {\n error(e);\n return next();\n }\n\n let body;\n try {\n body = await response.clone().json();\n } catch {\n body = await response.text();\n }\n\n const newHeaders: Record<string, string | string[]> = {};\n response.headers.forEach((value, key) => {\n if (\n !['content-length', 'transfer-encoding'].includes(key.toLowerCase())\n ) {\n if (newHeaders[key]) {\n const prev = newHeaders[key];\n newHeaders[key] = Array.isArray(prev)\n ? [...prev, value]\n : [prev, value];\n } else {\n newHeaders[key] = value;\n }\n }\n });\n\n if (!res) {\n return response;\n }\n\n if (!res.headersSent) {\n debug('sending response');\n res.status(response.status).set(newHeaders);\n typeof body === 'string' ? res.send(body) : res.json(body ?? {});\n }\n\n return ExtensionState.onHandleRequest;\n },\n });\n};\nexport { express };\n\nfunction normalizeHeaders(headers: IncomingHttpHeaders): HeadersInit {\n const normalized: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (typeof value === 'string') {\n normalized[key] = value;\n } else if (Array.isArray(value)) {\n normalized[key] = value.join(',');\n }\n }\n return normalized;\n}\n"],"mappings":"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","names":["AsyncLocalStorage","ExtensionState","cleaner","val","contextStore","express","app","instance","server","debug","rawPaths","paths","req","res","next","setRequestContext","context","tenantId","normalizeHeaders","handlers","params","error","reqUrl","method","init","proxyRequest","response","ctx","e","body","newHeaders","value","key","prev","normalizeHeaders","headers","normalized","key","value"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@niledatabase/express",
3
- "version": "5.2.0-alpha.0",
3
+ "version": "5.2.0-alpha.3",
4
4
  "license": "MIT",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -38,11 +38,12 @@
38
38
  "express": "^5.0.0 || ^4.0.0"
39
39
  },
40
40
  "devDependencies": {
41
+ "@niledatabase/server": "^5.2.0-alpha.3",
41
42
  "@types/express": "^5",
42
43
  "express": "^5.1.0",
43
44
  "jest": "^29.7.0",
44
45
  "ts-jest": "^29.3.4",
45
46
  "tsup": "^8.5.0"
46
47
  },
47
- "gitHead": "330946fdc6a92a82e28ba2f4d2f4f815365d0ce8"
48
+ "gitHead": "8cbfc2a045871f694edf6965f930cec1ee2192bf"
48
49
  }