lambda-pipe 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -717,6 +717,56 @@ See: [DOC](https://github.com/delpikye-v/lambda-pipe/blob/main/docs/websocket.md
717
717
 
718
718
  ---
719
719
 
720
+ # Server Runtime
721
+
722
+ Production-ready Fastify runtime for LambdaPipe.
723
+
724
+ Designed for:
725
+
726
+ * bundled deployments
727
+ * lazy-loaded route manifests
728
+ * serverless adapters
729
+ * container deployments
730
+ * runtime middleware pipelines
731
+ * runtime plugin lifecycle hooks
732
+
733
+ Unlike the dev runtime, production mode does not scan the filesystem dynamically.
734
+
735
+ Routes are registered through a static manifest for deterministic builds and deployment-safe startup.
736
+
737
+ ```ts
738
+ import { bootstrap } from 'lambda-pipe/server'
739
+
740
+ import routes from './routes.manifest'
741
+
742
+ await bootstrap({
743
+ port: 3000,
744
+
745
+ routes,
746
+ })
747
+
748
+ ```
749
+
750
+ ```ts
751
+ // routes.manifest.ts
752
+
753
+ import type {
754
+ RouteLoader,
755
+ } from 'lambda-pipe/server'
756
+
757
+ const routes: RouteLoader[] = [
758
+ () => import('./routes/health'),
759
+
760
+ () => import('./routes/user'),
761
+ ]
762
+
763
+ export default routes
764
+ ```
765
+
766
+ See: [DOC](https://github.com/delpikye-v/lambda-pipe/blob/main/docs/websocket.md)
767
+
768
+ ---
769
+
720
770
  # Philosophy
721
771
 
722
772
  ```text
@@ -1 +1 @@
1
- "use strict";var e=require("node:crypto"),t=require("node:fs"),r=require("node:path"),o=require("node:url"),n=require("@fastify/cookie"),s=require("chokidar"),a=require("fastify");Math.random().toString(36).slice(2);const i=[];function c(e){return e.endsWith(".ts")||e.endsWith(".js")||e.endsWith(".mjs")||e.endsWith(".cjs")}function u(e){if(e)return function(e){try{return JSON.parse(e)}catch{return e}}(e)}function h(t){return{headers:t.headers,body:t.body,queryStringParameters:t.query,pathParameters:t.params,cookies:t.cookies,rawPath:t.url,requestContext:{requestId:e.randomUUID(),http:{method:t.method}}}}async function d(e,n){const s=t.readdirSync(n,{withFileTypes:!0});for(const t of s){const s=r.join(n,t.name);if(t.isDirectory()){await d(e,s);continue}if(!c(t.name))continue;const a=(await import(o.pathToFileURL(s).href+"?v="+Date.now())).default;a?.__isRoute&&(i.push({method:a.method.toUpperCase(),path:a.path,file:s}),e.route({method:a.method.toUpperCase(),url:a.path,handler:async(e,t)=>{try{const r=await a.handler(h(e),{});if(t.code(r.statusCode),r.headers)for(const[e,o]of Object.entries(r.headers))t.header(e,o);return u(r.body)}catch(e){t.code(500).send({error:e.message,stack:e.stack,path:a.path})}}}))}}async function p(e={}){const o=a({logger:e.logger??!0});await o.register(n);const c=(u=e.routesDir,r.resolve(process.cwd(),u||process.env.ROUTES_DIR||"src/routes"));var u;if(!t.existsSync(c))throw Error("Routes directory not found: "+c);return function(e){e.get("/__routes",async()=>({routes:i})),e.get("/__health",async()=>({ok:!0,ts:Date.now()}))}(o),await d(o,c),!1!==e.hmr&&function(e,t){s.watch(t,{ignoreInitial:!0}).on("change",e=>{process.exit(0)})}(0,c),o}exports.bootstrap=async function(e={}){const t=await p(e),r=function(e){return Number(e||process.env.PORT||3e3)}(e.port),o=function(e){return e||process.env.HOST||"0.0.0.0"}(e.host);return await t.listen({port:r,host:o}),t},exports.createDevApp=p;
1
+ "use strict";var e=require("node:fs"),t=require("node:path"),o=require("node:url"),r=require("@fastify/cookie"),n=require("chokidar"),s=require("fastify"),a=require("node:crypto");function i(e,t){return{headers:e.headers,body:e.body,queryStringParameters:e.query,pathParameters:e.params,cookies:e.cookies||{},rawPath:e.url,method:e.method,requestId:a.randomUUID(),request:e,reply:t}}async function c(e=[],t){for(const o of e)await o(t)}async function u(e,t){const o=t.statusCode||200;if(e.code(o),t.headers)for(const[o,r]of Object.entries(t.headers))e.header(o,r);return t.body}async function d(e,t,o=[]){for(const r of o)r.onError&&await r.onError(e,t);return t.reply.code(e.statusCode||500),{error:e.message||"Internal Server Error",stack:"development"===process.env.NODE_ENV?e.stack:void 0}}async function f(e=[],t){for(const o of e)o.onRequest&&await o.onRequest(t)}async function h(e=[],t,o){for(const r of e)r.onResponse&&await r.onResponse(t,o)}const p=[];function y(e){return e.endsWith(".ts")||e.endsWith(".js")||e.endsWith(".mjs")||e.endsWith(".cjs")}async function l(r,n,s){const a=e.readdirSync(n,{withFileTypes:!0});for(const e of a){const a=t.join(n,e.name);if(e.isDirectory()){await l(r,a,s);continue}if(!y(e.name))continue;const w=(await import(o.pathToFileURL(a).href+"?v="+Date.now())).default;w?.__isRoute&&(p.push({method:w.method.toUpperCase(),path:w.path,file:a}),r.route({method:w.method.toUpperCase(),url:w.path,handler:async(e,t)=>{const o=i(e,t);try{await f(s.plugins,o),await c(s.globalMiddlewares,o),await c(w.middlewares,o);const e=await w.handler(o,r);return await h(s.plugins,o,e),u(t,e)}catch(e){return d(e,o,s.plugins)}}}))}}async function w(o={}){const a=s({logger:o.logger??!0});await a.register(r),await async function(e=[],t){for(const o of e)o.onInit&&await o.onInit(t)}(o.plugins,a);const i=(c=o.routesDir,t.resolve(process.cwd(),c||process.env.ROUTES_DIR||"src/routes"));var c;if(!e.existsSync(i))throw Error("Routes directory not found: "+i);return function(e){e.get("/__routes",async()=>({routes:p})),e.get("/__health",async()=>({ok:!0,ts:Date.now()}))}(a),await l(a,i,o),!1!==o.hmr&&function(e){n.watch(e,{ignoreInitial:!0}).on("change",e=>{process.exit(0)})}(i),a}exports.bootstrap=async function(e={}){const t=await w(e),o=function(e){return Number(e||process.env.PORT||3e3)}(e.port),r=function(e){return e||process.env.HOST||"0.0.0.0"}(e.host);return await t.listen({port:o,host:r}),t},exports.createDevApp=w;
@@ -0,0 +1 @@
1
+ "use strict";var o=require("@fastify/cookie"),t=require("fastify"),e=require("node:crypto");const r=new WeakMap;function n(o,t){return{headers:o.headers,body:o.body,queryStringParameters:o.query,pathParameters:o.params,cookies:o.cookies||{},rawPath:o.url,method:o.method,requestId:e.randomUUID(),request:o,reply:t}}async function a(o=[],t){for(const e of o)await e(t)}async function s(o,t){const e=t.statusCode||200;if(o.code(e),t.headers)for(const[e,r]of Object.entries(t.headers))o.header(e,r);return t.body}async function i(o,t,e=[]){for(const r of e)r.onError&&await r.onError(o,t);return t.reply.code(o.statusCode||500),{error:o.message||"Internal Server Error",stack:"development"===process.env.NODE_ENV?o.stack:void 0}}async function c(o){const t=r.get(o);if(t)return t;const e=(await o()).default;if(!e?.__isRoute)throw Error("Invalid route module");return r.set(o,e),e}async function u(o=[],t){for(const e of o)e.onRequest&&await e.onRequest(t)}async function f(o=[],t,e){for(const r of o)r.onResponse&&await r.onResponse(t,e)}async function d(e){const r=t({logger:e.logger??!0});await r.register(o),await async function(o=[],t){for(const e of o)e.onInit&&await e.onInit(t)}(e.plugins,r);for(const o of e.routes){const t=await c(o);r.log.info(`⚡ ${t.method} ${t.path}`),r.route({method:t.method,url:t.path,handler:async(o,c)=>{const d=n(o,c);try{await u(e.plugins,d),await a(e.globalMiddlewares,d),await a(t.middlewares,d);const o=await t.handler(d,r);return await f(e.plugins,d,o),s(c,o)}catch(o){return i(o,d,e.plugins)}}})}return r}exports.bootstrap=async function(o){const t=await d(o),e=Number(o.port||process.env.PORT||3e3),r=o.host||process.env.HOST||"0.0.0.0";return await t.listen({port:e,host:r}),t.log.info(""),t.log.info("🚀 LambdaPipe Runtime"),t.log.info(`🌐 http://${r}:${e}`),t.log.info("📦 routes: "+o.routes.length),t.log.info(""),t},exports.createRuntimeApp=d;
@@ -1,10 +1,12 @@
1
- import { type FastifyInstance } from 'fastify';
1
+ import type { RuntimeMiddleware, RuntimePlugin } from './shared';
2
2
  export type CreateDevAppOptions = {
3
3
  port?: number;
4
4
  host?: string;
5
5
  routesDir?: string;
6
6
  logger?: boolean;
7
7
  hmr?: boolean;
8
+ plugins?: RuntimePlugin[];
9
+ globalMiddlewares?: RuntimeMiddleware[];
8
10
  };
9
- export declare function createDevApp(options?: CreateDevAppOptions): Promise<FastifyInstance<import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>>;
10
- export declare function bootstrap(options?: CreateDevAppOptions): Promise<FastifyInstance<import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>>;
11
+ export declare function createDevApp(options?: CreateDevAppOptions): Promise<import("fastify").FastifyInstance<import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>>;
12
+ export declare function bootstrap(options?: CreateDevAppOptions): Promise<import("fastify").FastifyInstance<import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>>;
@@ -0,0 +1 @@
1
+ export declare const routes: (() => Promise<typeof import("../../src/routes/health")>)[];
@@ -0,0 +1,13 @@
1
+ import type { RouteLoader, RuntimeMiddleware, RuntimePlugin } from './shared';
2
+ export type CreateRuntimeAppOptions = {
3
+ routes: RouteLoader[];
4
+ logger?: boolean;
5
+ plugins?: RuntimePlugin[];
6
+ globalMiddlewares?: RuntimeMiddleware[];
7
+ };
8
+ export type BootstrapOptions = CreateRuntimeAppOptions & {
9
+ port?: number;
10
+ host?: string;
11
+ };
12
+ export declare function createRuntimeApp(options: CreateRuntimeAppOptions): Promise<import("fastify").FastifyInstance<import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>>;
13
+ export declare function bootstrap(options: BootstrapOptions): Promise<import("fastify").FastifyInstance<import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>, import("node:http").IncomingMessage, import("node:http").ServerResponse<import("node:http").IncomingMessage>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>>;
@@ -0,0 +1,49 @@
1
+ import type { FastifyReply, FastifyRequest } from 'fastify';
2
+ export type RuntimeContext = {
3
+ headers: Record<string, any>;
4
+ body: unknown;
5
+ queryStringParameters: Record<string, any>;
6
+ pathParameters: Record<string, any>;
7
+ cookies: Record<string, any>;
8
+ rawPath: string;
9
+ method: string;
10
+ requestId: string;
11
+ request: FastifyRequest;
12
+ reply: FastifyReply;
13
+ };
14
+ export type RuntimeResponse = {
15
+ statusCode?: number;
16
+ headers?: Record<string, any>;
17
+ body?: any;
18
+ };
19
+ export type RuntimeMiddleware = (ctx: RuntimeContext) => Promise<void> | void;
20
+ export type RuntimePlugin = {
21
+ name?: string;
22
+ onInit?: (app: any) => Promise<void> | void;
23
+ onRequest?: (ctx: RuntimeContext) => Promise<void> | void;
24
+ onResponse?: (ctx: RuntimeContext, response: RuntimeResponse) => Promise<void> | void;
25
+ onError?: (error: unknown, ctx: RuntimeContext) => Promise<void> | void;
26
+ };
27
+ export type RouteDefinition = {
28
+ __isRoute?: boolean;
29
+ path: string;
30
+ method: string;
31
+ middlewares?: RuntimeMiddleware[];
32
+ handler: (ctx: RuntimeContext, app: any) => Promise<RuntimeResponse>;
33
+ };
34
+ export type RouteModule = {
35
+ default: RouteDefinition;
36
+ };
37
+ export type RouteLoader = () => Promise<RouteModule>;
38
+ export declare const moduleCache: WeakMap<RouteLoader, RouteDefinition>;
39
+ export declare function createRuntimeContext(req: FastifyRequest, reply: FastifyReply): RuntimeContext;
40
+ export declare function runMiddlewares(middlewares: RuntimeMiddleware[] | undefined, ctx: RuntimeContext): Promise<void>;
41
+ export declare function serializeResponse(reply: FastifyReply, response: RuntimeResponse): Promise<any>;
42
+ export declare function handleRuntimeError(error: any, ctx: RuntimeContext, plugins?: RuntimePlugin[]): Promise<{
43
+ error: any;
44
+ stack: any;
45
+ }>;
46
+ export declare function loadRouteModule(load: RouteLoader): Promise<RouteDefinition>;
47
+ export declare function runPluginOnInit(plugins: RuntimePlugin[] | undefined, app: any): Promise<void>;
48
+ export declare function runPluginOnRequest(plugins: RuntimePlugin[] | undefined, ctx: RuntimeContext): Promise<void>;
49
+ export declare function runPluginOnResponse(plugins: RuntimePlugin[] | undefined, ctx: RuntimeContext, response: RuntimeResponse): Promise<void>;
@@ -1 +1 @@
1
- import t from"node:crypto";import e from"node:fs";import r from"node:path";import{pathToFileURL as o}from"node:url";import n from"@fastify/cookie";import s from"chokidar";import a from"fastify";Math.random().toString(36).slice(2);const i=[];function c(t){return t.endsWith(".ts")||t.endsWith(".js")||t.endsWith(".mjs")||t.endsWith(".cjs")}function u(t){if(t)return function(t){try{return JSON.parse(t)}catch{return t}}(t)}function h(e){return{headers:e.headers,body:e.body,queryStringParameters:e.query,pathParameters:e.params,cookies:e.cookies,rawPath:e.url,requestContext:{requestId:t.randomUUID(),http:{method:e.method}}}}async function d(t,n){const s=e.readdirSync(n,{withFileTypes:!0});for(const e of s){const s=r.join(n,e.name);if(e.isDirectory()){await d(t,s);continue}if(!c(e.name))continue;const a=(await import(o(s).href+"?v="+Date.now())).default;a?.__isRoute&&(i.push({method:a.method.toUpperCase(),path:a.path,file:s}),t.route({method:a.method.toUpperCase(),url:a.path,handler:async(t,e)=>{try{const r=await a.handler(h(t),{});if(e.code(r.statusCode),r.headers)for(const[t,o]of Object.entries(r.headers))e.header(t,o);return u(r.body)}catch(t){e.code(500).send({error:t.message,stack:t.stack,path:a.path})}}}))}}async function f(t={}){const o=a({logger:t.logger??!0});await o.register(n);const c=(u=t.routesDir,r.resolve(process.cwd(),u||process.env.ROUTES_DIR||"src/routes"));var u;if(!e.existsSync(c))throw Error("Routes directory not found: "+c);return function(t){t.get("/__routes",async()=>({routes:i})),t.get("/__health",async()=>({ok:!0,ts:Date.now()}))}(o),await d(o,c),!1!==t.hmr&&function(t,e){s.watch(e,{ignoreInitial:!0}).on("change",t=>{process.exit(0)})}(0,c),o}async function p(t={}){const e=await f(t),r=function(t){return Number(t||process.env.PORT||3e3)}(t.port),o=function(t){return t||process.env.HOST||"0.0.0.0"}(t.host);return await e.listen({port:r,host:o}),e}export{p as bootstrap,f as createDevApp};
1
+ import t from"node:fs";import o from"node:path";import{pathToFileURL as e}from"node:url";import r from"@fastify/cookie";import n from"chokidar";import s from"fastify";import a from"node:crypto";function i(t,o){return{headers:t.headers,body:t.body,queryStringParameters:t.query,pathParameters:t.params,cookies:t.cookies||{},rawPath:t.url,method:t.method,requestId:a.randomUUID(),request:t,reply:o}}async function c(t=[],o){for(const e of t)await e(o)}async function u(t,o){const e=o.statusCode||200;if(t.code(e),o.headers)for(const[e,r]of Object.entries(o.headers))t.header(e,r);return o.body}async function f(t,o,e=[]){for(const r of e)r.onError&&await r.onError(t,o);return o.reply.code(t.statusCode||500),{error:t.message||"Internal Server Error",stack:"development"===process.env.NODE_ENV?t.stack:void 0}}async function d(t=[],o){for(const e of t)e.onRequest&&await e.onRequest(o)}async function p(t=[],o,e){for(const r of t)r.onResponse&&await r.onResponse(o,e)}const h=[];function m(t){return t.endsWith(".ts")||t.endsWith(".js")||t.endsWith(".mjs")||t.endsWith(".cjs")}async function y(r,n,s){const a=t.readdirSync(n,{withFileTypes:!0});for(const t of a){const a=o.join(n,t.name);if(t.isDirectory()){await y(r,a,s);continue}if(!m(t.name))continue;const l=(await import(e(a).href+"?v="+Date.now())).default;l?.__isRoute&&(h.push({method:l.method.toUpperCase(),path:l.path,file:a}),r.route({method:l.method.toUpperCase(),url:l.path,handler:async(t,o)=>{const e=i(t,o);try{await d(s.plugins,e),await c(s.globalMiddlewares,e),await c(l.middlewares,e);const t=await l.handler(e,r);return await p(s.plugins,e,t),u(o,t)}catch(t){return f(t,e,s.plugins)}}}))}}async function l(e={}){const a=s({logger:e.logger??!0});await a.register(r),await async function(t=[],o){for(const e of t)e.onInit&&await e.onInit(o)}(e.plugins,a);const i=(c=e.routesDir,o.resolve(process.cwd(),c||process.env.ROUTES_DIR||"src/routes"));var c;if(!t.existsSync(i))throw Error("Routes directory not found: "+i);return function(t){t.get("/__routes",async()=>({routes:h})),t.get("/__health",async()=>({ok:!0,ts:Date.now()}))}(a),await y(a,i,e),!1!==e.hmr&&function(t){n.watch(t,{ignoreInitial:!0}).on("change",t=>{process.exit(0)})}(i),a}async function w(t={}){const o=await l(t),e=function(t){return Number(t||process.env.PORT||3e3)}(t.port),r=function(t){return t||process.env.HOST||"0.0.0.0"}(t.host);return await o.listen({port:e,host:r}),o}export{w as bootstrap,l as createDevApp};
@@ -0,0 +1 @@
1
+ import o from"@fastify/cookie";import t from"fastify";import e from"node:crypto";const n=new WeakMap;function r(o,t){return{headers:o.headers,body:o.body,queryStringParameters:o.query,pathParameters:o.params,cookies:o.cookies||{},rawPath:o.url,method:o.method,requestId:e.randomUUID(),request:o,reply:t}}async function a(o=[],t){for(const e of o)await e(t)}async function s(o,t){const e=t.statusCode||200;if(o.code(e),t.headers)for(const[e,n]of Object.entries(t.headers))o.header(e,n);return t.body}async function i(o,t,e=[]){for(const n of e)n.onError&&await n.onError(o,t);return t.reply.code(o.statusCode||500),{error:o.message||"Internal Server Error",stack:"development"===process.env.NODE_ENV?o.stack:void 0}}async function c(o){const t=n.get(o);if(t)return t;const e=(await o()).default;if(!e?.__isRoute)throw Error("Invalid route module");return n.set(o,e),e}async function u(o=[],t){for(const e of o)e.onRequest&&await e.onRequest(t)}async function f(o=[],t,e){for(const n of o)n.onResponse&&await n.onResponse(t,e)}async function d(e){const n=t({logger:e.logger??!0});await n.register(o),await async function(o=[],t){for(const e of o)e.onInit&&await e.onInit(t)}(e.plugins,n);for(const o of e.routes){const t=await c(o);n.log.info(`⚡ ${t.method} ${t.path}`),n.route({method:t.method,url:t.path,handler:async(o,c)=>{const d=r(o,c);try{await u(e.plugins,d),await a(e.globalMiddlewares,d),await a(t.middlewares,d);const o=await t.handler(d,n);return await f(e.plugins,d,o),s(c,o)}catch(o){return i(o,d,e.plugins)}}})}return n}async function l(o){const t=await d(o),e=Number(o.port||process.env.PORT||3e3),n=o.host||process.env.HOST||"0.0.0.0";return await t.listen({port:e,host:n}),t.log.info(""),t.log.info("🚀 LambdaPipe Runtime"),t.log.info(`🌐 http://${n}:${e}`),t.log.info("📦 routes: "+o.routes.length),t.log.info(""),t}export{l as bootstrap,d as createRuntimeApp};
@@ -0,0 +1,397 @@
1
+ # Production Runtime production.ts
2
+
3
+ Production-ready Fastify runtime for LambdaPipe.
4
+
5
+ Designed for:
6
+
7
+ * bundled deployments
8
+ * lazy-loaded route manifests
9
+ * serverless adapters
10
+ * container deployments
11
+ * runtime middleware pipelines
12
+ * runtime plugin lifecycle hooks
13
+
14
+ Unlike the dev runtime, production mode does not scan the filesystem dynamically.
15
+
16
+ Routes are registered through a static manifest for deterministic builds and deployment-safe startup.
17
+
18
+ ---
19
+
20
+ # Features
21
+
22
+ * Fastify production runtime
23
+ * Static route manifest system
24
+ * Lazy-loaded route modules
25
+ * Runtime middleware lifecycle
26
+ * Runtime plugin hooks
27
+ * Shared execution context
28
+ * Cookie support
29
+ * Response serialization
30
+ * Route module cache
31
+ * Error handling pipeline
32
+ * Deployment-safe routing
33
+ * Tree-shake friendly architecture
34
+
35
+ ---
36
+
37
+ # Installation
38
+
39
+ ```bash
40
+ npm install fastify @fastify/cookie
41
+ ```
42
+
43
+ ---
44
+
45
+ # Quick Start
46
+
47
+ ```ts
48
+ import { bootstrap } from 'lambda-pipe/server'
49
+
50
+ import routes from './routes.manifest'
51
+
52
+ await bootstrap({
53
+ port: 3000,
54
+
55
+ routes,
56
+ })
57
+ ```
58
+
59
+ ---
60
+
61
+ # Route Manifest
62
+
63
+ ```ts
64
+ // routes.manifest.ts
65
+
66
+ import type {
67
+ RouteLoader,
68
+ } from 'lambda-pipe/server'
69
+
70
+ const routes: RouteLoader[] = [
71
+ () => import('./routes/health'),
72
+
73
+ () => import('./routes/user'),
74
+ ]
75
+
76
+ export default routes
77
+ ```
78
+
79
+ ---
80
+
81
+ # Why Manifest Loaders
82
+
83
+ Each manifest item is only responsible for lazy-importing a route module.
84
+
85
+ Route metadata is defined directly inside `defineRoute()`.
86
+
87
+ This avoids:
88
+
89
+ * duplicated route metadata
90
+ * route mismatch bugs
91
+ * startup filesystem scanning
92
+ * runtime path resolution issues
93
+
94
+ ---
95
+
96
+ # Project Structure
97
+
98
+ ```bash
99
+ src/
100
+ routes/
101
+ health.ts
102
+ user.ts
103
+
104
+ routes.manifest.ts
105
+ ```
106
+
107
+ ---
108
+
109
+ # Health Route
110
+
111
+ ```ts
112
+ import {
113
+ defineRoute,
114
+ ok,
115
+ } from 'lambda-pipe'
116
+
117
+ export default defineRoute({
118
+ method: 'GET',
119
+
120
+ path: '/health',
121
+
122
+ handler: async () => {
123
+ return ok({
124
+ status: 'ok',
125
+
126
+ uptime: process.uptime(),
127
+ })
128
+ },
129
+ })
130
+ ```
131
+
132
+ ---
133
+
134
+ # Full Runtime Route Example
135
+
136
+ ```ts
137
+ import {
138
+ defineRoute,
139
+ ok,
140
+ HttpError,
141
+ } from 'lambda-pipe'
142
+
143
+ export default defineRoute({
144
+ method: 'GET',
145
+
146
+ path: '/user/:id',
147
+
148
+ middlewares: [
149
+ async (context) => {
150
+ if (!context.headers.authorization) {
151
+ throw new HttpError(
152
+ 401,
153
+ 'Unauthorized',
154
+ )
155
+ }
156
+ },
157
+ ],
158
+
159
+ handler: async (context) => {
160
+ const userId =
161
+ context.pathParameters.id
162
+
163
+ return ok({
164
+ id: userId,
165
+
166
+ source: 'runtime',
167
+
168
+ requestId:
169
+ context.requestId,
170
+ })
171
+ },
172
+ })
173
+ ```
174
+
175
+ ---
176
+
177
+ # Runtime Plugins
178
+
179
+ ```ts
180
+ import type {
181
+ RuntimePlugin,
182
+ } from 'lambda-pipe/server'
183
+
184
+ export const loggerPlugin: RuntimePlugin = {
185
+ name: 'logger',
186
+
187
+ onInit: async (app) => {
188
+ app.log.info(
189
+ 'logger initialized',
190
+ )
191
+ },
192
+
193
+ onRequest: async (context) => {
194
+ context.request.log.info({
195
+ method: context.method,
196
+
197
+ path: context.rawPath,
198
+ })
199
+ },
200
+
201
+ onResponse: async (
202
+ context,
203
+ response,
204
+ ) => {
205
+ context.request.log.info({
206
+ statusCode:
207
+ response.statusCode,
208
+ })
209
+ },
210
+
211
+ onError: async (
212
+ error,
213
+ context,
214
+ ) => {
215
+ context.request.log.error(
216
+ error,
217
+ )
218
+ },
219
+ }
220
+ ```
221
+
222
+ ---
223
+
224
+ # Using Plugins
225
+
226
+ ```ts
227
+ import { bootstrap } from 'lambda-pipe/server'
228
+
229
+ import routes from './routes.manifest'
230
+
231
+ import { loggerPlugin } from './plugins/logger'
232
+
233
+ await bootstrap({
234
+ routes,
235
+
236
+ plugins: [loggerPlugin],
237
+ })
238
+ ```
239
+
240
+ ---
241
+
242
+ # Global Middleware
243
+
244
+ ```ts
245
+ await bootstrap({
246
+ port: 3000,
247
+
248
+ routes,
249
+
250
+ logger: true,
251
+
252
+ globalMiddlewares: [
253
+ async (context) => {
254
+ context.request.log.info({
255
+ path: context.rawPath,
256
+ })
257
+ },
258
+ ],
259
+ })
260
+ ```
261
+
262
+ ---
263
+
264
+ # Runtime Lifecycle
265
+
266
+ ```text
267
+ HTTP Request
268
+
269
+ Fastify Runtime
270
+
271
+ Route Manifest Match
272
+
273
+ Lazy Route Import
274
+
275
+ Create Runtime Context
276
+
277
+ Plugin onRequest Hooks
278
+
279
+ Global Middlewares
280
+
281
+ Route Middlewares
282
+
283
+ Route Handler
284
+
285
+ Plugin onResponse Hooks
286
+
287
+ Serialize Response
288
+
289
+ HTTP Response
290
+ ```
291
+
292
+ ---
293
+
294
+ # Error Lifecycle
295
+
296
+ ```text
297
+ Handler Error
298
+
299
+ Plugin onError Hooks
300
+
301
+ Runtime Error Handler
302
+
303
+ Serialized Error Response
304
+ ```
305
+
306
+ ---
307
+
308
+ # Response Shape
309
+
310
+ ```ts
311
+ return {
312
+ statusCode: 200,
313
+
314
+ headers: {
315
+ 'x-runtime': 'lambda-pipe',
316
+ },
317
+
318
+ body: {
319
+ ok: true,
320
+ },
321
+ }
322
+ ```
323
+
324
+ ---
325
+
326
+ # Module Cache
327
+
328
+ Production runtime caches loaded route modules internally using a WeakMap-based loader cache.
329
+
330
+ Benefits:
331
+
332
+ * avoids duplicate imports
333
+ * keeps lazy-loading efficient
334
+ * reduces runtime overhead
335
+ * keeps route identity stable
336
+
337
+ ---
338
+
339
+ # Deployment Targets
340
+
341
+ Production runtime works well with:
342
+
343
+ * AWS Lambda
344
+ * ECS / Fargate
345
+ * Docker containers
346
+ * Bun runtime
347
+ * Node.js servers
348
+ * Edge-compatible adapters
349
+ * Kubernetes
350
+ * Railway / Render / Fly.io
351
+
352
+ ---
353
+
354
+ # Why Production Runtime Avoids FS Scanning
355
+
356
+ Filesystem scanning is avoided in production because:
357
+
358
+ * faster startup
359
+ * deployment-safe bundling
360
+ * smaller runtime surface
361
+ * deterministic route registration
362
+ * serverless compatibility
363
+ * better tree-shaking support
364
+ * fewer runtime filesystem assumptions
365
+
366
+ ---
367
+
368
+ # Example Production Bootstrap
369
+
370
+ ```ts
371
+ import { bootstrap } from 'lambda-pipe/server'
372
+
373
+ import routes from './routes.manifest'
374
+
375
+ await bootstrap({
376
+ port: 8080,
377
+
378
+ host: '0.0.0.0',
379
+
380
+ routes,
381
+
382
+ logger: true,
383
+ })
384
+ ```
385
+
386
+ ---
387
+
388
+ # Example Startup Logs
389
+
390
+ ```text
391
+ ⚡ GET /health
392
+ ⚡ GET /user/:id
393
+
394
+ 🚀 LambdaPipe Runtime
395
+ 🌐 http://0.0.0.0:3000
396
+ 📦 routes: 2
397
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambda-pipe",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Type-safe plugin & middleware runtime for AWS Lambda and serverless APIs.",
5
5
  "license": "MIT",
6
6
  "author": "Delpi.Kye",
@@ -38,6 +38,11 @@
38
38
  "types": "./build/cli/dev.d.ts",
39
39
  "import": "./build/esm/cli/index.js",
40
40
  "require": "./build/cjs/cli/index.js"
41
+ },
42
+ "./server": {
43
+ "types": "./build/cli/server.d.ts",
44
+ "import": "./build/esm/server/index.js",
45
+ "require": "./build/cjs/server/index.js"
41
46
  }
42
47
  },
43
48
  "files": [
@@ -47,6 +52,7 @@
47
52
  "scripts": {
48
53
  "clean": "rimraf build",
49
54
  "dev": "nodemon --watch src --ext ts --exec tsx src/cli/dev.ts",
55
+ "prod": "nodemon --watch src --ext ts --exec tsx src/cli/server.ts",
50
56
  "build": "rollup -c",
51
57
  "cb": "npm run clean && npm run build",
52
58
  "lint": "eslint \"src/**/*.{ts,tsx}\"",