lambda-pipe 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +761 -0
  3. package/build/cjs/cli/index.js +1 -0
  4. package/build/cjs/index.js +1 -0
  5. package/build/cjs/jobs/index.js +1 -0
  6. package/build/cjs/logger/index.js +1 -0
  7. package/build/cjs/runtime/index.js +1 -0
  8. package/build/cjs/websocket/index.js +1 -0
  9. package/build/cli/dev.d.ts +10 -0
  10. package/build/core/cache/index.d.ts +19 -0
  11. package/build/core/context/createRequestContext.d.ts +2 -0
  12. package/build/core/context/index.d.ts +2 -0
  13. package/build/core/context/types.d.ts +52 -0
  14. package/build/core/context/utils.d.ts +6 -0
  15. package/build/core/errors/HttpError.d.ts +6 -0
  16. package/build/core/errors/handleError.d.ts +2 -0
  17. package/build/core/errors/http.d.ts +13 -0
  18. package/build/core/errors/index.d.ts +3 -0
  19. package/build/core/http/helpers.d.ts +6 -0
  20. package/build/core/http/index.d.ts +3 -0
  21. package/build/core/http/serialize.d.ts +2 -0
  22. package/build/core/http/types.d.ts +6 -0
  23. package/build/core/index.d.ts +8 -0
  24. package/build/core/lifecycle/hooks.d.ts +4 -0
  25. package/build/core/lifecycle/index.d.ts +1 -0
  26. package/build/core/middleware/index.d.ts +2 -0
  27. package/build/core/middleware/run.d.ts +4 -0
  28. package/build/core/middleware/types.d.ts +11 -0
  29. package/build/core/plugins/index.d.ts +1 -0
  30. package/build/core/plugins/types.d.ts +15 -0
  31. package/build/core/routing/defineHandler.d.ts +14 -0
  32. package/build/core/routing/defineRoute.d.ts +20 -0
  33. package/build/core/routing/execution.d.ts +4 -0
  34. package/build/core/routing/index.d.ts +3 -0
  35. package/build/core/routing/types.d.ts +55 -0
  36. package/build/core/validation/index.d.ts +2 -0
  37. package/build/core/validation/types.d.ts +8 -0
  38. package/build/core/validation/validate.d.ts +2 -0
  39. package/build/esm/cli/index.js +1 -0
  40. package/build/esm/index.js +1 -0
  41. package/build/esm/jobs/index.js +1 -0
  42. package/build/esm/logger/index.js +1 -0
  43. package/build/esm/runtime/index.js +1 -0
  44. package/build/esm/websocket/index.js +1 -0
  45. package/build/index.d.ts +1 -0
  46. package/build/jobs/defineJob.d.ts +5 -0
  47. package/build/jobs/index.d.ts +2 -0
  48. package/build/jobs/sqs.d.ts +7 -0
  49. package/build/logger/index.d.ts +2 -0
  50. package/build/logger/logger.d.ts +2 -0
  51. package/build/logger/types.d.ts +6 -0
  52. package/build/routes/health.d.ts +2 -0
  53. package/build/routes/user.d.ts +2 -0
  54. package/build/runtime/aws/createAWSHandler.d.ts +3 -0
  55. package/build/runtime/aws/index.d.ts +2 -0
  56. package/build/runtime/aws/request.d.ts +4 -0
  57. package/build/runtime/fastify/createFastifyHandler.d.ts +3 -0
  58. package/build/runtime/fastify/index.d.ts +2 -0
  59. package/build/runtime/fastify/request.d.ts +4 -0
  60. package/build/runtime/index.d.ts +2 -0
  61. package/build/websocket/index.d.ts +16 -0
  62. package/docs/dev-server.md +278 -0
  63. package/docs/websocket.md +118 -0
  64. package/package.json +135 -0
