lambder 6.0.1 → 7.0.1

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 (195) hide show
  1. package/CHANGELOG.md +2316 -0
  2. package/README.md +60 -33
  3. package/dist/api/LambderApiAnswer.d.ts +40 -0
  4. package/dist/api/LambderApiAnswer.js +19 -0
  5. package/dist/api/LambderApiCallContext.d.ts +38 -0
  6. package/dist/api/LambderApiCallContext.js +13 -0
  7. package/dist/api/LambderApiDefinition.d.ts +18 -0
  8. package/dist/api/LambderApiDefinition.js +1 -0
  9. package/dist/api/LambderApiEnvelope.d.ts +67 -0
  10. package/dist/api/LambderApiEnvelope.js +180 -0
  11. package/dist/api/LambderApiGuards.d.ts +302 -0
  12. package/dist/api/LambderApiGuards.js +134 -0
  13. package/dist/api/LambderApiIdempotency.d.ts +122 -0
  14. package/dist/api/LambderApiIdempotency.js +330 -0
  15. package/dist/api/LambderApiPipeline.d.ts +134 -0
  16. package/dist/api/LambderApiPipeline.js +221 -0
  17. package/dist/api/LambderApiPolicyEngine.d.ts +36 -0
  18. package/dist/api/LambderApiPolicyEngine.js +77 -0
  19. package/dist/api/LambderApiRateLimits.d.ts +206 -0
  20. package/dist/api/LambderApiRateLimits.js +239 -0
  21. package/dist/api/LambderApiRequest.d.ts +101 -0
  22. package/dist/api/LambderApiRequest.js +129 -0
  23. package/dist/api/LambderApiValidationRefusal.d.ts +32 -0
  24. package/dist/api/LambderApiValidationRefusal.js +40 -0
  25. package/dist/client/LambderCaller.d.ts +62 -55
  26. package/dist/client/LambderCaller.js +147 -90
  27. package/dist/client/lambderFetchTransport.d.ts +9 -0
  28. package/dist/client/lambderFetchTransport.js +71 -0
  29. package/dist/client.d.ts +20 -10
  30. package/dist/client.js +11 -5
  31. package/dist/core/Lambder.d.ts +117 -253
  32. package/dist/core/Lambder.js +374 -341
  33. package/dist/core/LambderContext.d.ts +54 -44
  34. package/dist/core/LambderContext.js +41 -110
  35. package/dist/core/LambderCreateOptions.d.ts +285 -0
  36. package/dist/core/LambderCreateOptions.js +44 -0
  37. package/dist/core/LambderFiles.d.ts +1 -45
  38. package/dist/core/LambderFiles.js +18 -38
  39. package/dist/core/LambderIndexHtml.d.ts +37 -0
  40. package/dist/core/LambderIndexHtml.js +87 -0
  41. package/dist/core/LambderPolicyBuilders.d.ts +17 -0
  42. package/dist/core/LambderPolicyBuilders.js +16 -0
  43. package/dist/core/LambderPublicFiles.d.ts +5 -2
  44. package/dist/core/LambderPublicFiles.js +7 -2
  45. package/dist/core/LambderResolver.d.ts +8 -6
  46. package/dist/core/LambderResponse.d.ts +29 -11
  47. package/dist/core/LambderResponse.js +96 -49
  48. package/dist/core/LambderResponseBuilder.d.ts +18 -14
  49. package/dist/core/LambderResponseBuilder.js +19 -25
  50. package/dist/core/LambderRouting.d.ts +18 -7
  51. package/dist/core/LambderRouting.js +17 -7
  52. package/dist/core/LambderTemplatingEngine.d.ts +0 -62
  53. package/dist/core/LambderTemplatingEngine.js +7 -3
  54. package/dist/index.d.ts +85 -32
  55. package/dist/index.js +44 -16
  56. package/dist/invoke/LambderInvokeCaller.d.ts +46 -139
  57. package/dist/invoke/LambderInvokeCaller.js +140 -335
  58. package/dist/invoke/LambderInvokeOutcome.d.ts +165 -0
  59. package/dist/invoke/LambderInvokeOutcome.js +129 -0
  60. package/dist/invoke/LambderLambdaEvent.d.ts +81 -0
  61. package/dist/invoke/LambderLambdaEvent.js +187 -0
  62. package/dist/invoke/lambderHandlerTransport.d.ts +36 -0
  63. package/dist/invoke/lambderHandlerTransport.js +89 -0
  64. package/dist/mock/LambderMockApp.d.ts +352 -0
  65. package/dist/mock/LambderMockApp.js +815 -0
  66. package/dist/mock/LambderMockBrowserCookies.d.ts +55 -0
  67. package/dist/mock/LambderMockBrowserCookies.js +76 -0
  68. package/dist/mock/LambderMockCallRecorder.d.ts +85 -0
  69. package/dist/mock/LambderMockCallRecorder.js +183 -0
  70. package/dist/mock/LambderMockCreateOptions.d.ts +161 -0
  71. package/dist/mock/LambderMockCreateOptions.js +9 -0
  72. package/dist/mock/LambderMockEntryRegistry.d.ts +52 -0
  73. package/dist/mock/LambderMockEntryRegistry.js +126 -0
  74. package/dist/mock/LambderMockFailureInjector.d.ts +60 -0
  75. package/dist/mock/LambderMockFailureInjector.js +138 -0
  76. package/dist/mock/LambderMockTypes.d.ts +421 -0
  77. package/dist/mock/LambderMockTypes.js +8 -0
  78. package/dist/mock/lambderMockConsoleLogger.d.ts +16 -0
  79. package/dist/mock/lambderMockConsoleLogger.js +35 -0
  80. package/dist/mock/lambderMockInvokeTransport.d.ts +50 -0
  81. package/dist/mock/lambderMockInvokeTransport.js +52 -0
  82. package/dist/mock/lambderMockMswHandler.d.ts +99 -0
  83. package/dist/mock/lambderMockMswHandler.js +126 -0
  84. package/dist/mock.d.ts +34 -0
  85. package/dist/mock.js +27 -0
  86. package/dist/session/LambderSessionController.d.ts +199 -30
  87. package/dist/session/LambderSessionController.js +396 -82
  88. package/dist/session/LambderSessionCrypto.d.ts +66 -0
  89. package/dist/session/LambderSessionCrypto.js +101 -0
  90. package/dist/session/LambderSessionManager.d.ts +118 -80
  91. package/dist/session/LambderSessionManager.js +212 -184
  92. package/dist/shared/LambderI18n.d.ts +6 -6
  93. package/dist/shared/LambderI18n.js +1 -1
  94. package/dist/shared/contracts/LambderFileSource.d.ts +33 -0
  95. package/dist/shared/contracts/LambderFileSource.js +19 -0
  96. package/dist/shared/contracts/LambderIdempotencyStore.d.ts +66 -0
  97. package/dist/shared/contracts/LambderIdempotencyStore.js +12 -0
  98. package/dist/shared/contracts/LambderRateLimiter.d.ts +71 -0
  99. package/dist/shared/contracts/LambderRateLimiter.js +24 -0
  100. package/dist/shared/contracts/LambderSessionStore.d.ts +72 -0
  101. package/dist/shared/contracts/LambderSessionStore.js +13 -0
  102. package/dist/shared/transport/LambderApiTransport.d.ts +139 -0
  103. package/dist/shared/transport/LambderApiTransport.js +65 -0
  104. package/dist/shared/transport/LambderCookieJar.d.ts +121 -0
  105. package/dist/shared/transport/LambderCookieJar.js +246 -0
  106. package/dist/shared/transport/lambderCookieJarTransport.d.ts +30 -0
  107. package/dist/shared/transport/lambderCookieJarTransport.js +60 -0
  108. package/dist/shared/util/LambderBase64.d.ts +10 -0
  109. package/dist/shared/util/LambderBase64.js +27 -0
  110. package/dist/shared/util/LambderCallAbort.d.ts +62 -0
  111. package/dist/shared/util/LambderCallAbort.js +80 -0
  112. package/dist/shared/util/LambderClientIp.d.ts +32 -0
  113. package/dist/shared/util/LambderClientIp.js +56 -0
  114. package/dist/shared/util/LambderExpiringMap.d.ts +119 -0
  115. package/dist/shared/util/LambderExpiringMap.js +217 -0
  116. package/dist/shared/util/LambderKeyFields.d.ts +32 -0
  117. package/dist/shared/util/LambderKeyFields.js +34 -0
  118. package/dist/shared/util/LambderNodeModules.d.ts +9 -0
  119. package/dist/shared/util/LambderNodeModules.js +39 -0
  120. package/dist/shared/util/LambderOptionChecks.d.ts +17 -0
  121. package/dist/shared/util/LambderOptionChecks.js +33 -0
  122. package/dist/shared/util/LambderResponseBrand.d.ts +20 -0
  123. package/dist/shared/util/LambderResponseBrand.js +18 -0
  124. package/dist/shared/util/LambderTextDigest.d.ts +17 -0
  125. package/dist/shared/util/LambderTextDigest.js +34 -0
  126. package/dist/shared/util/LambderTypeUtilities.d.ts +33 -0
  127. package/dist/shared/util/LambderTypeUtilities.js +8 -0
  128. package/dist/shared/wire/LambderAnswerHeaders.d.ts +60 -0
  129. package/dist/shared/wire/LambderAnswerHeaders.js +94 -0
  130. package/dist/shared/wire/LambderApiContract.d.ts +129 -0
  131. package/dist/shared/wire/LambderApiOptionValues.d.ts +39 -0
  132. package/dist/shared/wire/LambderApiOptionValues.js +11 -0
  133. package/dist/shared/wire/LambderApiOutcome.d.ts +128 -0
  134. package/dist/shared/{LambderApiOutcome.js → wire/LambderApiOutcome.js} +16 -9
  135. package/dist/shared/{LambderApiError.d.ts → wire/LambderApiRefusal.d.ts} +48 -26
  136. package/dist/shared/{LambderApiError.js → wire/LambderApiRefusal.js} +13 -11
  137. package/dist/shared/wire/LambderCallOptions.d.ts +171 -0
  138. package/dist/shared/wire/LambderCallOptions.js +17 -0
  139. package/dist/shared/{LambderCompressionCodec.d.ts → wire/LambderCompressionCodec.d.ts} +10 -6
  140. package/dist/shared/{LambderCompressionCodec.js → wire/LambderCompressionCodec.js} +67 -23
  141. package/dist/shared/{LambderCompressionOption.d.ts → wire/LambderCompressionOption.d.ts} +1 -1
  142. package/dist/shared/{LambderCompressionOption.js → wire/LambderCompressionOption.js} +3 -4
  143. package/dist/shared/{LambderCrashDetail.d.ts → wire/LambderCrashDetail.d.ts} +10 -0
  144. package/dist/shared/{LambderCrashDetail.js → wire/LambderCrashDetail.js} +30 -0
  145. package/dist/shared/wire/LambderHttpStatus.d.ts +12 -0
  146. package/dist/shared/wire/LambderHttpStatus.js +1 -0
  147. package/dist/shared/{LambderRequestPayload.d.ts → wire/LambderRequestPayload.d.ts} +25 -17
  148. package/dist/shared/{LambderRequestPayload.js → wire/LambderRequestPayload.js} +29 -52
  149. package/dist/shared/wire/LambderSessionCookieNames.d.ts +9 -0
  150. package/dist/shared/wire/LambderSessionCookieNames.js +9 -0
  151. package/dist/stores/LambderDdbCache.d.ts +12 -9
  152. package/dist/stores/LambderDdbCache.js +56 -47
  153. package/dist/stores/{LambderDdbIdempotency.d.ts → LambderDdbIdempotencyStore.d.ts} +41 -31
  154. package/dist/stores/LambderDdbIdempotencyStore.js +319 -0
  155. package/dist/stores/LambderDdbRateLimiter.d.ts +30 -49
  156. package/dist/stores/LambderDdbRateLimiter.js +47 -45
  157. package/dist/stores/LambderDdbSdk.d.ts +83 -6
  158. package/dist/stores/LambderDdbSdk.js +83 -2
  159. package/dist/stores/LambderDdbSessionStore.d.ts +65 -0
  160. package/dist/stores/LambderDdbSessionStore.js +161 -0
  161. package/dist/stores/LambderHttpFileSource.d.ts +1 -1
  162. package/dist/stores/LambderHttpFileSource.js +10 -1
  163. package/dist/stores/LambderLocalFileSource.d.ts +15 -0
  164. package/dist/stores/LambderLocalFileSource.js +28 -0
  165. package/dist/stores/LambderMemoryIdempotencyStore.d.ts +63 -0
  166. package/dist/stores/LambderMemoryIdempotencyStore.js +113 -0
  167. package/dist/stores/LambderMemoryRateLimiter.d.ts +34 -0
  168. package/dist/stores/LambderMemoryRateLimiter.js +64 -0
  169. package/dist/stores/LambderMemorySessionStore.d.ts +48 -0
  170. package/dist/stores/LambderMemorySessionStore.js +74 -0
  171. package/dist/stores/LambderS3FileSource.d.ts +1 -1
  172. package/dist/stores/LambderS3FileSource.js +1 -1
  173. package/package.json +26 -24
  174. package/dist/client/LambderMSW.d.ts +0 -69
  175. package/dist/client/LambderMSW.js +0 -121
  176. package/dist/policies/LambderApiGuards.d.ts +0 -256
  177. package/dist/policies/LambderApiGuards.js +0 -94
  178. package/dist/policies/LambderApiIdempotency.d.ts +0 -58
  179. package/dist/policies/LambderApiIdempotency.js +0 -219
  180. package/dist/policies/LambderApiPolicies.d.ts +0 -42
  181. package/dist/policies/LambderApiPolicies.js +0 -52
  182. package/dist/policies/LambderApiRateLimits.d.ts +0 -132
  183. package/dist/policies/LambderApiRateLimits.js +0 -119
  184. package/dist/shared/LambderApiContract.d.ts +0 -57
  185. package/dist/shared/LambderApiOutcome.d.ts +0 -69
  186. package/dist/shared/LambderCallOptions.d.ts +0 -71
  187. package/dist/shared/LambderCallOptions.js +0 -16
  188. package/dist/shared/node-polyfills.d.ts +0 -4
  189. package/dist/shared/node-polyfills.js +0 -58
  190. package/dist/stores/LambderDdbIdempotency.js +0 -229
  191. package/dist/testing.d.ts +0 -9
  192. package/dist/testing.js +0 -8
  193. /package/dist/shared/{LambderApiContract.js → wire/LambderApiContract.js} +0 -0
  194. /package/dist/{core → shared/wire}/LambderCookie.d.ts +0 -0
  195. /package/dist/{core → shared/wire}/LambderCookie.js +0 -0