package/README.md ADDED
@@ -0,0 +1,761 @@
1
+ # ⚡ lambda-pipe
2
+
3
+ [![NPM](https://img.shields.io/npm/v/lambda-pipe.svg)](https://www.npmjs.com/package/lambda-pipe) ![Downloads](https://img.shields.io/npm/dt/lambda-pipe.svg) ![Bundle Size](https://img.shields.io/bundlephobia/minzip/lambda-pipe)
4
+
5
+ [LIVE EXAMPLE](https://codesandbox.io/p/devbox/2tyg7y)
6
+
7
+
8
+ 🚀 A type-safe plugin & middleware runtime for AWS Lambda APIs.
9
+
10
+ > Build composable serverless backends with typed context, lifecycle hooks, validation, plugins, middleware, caching, runtime adapters, and background jobs.
11
+
12
+ ---
13
+
14
+ # Basic Route
15
+
16
+ ```ts
17
+ // src/routes
18
+ import {
19
+ defineRoute,
20
+ ok,
21
+ } from 'lambda-pipe'
22
+
23
+ export default defineRoute({
24
+ method: 'GET',
25
+
26
+ path: '/health',
27
+
28
+ handler: async () => {
29
+ return ok({
30
+ status: 'ok',
31
+ })
32
+ },
33
+ })
34
+
35
+ ```
36
+
37
+ ---
38
+
39
+ # What is lambda-pipe?
40
+
41
+ lambda-pipe is a lightweight execution runtime for serverless APIs.
42
+
43
+ It provides:
44
+
45
+ * typed request context
46
+ * middleware lifecycle
47
+ * plugin system
48
+ * validation pipeline
49
+ * authentication pipeline
50
+ * execution metadata
51
+ * cache layer
52
+ * runtime adapters
53
+ * background job handlers
54
+
55
+ without forcing framework architecture.
56
+
57
+ ---
58
+
59
+ # Why lambda-pipe?
60
+
61
+ Most serverless projects become messy over time:
62
+
63
+ * auth duplicated everywhere
64
+ * validation inconsistent
65
+ * middleware order unclear
66
+ * request context untyped
67
+ * plugins tightly coupled
68
+ * runtime logic scattered
69
+
70
+ lambda-pipe provides:
71
+
72
+ * deterministic execution
73
+ * composable plugins
74
+ * centralized lifecycle
75
+ * typed runtime context
76
+ * adapter-friendly architecture
77
+
78
+ ---
79
+
80
+ # Features
81
+
82
+ * ⚡ Serverless-first runtime
83
+ * 🔌 Plugin system
84
+ * 🧩 Middleware pipeline
85
+ * 🧠 Cold start lifecycle hooks
86
+ * 🛡 Typed validation
87
+ * 🔑 Authentication pipeline
88
+ * 🍪 Cookie helpers
89
+ * 🧱 Runtime adapters
90
+ * 📝 Full TypeScript inference
91
+ * 💾 In-memory execution cache
92
+ * 🌐 Framework agnostic
93
+ * 📦 SQS job runtime
94
+ * 🚀 Fastify development server
95
+
96
+ ---
97
+
98
+ # Metal model
99
+
100
+ ```text
101
+ Runtime Adapter
102
+
103
+ normalizeRequest()
104
+
105
+ createRequestContext()
106
+
107
+ Plugins
108
+
109
+ Middleware Pipeline
110
+
111
+ Handler
112
+
113
+ serializeResponse()
114
+ ```
115
+
116
+ ---
117
+
118
+ # Installation
119
+
120
+ ```bash
121
+ npm install lambda-pipe
122
+ ```
123
+
124
+ ---
125
+
126
+ # Route Definition
127
+
128
+ ```ts
129
+ import {
130
+ defineRoute,
131
+ ok,
132
+ } from 'lambda-pipe'
133
+
134
+ export default defineRoute({
135
+ method: 'GET',
136
+
137
+ path: '/users/:id',
138
+
139
+ handler: async (context) => {
140
+ return ok({
141
+ id: context.params.id,
142
+ })
143
+ },
144
+ })
145
+ ```
146
+
147
+ ---
148
+
149
+ # Typed Context
150
+
151
+ ```ts
152
+ import {
153
+ defineRoute,
154
+ ok,
155
+ } from 'lambda-pipe'
156
+
157
+ export default defineRoute<
158
+ {
159
+ query: {
160
+ expand?: string
161
+ }
162
+
163
+ params: {
164
+ id: string
165
+ }
166
+ }
167
+ >({
168
+ method: 'GET',
169
+
170
+ path: '/users/:id',
171
+
172
+ handler: async (context) => {
173
+ context.query.expand
174
+
175
+ context.params.id
176
+
177
+ return ok()
178
+ },
179
+ })
180
+ ```
181
+
182
+ ---
183
+
184
+ # Validation
185
+
186
+ ```ts
187
+ import {
188
+ defineRoute,
189
+ ok,
190
+ ValidationError,
191
+ } from 'lambda-pipe'
192
+
193
+ import type {
194
+ Validator,
195
+ } from 'lambda-pipe'
196
+
197
+ const paramsValidator: Validator<{
198
+ id: string
199
+ }> = async (input) => {
200
+ const params = input as any
201
+
202
+ if (!params.id) {
203
+ throw new ValidationError('missing id')
204
+ }
205
+
206
+ return {
207
+ id: params.id,
208
+ }
209
+ }
210
+
211
+ export default defineRoute({
212
+ method: 'GET',
213
+
214
+ path: '/users/:id',
215
+
216
+ validate: {
217
+ params: paramsValidator,
218
+ },
219
+
220
+ handler: async (context) => {
221
+ return ok(context.params)
222
+ },
223
+ })
224
+ ```
225
+
226
+ ---
227
+
228
+ # Middleware
229
+
230
+ ```ts
231
+ import type {
232
+ Middleware,
233
+ } from 'lambda-pipe'
234
+
235
+ const loggerMiddleware: Middleware = {
236
+ onRequest: async (context, next) => {
237
+ console.log(context.req.requestId)
238
+
239
+ await next()
240
+ },
241
+
242
+ onResponse: async () => {
243
+ console.log('response sent')
244
+ },
245
+
246
+ onError: async (error) => {
247
+ console.error(error)
248
+ },
249
+ }
250
+ ```
251
+
252
+ ---
253
+
254
+ # Middleware Lifecycle
255
+
256
+ | Hook | Description |
257
+ | --------------- | ---------------------------------- |
258
+ | `onInit` | Runs once during cold start |
259
+ | `onRequest` | Runs before handler |
260
+ | `onResponse` | Runs before response serialization |
261
+ | `afterResponse` | Runs after response serialization |
262
+ | `onError` | Runs on request failure |
263
+
264
+ ---
265
+
266
+ # Plugins
267
+
268
+ ```ts
269
+ import type {
270
+ Plugin,
271
+ } from 'lambda-pipe'
272
+
273
+ const authPlugin: Plugin<{
274
+ auth: {
275
+ can: (scope: string) => boolean
276
+ }
277
+ }> = {
278
+ name: 'auth',
279
+
280
+ setup: async () => {
281
+ console.log('setup once')
282
+ },
283
+
284
+ onColdStart: async () => {
285
+ console.log('cold start')
286
+ },
287
+
288
+ extend: () => {
289
+ return {
290
+ auth: {
291
+ can: (scope) => {
292
+ return scope === 'read:user'
293
+ },
294
+ },
295
+ }
296
+ },
297
+ }
298
+ ```
299
+
300
+ ---
301
+
302
+ # Plugin Context Injection
303
+
304
+ ```ts
305
+ import {
306
+ defineRoute,
307
+ ok,
308
+ } from 'lambda-pipe'
309
+
310
+ type User = {
311
+ id: string
312
+ }
313
+
314
+ type PluginContext = {
315
+ auth: {
316
+ can: (scope: string) => boolean
317
+ }
318
+ }
319
+
320
+ export default defineRoute<
321
+ {},
322
+ User,
323
+ PluginContext
324
+ >({
325
+ plugins: [authPlugin],
326
+
327
+ handler: async (context) => {
328
+ context.auth.can('read:user')
329
+
330
+ return ok()
331
+ },
332
+ })
333
+ ```
334
+
335
+ ---
336
+
337
+ # Authentication
338
+
339
+ ```ts
340
+ import {
341
+ defineRoute,
342
+ ok,
343
+ } from 'lambda-pipe'
344
+
345
+ export default defineRoute({
346
+ authenticate: async (token) => {
347
+ if (token !== 'valid-token') {
348
+ return null
349
+ }
350
+
351
+ return {
352
+ user: {
353
+ id: 'u1',
354
+ },
355
+
356
+ scope: ['read:user'],
357
+ }
358
+ },
359
+
360
+ handler: async (context) => {
361
+ return ok({
362
+ user: context.req.user,
363
+ })
364
+ },
365
+ })
366
+ ```
367
+
368
+ ---
369
+
370
+ # Request Context
371
+
372
+ | Property | Description |
373
+ | ------------------------ | ------------------ |
374
+ | `context.req.user` | authenticated user |
375
+ | `context.req.scope` | auth scopes |
376
+ | `context.req.metadata` | auth metadata |
377
+ | `context.req.requestId` | request id |
378
+ | `context.req.ip` | client ip |
379
+ | `context.req.userAgent` | request user agent |
380
+ | `context.req.cookies` | parsed cookies |
381
+ | `context.req.receivedAt` | request timestamp |
382
+
383
+ ---
384
+
385
+ # Execution Context
386
+
387
+ | Property | Description |
388
+ | -------------------------- | -------------------- |
389
+ | `context.exec.isColdStart` | lambda cold start |
390
+ | `context.exec.containerId` | runtime container id |
391
+ | `context.exec.cache` | in-memory cache |
392
+
393
+ ---
394
+
395
+ # Cache Example
396
+
397
+ ```ts
398
+ const cacheKey = `user:${context.params.id}`
399
+
400
+ const user = await context.exec.cache.getOrSet(
401
+ cacheKey,
402
+ async () => {
403
+ return db.user.findUnique({
404
+ where: {
405
+ id: context.params.id,
406
+ },
407
+ })
408
+ },
409
+ 10000,
410
+ )
411
+
412
+ return ok(user)
413
+ ```
414
+
415
+ ---
416
+
417
+ # Response Helpers
418
+
419
+ ```ts
420
+ import {
421
+ ok,
422
+ json,
423
+ badRequest,
424
+ unauthorized,
425
+ forbidden,
426
+ } from 'lambda-pipe'
427
+ ```
428
+
429
+ | Helper | Status |
430
+ | ---------------- | ------ |
431
+ | `ok()` | 200 |
432
+ | `json()` | custom |
433
+ | `badRequest()` | 400 |
434
+ | `unauthorized()` | 401 |
435
+ | `forbidden()` | 403 |
436
+
437
+ ---
438
+
439
+ # Context Response API
440
+
441
+ ```ts
442
+ context.setHeader('x-request-id', '123')
443
+
444
+ context.status(200)
445
+
446
+ context.setCookie('session', 'abc', {
447
+ httpOnly: true,
448
+ secure: true,
449
+ path: '/',
450
+ maxAge: 3600,
451
+ })
452
+ ```
453
+
454
+ ---
455
+
456
+ # Error Handling
457
+
458
+ ```ts
459
+ import {
460
+ HttpError,
461
+ } from 'lambda-pipe'
462
+
463
+ throw new HttpError(
464
+ 403,
465
+ 'Forbidden',
466
+ )
467
+ ```
468
+
469
+ ---
470
+
471
+ # Built-in Errors
472
+
473
+ ```ts
474
+ import {
475
+ UnauthorizedError,
476
+ ForbiddenError,
477
+ ValidationError,
478
+ NotFoundError,
479
+ } from 'lambda-pipe'
480
+ ```
481
+
482
+ ---
483
+
484
+ # AWS Lambda Example
485
+
486
+ ```ts
487
+ // src/functions/user.ts
488
+
489
+ import {
490
+ lambdaHandler,
491
+ } from 'lambda-pipe/runtime'
492
+
493
+ import route from '../routes/user'
494
+
495
+ export const handler =
496
+ lambdaHandler(route)
497
+ ```
498
+
499
+ ---
500
+
501
+ # SQS Job Example
502
+
503
+ ```ts
504
+ import {
505
+ defineJob,
506
+ sqsHandler,
507
+ } from 'lambda-pipe/jobs'
508
+
509
+ const userCreatedJob = defineJob({
510
+ name: 'user-created',
511
+
512
+ handler: async (payload: {
513
+ id: string
514
+ }) => {
515
+ console.log(payload.id)
516
+ },
517
+ })
518
+
519
+ export const handler =
520
+ sqsHandler(userCreatedJob)
521
+ ```
522
+
523
+ ---
524
+
525
+ # Utilities
526
+
527
+ ```ts
528
+ import {
529
+ normalizeHeaders,
530
+ parseCookies,
531
+ extractBearerToken,
532
+ safeJsonParse,
533
+ } from 'lambda-pipe'
534
+ ```
535
+
536
+ ---
537
+
538
+ # Route Example
539
+
540
+ ```ts
541
+ import {
542
+ defineRoute,
543
+ ok,
544
+ HttpError,
545
+ } from 'lambda-pipe'
546
+
547
+ import type {
548
+ Middleware,
549
+ Plugin,
550
+ } from 'lambda-pipe'
551
+
552
+ type User = {
553
+ id: string
554
+ }
555
+
556
+ type PluginContext = {
557
+ auth: {
558
+ can: (
559
+ scope: string,
560
+ ) => boolean
561
+ }
562
+ }
563
+
564
+ /**
565
+ * plugin
566
+ */
567
+
568
+ const authPlugin: Plugin<
569
+ PluginContext
570
+ > = {
571
+ name: 'auth',
572
+
573
+ extend: () => ({
574
+ auth: {
575
+ can: (scope: string) =>
576
+ scope === 'read:user',
577
+ },
578
+ }),
579
+ }
580
+
581
+ /**
582
+ * middleware
583
+ */
584
+
585
+ const authMiddleware: Middleware<
586
+ any,
587
+ User,
588
+ PluginContext
589
+ > = {
590
+ onRequest: async (
591
+ context,
592
+ next,
593
+ ) => {
594
+ if (!context.req.user) {
595
+ throw new HttpError(
596
+ 401,
597
+ 'Unauthorized',
598
+ )
599
+ }
600
+
601
+ await next()
602
+ },
603
+ }
604
+
605
+ /**
606
+ * route
607
+ */
608
+
609
+ export default defineRoute<
610
+ {
611
+ params: {
612
+ id: string
613
+ }
614
+ },
615
+ User,
616
+ PluginContext
617
+ >({
618
+ method: 'GET',
619
+
620
+ path: '/users/:id',
621
+
622
+ plugins: [authPlugin],
623
+
624
+ middleware: [
625
+ authMiddleware,
626
+ ],
627
+
628
+ authenticate: async (token) => {
629
+ if (token !== 'valid-token') {
630
+ return null
631
+ }
632
+
633
+ return {
634
+ user: {
635
+ id: 'u1',
636
+ },
637
+
638
+ scope: ['read:user'],
639
+ }
640
+ },
641
+
642
+ handler: async (context) => {
643
+ return ok({
644
+ id: context.params.id,
645
+
646
+ canRead:
647
+ context.auth.can(
648
+ 'read:user',
649
+ ),
650
+
651
+ requestId:
652
+ context.req.requestId,
653
+ })
654
+ },
655
+ })
656
+ ```
657
+
658
+ ---
659
+
660
+ # Development Runtime
661
+
662
+ Local development server that simulates AWS Lambda execution using a Fastify-based runtime.
663
+
664
+ It converts HTTP requests into Lambda-like events and executes routes with full middleware + plugin lifecycle.
665
+
666
+ ```ts
667
+ import { createDevApp } from 'lambda-pipe/cli'
668
+
669
+ await createDevApp({
670
+ port: 3000,
671
+ routesDir: 'src/routes',
672
+ })
673
+
674
+ ```
675
+
676
+ ```bash
677
+ npm run dev
678
+ ```
679
+
680
+ See: [DOC](https://github.com/delpikye-v/lambda-pipe/blob/main/docs/dev-server.md
681
+ )
682
+
683
+ ---
684
+
685
+ # WebSocket Runtime
686
+
687
+ `lambda-pipe` supports AWS API Gateway WebSocket APIs with a simple handler abstraction.
688
+
689
+ It handles:
690
+
691
+ - $connect
692
+ - $disconnect
693
+ - custom route messages
694
+
695
+ ```ts
696
+ import { wsHandler } from 'lambda-pipe/websocket'
697
+
698
+ export const handler = wsHandler({
699
+ connect: async () => {
700
+ console.log('client connected')
701
+ },
702
+
703
+ message: async (ctx) => {
704
+ return {
705
+ statusCode: 200,
706
+ body: JSON.stringify({ ok: true }),
707
+ }
708
+ },
709
+
710
+ disconnect: async () => {
711
+ console.log('client disconnected')
712
+ },
713
+ })
714
+ ```
715
+
716
+ See: [DOC](https://github.com/delpikye-v/lambda-pipe/blob/main/docs/websocket.md)
717
+
718
+ ---
719
+
720
+ # Philosophy
721
+
722
+ ```text
723
+ You control:
724
+ - plugins
725
+ - middleware
726
+ - runtime
727
+ - architecture
728
+
729
+ lambda-pipe controls:
730
+ - execution
731
+ - lifecycle
732
+ - typing
733
+ - orchestration
734
+ ```
735
+
736
+ ---
737
+
738
+ # Design Principles
739
+
740
+ * Explicit execution
741
+ * Typed runtime
742
+ * Composition over inheritance
743
+ * Runtime agnostic
744
+ * Serverless-first architecture
745
+
746
+ ---
747
+
748
+ # When to Use
749
+
750
+ * Serverless APIs
751
+ * Lambda backends
752
+ * Internal platforms
753
+ * Runtime adapters
754
+ * Typed middleware systems
755
+ * Plugin-based architecture
756
+
757
+ ---
758
+
759
+ # License
760
+
761
+ MIT