@@ -1,121 +0,0 @@
1
- import { COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, decompressPayloadGzip } from '../shared/LambderRequestPayload.js';
2
- export default class LambderMSW {
3
- apiPath;
4
- apiVersion;
5
- http;
6
- HttpResponse;
7
- constructor({ apiPath, apiVersion, msw, }) {
8
- this.apiPath = apiPath;
9
- this.apiVersion = apiVersion;
10
- if (!msw?.http || !msw?.HttpResponse) {
11
- throw new Error('LambderMSW requires the msw module: new LambderMSW({ apiPath, msw: await import("msw") }). Install it with: npm install msw --save-dev');
12
- }
13
- this.http = msw.http;
14
- this.HttpResponse = msw.HttpResponse;
15
- }
16
- /**
17
- * Mock an API endpoint with MSW
18
- * @param apiName - The name of the API to mock
19
- * @param handler - Function that returns the mock payload
20
- * @param options - Additional response options (session expired, version expired, etc.)
21
- *
22
- * Note: TypeScript will check that the return type is assignable to the output type,
23
- * but due to structural typing, extra properties are allowed. Enable strict checks
24
- * in your tsconfig.json with "noUncheckedIndexedAccess" and "exactOptionalPropertyTypes"
25
- * for better type safety.
26
- */
27
- mockApi(apiName, handler, options) {
28
- return this.http.post(this.apiPath, async ({ request }) => {
29
- let body;
30
- try {
31
- const clonedRequest = request.clone();
32
- body = await clonedRequest.json();
33
- }
34
- catch (parseErr) {
35
- // If JSON parsing fails, return undefined to let other handlers try
36
- console.warn("LambderMSW: Failed to parse request body as JSON");
37
- return;
38
- }
39
- // Check if body is valid and has apiName
40
- if (!body || typeof body.apiName !== 'string') {
41
- return;
42
- }
43
- if (body.apiName !== apiName) {
44
- return;
45
- }
46
- // A caller with requestCompression on sends the payload gzipped;
47
- // mock handlers still receive the payload itself, and the wire
48
- // fields are consumed the way the server consumes them.
49
- if (typeof body[COMPRESSED_PAYLOAD_GZ_FIELD] === 'string') {
50
- try {
51
- body.payload = await decompressPayloadGzip(body[COMPRESSED_PAYLOAD_GZ_FIELD]);
52
- }
53
- catch {
54
- console.warn("LambderMSW: Failed to decompress the request payload");
55
- return;
56
- }
57
- delete body[COMPRESSED_PAYLOAD_GZ_FIELD];
58
- delete body[COMPRESSED_PAYLOAD_BYTES_FIELD];
59
- }
60
- try {
61
- // Add artificial delay if specified
62
- if (options?.delay) {
63
- await new Promise(resolve => setTimeout(resolve, options.delay));
64
- }
65
- // Call the handler with the payload from the request
66
- const payload = await handler(body.payload);
67
- const response = {
68
- apiVersion: this.apiVersion,
69
- payload,
70
- ...(options?.versionExpired ? { versionExpired: options.versionExpired } : {}),
71
- ...(options?.sessionExpired ? { sessionExpired: options.sessionExpired } : {}),
72
- ...(options?.notAuthorized ? { notAuthorized: options.notAuthorized } : {}),
73
- ...(options?.message ? { message: options.message } : {}),
74
- ...(options?.errorMessage ? { errorMessage: options.errorMessage } : {}),
75
- ...(options?.logList?.length ? { logList: options.logList } : {}),
76
- };
77
- return this.HttpResponse.json(response);
78
- }
79
- catch (err) {
80
- // Only handle errors that occur during handler execution for matched APIs
81
- console.error("Error in LambderMSW handler for", apiName, ":", err);
82
- const errorResponse = {
83
- apiVersion: this.apiVersion,
84
- payload: null,
85
- errorMessage: err.message || "Unknown error",
86
- };
87
- return this.HttpResponse.json(errorResponse, { status: 500 });
88
- }
89
- });
90
- }
91
- /**
92
- * Mock an API endpoint that returns a session expired error
93
- */
94
- mockSessionExpired(apiName) {
95
- return this.mockApi(apiName, async () => null, { sessionExpired: true });
96
- }
97
- /**
98
- * Mock an API endpoint that returns a version expired error
99
- */
100
- mockVersionExpired(apiName) {
101
- return this.mockApi(apiName, async () => null, { versionExpired: true });
102
- }
103
- /**
104
- * Mock an API endpoint that returns a not authorized error
105
- */
106
- mockNotAuthorized(apiName) {
107
- return this.mockApi(apiName, async () => null, { notAuthorized: true });
108
- }
109
- /**
110
- * Mock an API endpoint that returns an error message
111
- */
112
- mockError(apiName, errorMessage) {
113
- return this.mockApi(apiName, async () => null, { errorMessage });
114
- }
115
- /**
116
- * Mock an API endpoint with a custom message
117
- */
118
- mockWithMessage(apiName, handler, message) {
119
- return this.mockApi(apiName, handler, { message });
120
- }
121
- }
@@ -1,256 +0,0 @@
1
- import type { z } from "zod";
2
- import type { LambderRenderContext, LambderSessionRenderContext } from "../core/LambderContext.js";
3
- import type LambderResolver from "../core/LambderResolver.js";
4
- import type { LambderResponse } from "../core/LambderResponse.js";
5
- /**
6
- * A named guard, run before the API's own input validation. Three input
7
- * modes:
8
- *
9
- * - `apiInput`: the guard checks fields of the API's OWN payload. The slice
10
- * is validated against the raw payload before `handler` runs and handed to
11
- * it typed. The API's input schema stays the owner of those fields:
12
- * declaring the guard on an API whose schema does not carry them is a
13
- * compile error.
14
- * - `guardInput`: the guard has its own value the client sends SEPARATELY,
15
- * outside the API payload, via the caller's options.guardInputs[name].
16
- * The requirement lands on the API's contract (`guardInputs`), so the
17
- * typed caller refuses to compile a call that does not send it. The API
18
- * payload and handler never see the value.
19
- * - neither: the guard reads only the context.
20
- *
21
- * Orthogonally, a guard may also:
22
- *
23
- * - declare `session: true`: the guard needs ctx.session, so it is only
24
- * declarable on addSessionApi (compile error and startup assert on public
25
- * APIs) and its handler receives the session-typed context.
26
- * - take a PARAMETER: annotate a 4th handler argument
27
- * (`(ctx, payload, res, param: YourType) => ...`) and APIs pass the value
28
- * in their declaration: `guards: { yourGuard: paramValue }`. The value is
29
- * trusted registration-time code (never client data), typed per guard.
30
- * - RETURN a value: whatever the handler returns (awaited) is attached to
31
- * the API handler's context as `ctx.guardData[guardName]`, fully typed.
32
- * Guards that return nothing never appear in guardData.
33
- *
34
- * A validation failure answers like the API's own input validation (the
35
- * app's setApiInputValidationErrorHandler when set, else the standard 422),
36
- * and the handler refuses by throwing (typically refuse()/LambderApiError).
37
- * Build with lambderGuard() so the handler's payload/ctx/param types line up.
38
- */
39
- export type LambderApiGuard<TInput extends z.ZodType = z.ZodType, TParam = any, TOutput = any> = {
40
- apiInput: TInput;
41
- guardInput?: undefined;
42
- session?: boolean;
43
- handler: (ctx: any, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
44
- } | {
45
- guardInput: TInput;
46
- apiInput?: undefined;
47
- session?: boolean;
48
- handler: (ctx: any, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
49
- } | {
50
- apiInput?: undefined;
51
- guardInput?: undefined;
52
- session?: boolean;
53
- handler: (ctx: any, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
54
- };
55
- type GuardCtx = LambderRenderContext;
56
- type GuardSessionCtx = LambderSessionRenderContext<any, any>;
57
- /**
58
- * Builder that ties the handler's payload, context, param, and output types
59
- * together inside one literal. Returns the exact shape so type extraction
60
- * (mode, session, param, output) works downstream. The param type is
61
- * inferred from the handler's 4th argument annotation; the output from its
62
- * return type.
63
- */
64
- export declare function lambderGuard<TInput extends z.ZodType, TParam = undefined, TOutput = void>(guard: {
65
- apiInput: TInput;
66
- session: true;
67
- handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
68
- }): {
69
- apiInput: TInput;
70
- guardInput?: undefined;
71
- session: true;
72
- handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
73
- };
74
- export declare function lambderGuard<TInput extends z.ZodType, TParam = undefined, TOutput = void>(guard: {
75
- apiInput: TInput;
76
- handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
77
- }): {
78
- apiInput: TInput;
79
- guardInput?: undefined;
80
- session?: undefined;
81
- handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
82
- };
83
- export declare function lambderGuard<TInput extends z.ZodType, TParam = undefined, TOutput = void>(guard: {
84
- guardInput: TInput;
85
- session: true;
86
- handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
87
- }): {
88
- guardInput: TInput;
89
- apiInput?: undefined;
90
- session: true;
91
- handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
92
- };
93
- export declare function lambderGuard<TInput extends z.ZodType, TParam = undefined, TOutput = void>(guard: {
94
- guardInput: TInput;
95
- handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
96
- }): {
97
- guardInput: TInput;
98
- apiInput?: undefined;
99
- session?: undefined;
100
- handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
101
- };
102
- export declare function lambderGuard<TParam = undefined, TOutput = void>(guard: {
103
- session: true;
104
- handler: (ctx: GuardSessionCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
105
- }): {
106
- apiInput?: undefined;
107
- guardInput?: undefined;
108
- session: true;
109
- handler: (ctx: GuardSessionCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
110
- };
111
- export declare function lambderGuard<TParam = undefined, TOutput = void>(guard: {
112
- handler: (ctx: GuardCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
113
- }): {
114
- apiInput?: undefined;
115
- guardInput?: undefined;
116
- session?: undefined;
117
- handler: (ctx: GuardCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
118
- };
119
- /** The param type a guard's handler declares as its 4th argument; undefined for paramless guards. */
120
- type LambderGuardParamOf<G> = G extends {
121
- handler: (...args: infer A) => any;
122
- } ? (A extends [any, any, any, infer P, ...any[]] ? P : undefined) : undefined;
123
- /** What a guard's handler returns (awaited); void for check-only guards. */
124
- type LambderGuardOutputOf<G> = G extends {
125
- handler: (...args: any[]) => infer R;
126
- } ? Awaited<R> : never;
127
- /** Per-guard metadata carried on the Lambder instance: input mode, session requirement, param type, output type. */
128
- export type LambderGuardMeta<G> = (G extends {
129
- apiInput: infer S extends z.ZodType;
130
- } ? {
131
- apiInput: z.output<S>;
132
- } : G extends {
133
- guardInput: infer S extends z.ZodType;
134
- } ? {
135
- guardInput: z.output<S>;
136
- } : {}) & (G extends {
137
- session: true;
138
- } ? {
139
- session: true;
140
- } : {}) & {
141
- param: LambderGuardParamOf<G>;
142
- output: LambderGuardOutputOf<G>;
143
- };
144
- export type LambderGuardMetaMap<TGuards> = {
145
- [K in keyof TGuards]: LambderGuardMeta<TGuards[K]>;
146
- };
147
- /** Guard names referenced by a guards option, whichever of its three forms is used. */
148
- type NamesIn<TOpt> = TOpt extends string ? TOpt : TOpt extends readonly (infer N extends string)[] ? N : TOpt extends object ? keyof TOpt & string : never;
149
- type LambderGuardNameIfPayloadOk<TGuards, K extends keyof TGuards, TPayload> = TGuards[K] extends {
150
- apiInput: infer R;
151
- } ? (TPayload extends R ? K : never) : K;
152
- /**
153
- * Guard names an API may declare: apiInput-mode guards only when the API's
154
- * payload carries their fields, session guards only on session APIs.
155
- */
156
- export type LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession extends boolean = true> = {
157
- [K in keyof TGuards]: TGuards[K] extends {
158
- session: true;
159
- } ? (TIncludeSession extends true ? LambderGuardNameIfPayloadOk<TGuards, K, TPayload> : never) : LambderGuardNameIfPayloadOk<TGuards, K, TPayload>;
160
- }[keyof TGuards] & string;
161
- /** The allowed guard names whose handler takes no param (usable in the string/array forms). */
162
- export type LambderParamlessGuardNames<TGuards, TPayload, TIncludeSession extends boolean> = {
163
- [K in LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession> & keyof TGuards]: TGuards[K] extends {
164
- param: undefined;
165
- } ? K & string : never;
166
- }[LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession> & keyof TGuards];
167
- /** The map form's full shape: every declarable guard name, each carrying its own param type. */
168
- type LambderGuardsMap<TGuards, TPayload, TIncludeSession extends boolean> = {
169
- readonly [K in LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession> & keyof TGuards]?: TGuards[K] extends {
170
- param: undefined;
171
- } ? true : TGuards[K] extends {
172
- param: infer P;
173
- } ? P : true;
174
- };
175
- /**
176
- * The map form with AT LEAST ONE entry: the union, over every declarable
177
- * name, of "this one required and the rest optional".
178
- *
179
- * An all-optional map is inhabited by `{}`, which would let `guards: {}`
180
- * satisfy requireSessionApiGuards / requirePublicApiGuards at the type level
181
- * while declaring no guard at all: the option is present, so the required-field
182
- * check passes, and it normalizes to zero entries, so nothing runs. Requiring
183
- * the chosen key also rejects `{ theGuard: undefined }`, which an optional
184
- * property accepts and which would otherwise reach the guard's handler with an
185
- * undefined param.
186
- */
187
- type LambderNonEmptyGuardsMap<TGuards, TPayload, TIncludeSession extends boolean, TMap = LambderGuardsMap<TGuards, TPayload, TIncludeSession>> = {
188
- [K in keyof TMap]-?: Required<Pick<TMap, K>> & Omit<TMap, K>;
189
- }[keyof TMap];
190
- /**
191
- * The per-API `guards` option: one paramless guard name, a non-empty ordered
192
- * list of paramless names, or a non-empty object map that can carry each
193
- * guard's param (`true` enables a paramless guard). Map entries run in
194
- * insertion order.
195
- *
196
- * Every form is non-empty by construction, so declaring the option is always
197
- * declaring a guard. See LambderNonEmptyGuardsMap.
198
- */
199
- export type LambderGuardsOption<TGuards, TPayload, TIncludeSession extends boolean> = LambderParamlessGuardNames<TGuards, TPayload, TIncludeSession> | readonly [
200
- LambderParamlessGuardNames<TGuards, TPayload, TIncludeSession>,
201
- ...LambderParamlessGuardNames<TGuards, TPayload, TIncludeSession>[]
202
- ] | LambderNonEmptyGuardsMap<TGuards, TPayload, TIncludeSession>;
203
- /**
204
- * The typed ctx.guardData an API's handler sees: declared guards that return
205
- * a value, keyed by name. Check-only (void) guards never appear.
206
- */
207
- export type LambderGuardDataOf<TGuards, TOpt> = {
208
- [K in NamesIn<TOpt> & keyof TGuards as [
209
- TGuards[K] extends {
210
- output: infer O;
211
- } ? O : never
212
- ] extends [void] ? never : K & string]: TGuards[K] extends {
213
- output: infer O;
214
- } ? O : never;
215
- };
216
- type GuardInputsEntries<TGuards, TOpt> = {
217
- [K in Extract<NamesIn<TOpt>, keyof TGuards> as TGuards[K] extends {
218
- guardInput: any;
219
- } ? K : never]: TGuards[K] extends {
220
- guardInput: infer V;
221
- } ? V : never;
222
- };
223
- /** The guardInputs map an API's contract requires clients to send; never when no declared guard uses guardInput mode. */
224
- export type LambderGuardInputsOf<TGuards, TOpt> = keyof GuardInputsEntries<TGuards, TOpt> extends never ? never : GuardInputsEntries<TGuards, TOpt>;
225
- /** The guards option's runtime shape: a name, ordered names, or a name-to-param map. */
226
- export type LambderGuardsOptionValue = string | readonly string[] | Readonly<Record<string, unknown>>;
227
- /**
228
- * How a rejected input answers. Lambder binds this to its own decision
229
- * (setApiInputValidationErrorHandler when set, else the standard 422 body),
230
- * so the API's schema and every preflight slice refuse with one shape.
231
- */
232
- export type LambderInputValidationRefusal = (ctx: LambderRenderContext, resolver: LambderResolver, zodError: z.ZodError) => Promise<LambderResponse>;
233
- /**
234
- * Validate a preflight input slice (an apiInput slice of the raw payload, or
235
- * a guardInput value from the raw guardInputs map). Runs before the API's
236
- * own validation; a failure throws the response `onInvalid` decides, the
237
- * same one regular input validation answers. Shared with the rate-limit
238
- * engine's apiInput-keyed policies.
239
- */
240
- export declare const parsePreflightSlice: (input: z.ZodType, value: unknown, ctx: LambderRenderContext, resolver: LambderResolver, onInvalid: LambderInputValidationRefusal) => Promise<unknown>;
241
- /**
242
- * Runtime side of the guards subsystem: holds the defined guards, asserts
243
- * API registrations against them at startup, and executes an API's declared
244
- * guards during preflight. Composed into LambderApiPolicyEngine.
245
- */
246
- export declare class LambderApiGuardsEngine {
247
- private readonly onInvalidInput;
248
- private guards;
249
- constructor(onInvalidInput: LambderInputValidationRefusal);
250
- addGuards(guards: Record<string, LambderApiGuard<any, any, any>>): void;
251
- /** Startup validation of one API registration's guards option. */
252
- assertRegistration(apiName: string, mode: "public" | "session", guardsOption?: LambderGuardsOptionValue): void;
253
- /** Run the API's guards in declared order. Refusals throw; outputs land on ctx.guardData. */
254
- run(ctx: LambderRenderContext, resolver: LambderResolver, guardsOption?: LambderGuardsOptionValue): Promise<void>;
255
- }
256
- export {};
@@ -1,94 +0,0 @@
1
- export function lambderGuard(guard) { return guard; }
2
- /** Normalize the three guards-option forms into ordered { name, param } entries. */
3
- const toGuardEntries = (value) => {
4
- if (value === undefined)
5
- return [];
6
- if (typeof value === "string")
7
- return [{ name: value, param: undefined }];
8
- if (Array.isArray(value))
9
- return value.map((name) => ({ name: String(name), param: undefined }));
10
- // Object form: insertion order, params passed verbatim (paramless guards
11
- // are declared with `true` and their handlers take no param argument).
12
- return Object.entries(value).map(([name, param]) => ({ name, param }));
13
- };
14
- /**
15
- * Validate a preflight input slice (an apiInput slice of the raw payload, or
16
- * a guardInput value from the raw guardInputs map). Runs before the API's
17
- * own validation; a failure throws the response `onInvalid` decides, the
18
- * same one regular input validation answers. Shared with the rate-limit
19
- * engine's apiInput-keyed policies.
20
- */
21
- export const parsePreflightSlice = async (input, value, ctx, resolver, onInvalid) => {
22
- const parsed = input.safeParse(value);
23
- if (!parsed.success)
24
- throw await onInvalid(ctx, resolver, parsed.error);
25
- return parsed.data;
26
- };
27
- /**
28
- * Runtime side of the guards subsystem: holds the defined guards, asserts
29
- * API registrations against them at startup, and executes an API's declared
30
- * guards during preflight. Composed into LambderApiPolicyEngine.
31
- */
32
- export class LambderApiGuardsEngine {
33
- onInvalidInput;
34
- guards = {};
35
- constructor(onInvalidInput) {
36
- this.onInvalidInput = onInvalidInput;
37
- }
38
- addGuards(guards) {
39
- for (const [name, guardDef] of Object.entries(guards)) {
40
- if (this.guards[name])
41
- throw new Error(`Lambder: guard "${name}" is already defined.`);
42
- if (typeof guardDef?.handler !== "function")
43
- throw new Error(`Lambder: guard "${name}" has no handler function.`);
44
- if (guardDef.apiInput && guardDef.guardInput)
45
- throw new Error(`Lambder: guard "${name}" declares both apiInput and guardInput; pick one.`);
46
- this.guards[name] = guardDef;
47
- }
48
- }
49
- /** Startup validation of one API registration's guards option. */
50
- assertRegistration(apiName, mode, guardsOption) {
51
- const entries = toGuardEntries(guardsOption);
52
- // The runtime half of LambderNonEmptyGuardsMap. `guards: {}` and
53
- // `guards: []` are present-but-empty: they satisfy the require*ApiGuards
54
- // field check while running nothing, which is the one shape that turns a
55
- // mandatory authorization declaration back into an optional one. The type
56
- // rejects both; a plain-JS caller, a cast, or a spread that happened to
57
- // produce an empty object lands here instead.
58
- if (guardsOption !== undefined && entries.length === 0) {
59
- throw new Error(`Lambder: API "${apiName}" declares an empty guards option, which authorizes nothing. ` +
60
- `Name the guard that authorizes it, or omit the option entirely.`);
61
- }
62
- for (const { name } of entries) {
63
- const guardDef = this.guards[name];
64
- if (!guardDef) {
65
- throw new Error(`Lambder: API "${apiName}" references unknown guard "${name}". Declare it in the guards option at creation.`);
66
- }
67
- if (guardDef.session && mode !== "session") {
68
- throw new Error(`Lambder: API "${apiName}" uses guard "${name}" (session: true), which requires addSessionApi.`);
69
- }
70
- }
71
- }
72
- /** Run the API's guards in declared order. Refusals throw; outputs land on ctx.guardData. */
73
- async run(ctx, resolver, guardsOption) {
74
- for (const { name, param } of toGuardEntries(guardsOption)) {
75
- const guardDef = this.guards[name];
76
- if (!guardDef)
77
- throw new Error(`Lambder: guard "${name}" is not configured.`);
78
- const post = ctx.post;
79
- let payload;
80
- if (guardDef.apiInput) {
81
- payload = await parsePreflightSlice(guardDef.apiInput, post?.payload, ctx, resolver, this.onInvalidInput);
82
- }
83
- else if (guardDef.guardInput) {
84
- payload = await parsePreflightSlice(guardDef.guardInput, post?.guardInputs?.[name], ctx, resolver, this.onInvalidInput);
85
- }
86
- // A guard's return value becomes the handler's typed
87
- // ctx.guardData[name]; check-only guards return undefined.
88
- const output = await guardDef.handler(ctx, payload, resolver, param);
89
- if (output !== undefined) {
90
- ctx.guardData[name] = output;
91
- }
92
- }
93
- }
94
- }
@@ -1,58 +0,0 @@
1
- import type { LambderRenderContext } from "../core/LambderContext.js";
2
- import type { LambderDdbIdempotency } from "../stores/LambderDdbIdempotency.js";
3
- import { LambderResponse } from "../core/LambderResponse.js";
4
- export type LambderApiIdempotencyConfig = {
5
- /** Your idempotency store instance; may share the rate limiter's table (distinct key prefix). */
6
- store: LambderDdbIdempotency;
7
- /** Seconds a stored response replays for. Default: 86400 (24h). Per-API override: idempotency: { ttlSeconds }. */
8
- defaultTtlSeconds?: number;
9
- /** Skip idempotency (execute normally) when DynamoDB errors, instead of failing the request. Default: true. */
10
- failOpen?: boolean;
11
- };
12
- /**
13
- * Runtime side of the idempotency subsystem: claims a per-operation scope
14
- * around handler execution, replays stored responses, and settles claims.
15
- * Composed into LambderApiPolicyEngine.
16
- */
17
- export declare class LambderApiIdempotencyEngine {
18
- private store;
19
- private defaultTtlSeconds;
20
- private failOpen;
21
- configure(config: LambderApiIdempotencyConfig): void;
22
- /** True once the idempotency option was configured; registration asserts check it. */
23
- get isConfigured(): boolean;
24
- /**
25
- * The request's idempotencyKey: null when absent, the key when valid, a
26
- * 400 refusal when malformed. The minimum length matters for security:
27
- * see IDEMPOTENCY_MIN_KEY_LENGTH.
28
- */
29
- private readKey;
30
- /**
31
- * The record's scope. Session APIs scope per session, so even a leaked
32
- * key cannot cross users. Public APIs scope by the key alone: the key is
33
- * required to be long (and documented to be random), and identity proxies
34
- * like the client IP are deliberately NOT part of the scope, because the
35
- * retry idempotency exists for (a timeout followed by a network change)
36
- * frequently arrives from a different IP.
37
- */
38
- private scopeOf;
39
- /**
40
- * Replay fast path, run BEFORE rate limits and guards: a completed record
41
- * answers with its stored response so a legitimate retry neither burns
42
- * rate-limit quota nor re-runs guards (the original already passed them,
43
- * and no handler executes). Misses fall through to the normal pipeline;
44
- * store errors follow the failOpen setting.
45
- */
46
- findReplay(apiName: string, ctx: LambderRenderContext): Promise<LambderResponse | null>;
47
- /**
48
- * Idempotency wrapper around validation-passed handler execution. Without
49
- * a client idempotencyKey the handler just runs; with one, the scope
50
- * (identity + api + key) is claimed atomically: duplicates of an
51
- * in-flight original refuse with 409, replays of a completed one return
52
- * the stored response verbatim, and a crashed original releases its claim
53
- * so a retry actually retries.
54
- */
55
- withIdempotency(apiName: string, ctx: LambderRenderContext, config: boolean | {
56
- ttlSeconds?: number;
57
- }, exec: () => Promise<LambderResponse>): Promise<LambderResponse>;
58
- }