lambder 6.0.2 → 7.0.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.
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 +21 -19
  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
@@ -0,0 +1,165 @@
1
+ /**
2
+ * What an invoke call reports: the failure reasons, the outcome union, the
3
+ * error api() throws, and the pure functions that read a failure (what a
4
+ * rejected delivery means, what Lambda's error payload says, the one-line
5
+ * detail an error message ends with).
6
+ *
7
+ * Split out of LambderInvokeCaller for the reason shared/wire/LambderApiOutcome.ts
8
+ * is split out of the browser caller: this is the vocabulary a CALLER of the
9
+ * caller reads, and a site that only annotates an outcome or narrows an error
10
+ * should not have to read a 700-line class to find it. The functions here
11
+ * touch none of the caller's state, so every "what went wrong on an invoke"
12
+ * answer is in one file.
13
+ */
14
+ import type { LambderApiEnvelopeBody } from "../shared/wire/LambderApiContract.js";
15
+ import type { LambderApiFailureReason, LambderValidationError } from "../shared/wire/LambderApiOutcome.js";
16
+ import type { LambderCrashDetail } from "../shared/wire/LambderCrashDetail.js";
17
+ export type LambderInvokeFailureReason = LambderApiFailureReason | 'crash' | 'protocol' | 'payloadTooLarge';
18
+ /** Lambda's own error payload for a FunctionError invocation. */
19
+ export type LambderInvokeFunctionError = {
20
+ errorType?: string;
21
+ errorMessage?: string;
22
+ trace?: string[];
23
+ };
24
+ /** What every invoke failure carries, whatever went wrong. */
25
+ type LambderInvokeFailureFields = {
26
+ ok: false;
27
+ /** HTTP status, when the callee answered. */
28
+ status?: number;
29
+ /** Envelope errorMessage, when the callee provided one. */
30
+ errorMessage?: any;
31
+ /** Seconds to wait before retrying, from the answer's Retry-After header. */
32
+ retryAfterSeconds?: number;
33
+ /** Always present: the error api() throws for this failure, with the callee's error as its cause when one is known. */
34
+ error: LambderInvokeError;
35
+ /** The answer's logList; empty when no envelope came back. */
36
+ logList: unknown[];
37
+ /** The answer's Set-Cookie values; empty when no answer came back. */
38
+ cookies: string[];
39
+ };
40
+ /** HTTP 422: the callee rejected the input against the API's schema. Always carries the issues. */
41
+ export type LambderInvokeValidationFailure = LambderInvokeFailureFields & {
42
+ reason: 'validation';
43
+ zodError: LambderValidationError;
44
+ };
45
+ /** Lambda reported a FunctionError: the callee failed outside the framework (an init failure, a timeout, out of memory). Always carries the runtime's error payload. */
46
+ export type LambderInvokeCrashFailure = LambderInvokeFailureFields & {
47
+ reason: 'crash';
48
+ functionError: LambderInvokeFunctionError;
49
+ };
50
+ /** Refused before sending: the serialized event is over the invoke cap. Always carries its size. */
51
+ export type LambderInvokePayloadTooLargeFailure = LambderInvokeFailureFields & {
52
+ reason: 'payloadTooLarge';
53
+ /** The event's byte size, so a caller can say by how much it is over. */
54
+ bytes: number;
55
+ };
56
+ /** The callee answered, and the envelope itself says the call is refused. Always carries that envelope. */
57
+ export type LambderInvokeEnvelopeFailure = LambderInvokeFailureFields & {
58
+ reason: 'versionExpired' | 'sessionExpired' | 'notAuthorized' | 'errorMessage';
59
+ response: LambderApiEnvelopeBody<any>;
60
+ /** The callee's crash detail, when its global error handler sent one (the envelope's `crash` field). */
61
+ crash?: LambderCrashDetail;
62
+ };
63
+ /**
64
+ * Nothing usable came back: the invoke never arrived or was given up on, the
65
+ * Lambda service answered instead of the callee, the callee answered 5xx, or
66
+ * something around the call threw. A 5xx carries `response` when the callee
67
+ * answered with Lambder's own envelope, which is how a crash detail and a
68
+ * logList arrive with it.
69
+ */
70
+ export type LambderInvokeDeliveryFailure = LambderInvokeFailureFields & {
71
+ reason: 'network' | 'timeout' | 'server' | 'protocol' | 'unknown';
72
+ response?: LambderApiEnvelopeBody<any>;
73
+ /** The callee's crash detail, when its global error handler sent one (the envelope's `crash` field). */
74
+ crash?: LambderCrashDetail;
75
+ };
76
+ /**
77
+ * A failed invoke, discriminated by `reason` rather than written as one arm of
78
+ * optional fields, so narrowing to a reason narrows to what that reason
79
+ * actually carries: `zodError` after `validation`, `functionError` after
80
+ * `crash`, `bytes` after `payloadTooLarge`, `response` after an envelope
81
+ * reason, so a reader never writes an optional chain or a `!` for a field the
82
+ * reason already guarantees. The browser caller's LambderApiOutcome is
83
+ * discriminated the same way.
84
+ *
85
+ * `error`, `logList` and `cookies` are on every arm, and `status`,
86
+ * `errorMessage` and `retryAfterSeconds` are there whenever an answer came
87
+ * back to read them from.
88
+ */
89
+ export type LambderInvokeFailure = LambderInvokeValidationFailure | LambderInvokeCrashFailure | LambderInvokePayloadTooLargeFailure | LambderInvokeEnvelopeFailure | LambderInvokeDeliveryFailure;
90
+ export type LambderInvokeOutcome<T> = {
91
+ ok: true;
92
+ payload: T;
93
+ response: LambderApiEnvelopeBody<T>;
94
+ logList: unknown[];
95
+ /**
96
+ * The answer's Set-Cookie values. A caller carrying a user's session
97
+ * is the browser for that call, and nothing else is: a callee that
98
+ * rotated or cleared the session cookies says so here, and a caller
99
+ * that ignores them keeps sending the old token. See
100
+ * reissueSession() on the callee for the tokens themselves.
101
+ */
102
+ cookies: string[];
103
+ } | LambderInvokeFailure;
104
+ export type LambderInvokeErrorInit = {
105
+ message: string;
106
+ reason: LambderInvokeFailureReason;
107
+ apiName: string;
108
+ functionName: string;
109
+ status?: number;
110
+ errorMessage?: any;
111
+ crash?: LambderCrashDetail;
112
+ functionError?: LambderInvokeFunctionError;
113
+ logList: unknown[];
114
+ zodError?: LambderValidationError;
115
+ retryAfterSeconds?: number;
116
+ bytes?: number;
117
+ cause?: unknown;
118
+ };
119
+ /**
120
+ * What api() throws. Its message names the function, the API and the reason,
121
+ * so an error reporter that fingerprints on the message groups one broken
122
+ * API into one row; its cause is the callee's own error rebuilt from the
123
+ * crash detail (or Lambda's FunctionError, or the SDK's rejection), so a
124
+ * reporter that walks causes stores the callee's stack.
125
+ */
126
+ export declare class LambderInvokeError extends Error {
127
+ /** Brand for detection across duplicate lambder installs, like LambderApiRefusal. */
128
+ readonly isLambderInvokeError = true;
129
+ readonly reason: LambderInvokeFailureReason;
130
+ readonly apiName: string;
131
+ readonly functionName: string;
132
+ readonly status?: number;
133
+ readonly errorMessage?: any;
134
+ readonly crash?: LambderCrashDetail;
135
+ readonly functionError?: LambderInvokeFunctionError;
136
+ readonly logList: unknown[];
137
+ readonly zodError?: LambderValidationError;
138
+ readonly retryAfterSeconds?: number;
139
+ readonly bytes?: number;
140
+ /** The full failure outcome; it carries this error and this error carries it. */
141
+ outcome: LambderInvokeFailure;
142
+ constructor(init: LambderInvokeErrorInit);
143
+ }
144
+ /** Brand-based type guard (see LambderInvokeError.isLambderInvokeError). */
145
+ export declare const isLambderInvokeError: (err: unknown) => err is LambderInvokeError;
146
+ /**
147
+ * What a rejected delivery means, as far as the rejection itself says.
148
+ *
149
+ * A transport that named its own reason is believed. Otherwise: the Lambda
150
+ * SDK throws its service exceptions (AccessDeniedException,
151
+ * ResourceNotFoundException, RequestEntityTooLargeException and the rest)
152
+ * with a $fault mark and a name ending in "Exception", while a connectivity
153
+ * failure arrives as a plain Error or TypeError carrying neither. The first
154
+ * kind is the Lambda service answering the invoke, which is a permission,
155
+ * wiring or size fault to go and fix; calling it `network` sent whoever read
156
+ * it to look at their connection instead.
157
+ */
158
+ export declare const classifyDeliveryFailure: (error: Error) => "network" | "protocol";
159
+ /** Lambda's error payload as an Error, with the callee's own name and trace. */
160
+ export declare const errorFromFunctionError: (functionError: LambderInvokeFunctionError) => Error;
161
+ /** Lambda's error payload as it actually arrives: an object with the three fields, or whatever else the runtime sent. */
162
+ export declare const parseFunctionError: (result: unknown) => LambderInvokeFunctionError;
163
+ /** The one-line detail a failure's message ends with. */
164
+ export declare const describeFailure: (init: Omit<LambderInvokeErrorInit, "message" | "apiName" | "functionName" | "logList">) => string;
165
+ export {};
@@ -0,0 +1,129 @@
1
+ /**
2
+ * What an invoke call reports: the failure reasons, the outcome union, the
3
+ * error api() throws, and the pure functions that read a failure (what a
4
+ * rejected delivery means, what Lambda's error payload says, the one-line
5
+ * detail an error message ends with).
6
+ *
7
+ * Split out of LambderInvokeCaller for the reason shared/wire/LambderApiOutcome.ts
8
+ * is split out of the browser caller: this is the vocabulary a CALLER of the
9
+ * caller reads, and a site that only annotates an outcome or narrows an error
10
+ * should not have to read a 700-line class to find it. The functions here
11
+ * touch none of the caller's state, so every "what went wrong on an invoke"
12
+ * answer is in one file.
13
+ */
14
+ import { isLambderTransportFailure } from "../shared/transport/LambderApiTransport.js";
15
+ /**
16
+ * What api() throws. Its message names the function, the API and the reason,
17
+ * so an error reporter that fingerprints on the message groups one broken
18
+ * API into one row; its cause is the callee's own error rebuilt from the
19
+ * crash detail (or Lambda's FunctionError, or the SDK's rejection), so a
20
+ * reporter that walks causes stores the callee's stack.
21
+ */
22
+ export class LambderInvokeError extends Error {
23
+ /** Brand for detection across duplicate lambder installs, like LambderApiRefusal. */
24
+ isLambderInvokeError = true;
25
+ reason;
26
+ apiName;
27
+ functionName;
28
+ status;
29
+ errorMessage;
30
+ crash;
31
+ functionError;
32
+ logList;
33
+ zodError;
34
+ retryAfterSeconds;
35
+ bytes;
36
+ /** The full failure outcome; it carries this error and this error carries it. */
37
+ outcome;
38
+ constructor(init) {
39
+ super(init.message, init.cause !== undefined ? { cause: init.cause } : undefined);
40
+ this.name = "LambderInvokeError";
41
+ this.reason = init.reason;
42
+ this.apiName = init.apiName;
43
+ this.functionName = init.functionName;
44
+ this.status = init.status;
45
+ this.errorMessage = init.errorMessage;
46
+ this.crash = init.crash;
47
+ this.functionError = init.functionError;
48
+ this.logList = init.logList;
49
+ this.zodError = init.zodError;
50
+ this.retryAfterSeconds = init.retryAfterSeconds;
51
+ this.bytes = init.bytes;
52
+ }
53
+ }
54
+ /** Brand-based type guard (see LambderInvokeError.isLambderInvokeError). */
55
+ export const isLambderInvokeError = (err) => err instanceof Error && err.isLambderInvokeError === true;
56
+ // ---------------------------------------------------------------------------
57
+ // Reading a failure: pure functions over what came back, used by the caller.
58
+ // ---------------------------------------------------------------------------
59
+ /**
60
+ * What a rejected delivery means, as far as the rejection itself says.
61
+ *
62
+ * A transport that named its own reason is believed. Otherwise: the Lambda
63
+ * SDK throws its service exceptions (AccessDeniedException,
64
+ * ResourceNotFoundException, RequestEntityTooLargeException and the rest)
65
+ * with a $fault mark and a name ending in "Exception", while a connectivity
66
+ * failure arrives as a plain Error or TypeError carrying neither. The first
67
+ * kind is the Lambda service answering the invoke, which is a permission,
68
+ * wiring or size fault to go and fix; calling it `network` sent whoever read
69
+ * it to look at their connection instead.
70
+ */
71
+ export const classifyDeliveryFailure = (error) => {
72
+ if (isLambderTransportFailure(error))
73
+ return error.reason;
74
+ const fault = error.$fault;
75
+ if (fault === "client" || fault === "server")
76
+ return 'protocol';
77
+ return error.name.endsWith("Exception") ? 'protocol' : 'network';
78
+ };
79
+ /** Lambda's error payload as an Error, with the callee's own name and trace. */
80
+ export const errorFromFunctionError = (functionError) => {
81
+ const error = new Error(functionError.errorMessage ?? "the function failed");
82
+ error.name = functionError.errorType ?? "FunctionError";
83
+ if (functionError.trace?.length)
84
+ error.stack = functionError.trace.join("\n");
85
+ return error;
86
+ };
87
+ /** Lambda's error payload as it actually arrives: an object with the three fields, or whatever else the runtime sent. */
88
+ export const parseFunctionError = (result) => {
89
+ if (result && typeof result === "object") {
90
+ const { errorType, errorMessage, trace } = result;
91
+ return {
92
+ ...(typeof errorType === "string" ? { errorType } : {}),
93
+ ...(typeof errorMessage === "string" ? { errorMessage } : {}),
94
+ ...(Array.isArray(trace) ? { trace: trace.map(String) } : {}),
95
+ };
96
+ }
97
+ return { errorMessage: typeof result === "string" ? result : undefined };
98
+ };
99
+ /** The one-line detail a failure's message ends with. */
100
+ export const describeFailure = (init) => {
101
+ if (init.crash)
102
+ return init.crash.message;
103
+ if (init.functionError)
104
+ return `${init.functionError.errorType ?? "FunctionError"}: ${init.functionError.errorMessage ?? "the function failed"}`;
105
+ if (init.errorMessage !== undefined) {
106
+ const content = init.errorMessage?.content;
107
+ if (typeof content === "string")
108
+ return content;
109
+ if (typeof init.errorMessage === "string")
110
+ return init.errorMessage;
111
+ try {
112
+ return JSON.stringify(init.errorMessage);
113
+ }
114
+ catch {
115
+ return String(init.errorMessage);
116
+ }
117
+ }
118
+ if (init.reason === 'validation')
119
+ return "the callee rejected the input";
120
+ if (init.reason === 'versionExpired')
121
+ return "the callee answered versionExpired";
122
+ if (init.reason === 'sessionExpired')
123
+ return "the callee answered sessionExpired";
124
+ if (init.reason === 'notAuthorized')
125
+ return "the callee answered notAuthorized";
126
+ if (init.cause instanceof Error)
127
+ return init.cause.message;
128
+ return init.status !== undefined ? `HTTP ${init.status}` : "no answer";
129
+ };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The two conversions between a request and what Lambda speaks: a request
3
+ * as the API Gateway payload-format-2.0 event it would have delivered, and
4
+ * a function's answer as the decoded HTTP result it stands for. Shared by
5
+ * LambderInvokeCaller (a real invoke through the Lambda SDK) and
6
+ * lambderHandlerTransport (a Lambder handler called in-process, for tests),
7
+ * along with the smaller conversions that belong to a synthesized request:
8
+ * its body envelope, and a carried session as the cookie pair it travels as.
9
+ * Server-only: Buffer and the codec's zlib restore.
10
+ */
11
+ import type { APIGatewayProxyEventV2, Context } from "aws-lambda";
12
+ import type { LambderCompressedBrotliPayload, LambderCompressedGzipPayload } from "../shared/wire/LambderRequestPayload.js";
13
+ /** Marks a synthesized request as an invoke, for guards and hooks that want to tell. Not an authorization. */
14
+ export declare const LAMBDER_INVOKE_HEADER = "x-lambder-invoke";
15
+ /** The invoking function's name, when the caller runs in Lambda; for the callee's logs. */
16
+ export declare const LAMBDER_INVOKED_BY_HEADER = "x-lambder-invoked-by";
17
+ /** The value of the marker header; a future incompatible event shape would bump it. */
18
+ export declare const LAMBDER_INVOKE_PROTOCOL = "1";
19
+ /** A session carried on a user's behalf: the two values a browser holds. */
20
+ export type LambderInvokeSession = {
21
+ token: string;
22
+ csrf: string;
23
+ };
24
+ /** The session's cookie pair for a synthesized event: the token rides as a cookie, the CSRF value in the envelope's `token` field. */
25
+ export declare const sessionCookies: (session: LambderInvokeSession | undefined, tokenCookieKey: string) => string[] | undefined;
26
+ export type LambderSynthesizedRequest = {
27
+ method: string;
28
+ path: string;
29
+ query?: Record<string, string>;
30
+ host: string;
31
+ headers?: Record<string, string>;
32
+ clientIp?: string;
33
+ cookies?: string[];
34
+ body?: string | Buffer;
35
+ };
36
+ /**
37
+ * The payload-format-2.0 event API Gateway would deliver for this request.
38
+ * `invoke: true` adds the invoke marker headers a server-to-server call
39
+ * carries; a browser-shaped request (the handler transport) leaves them off.
40
+ *
41
+ * The client address is `clientIp` and reaches the callee as
42
+ * requestContext.http.sourceIp only. Writing it as x-forwarded-for as well
43
+ * would put the same fact on a channel a callee may be configured to trust
44
+ * (trustedClientIpHeaders), and the header is the one the caller's own
45
+ * `headers` could otherwise have set.
46
+ */
47
+ export declare const synthesizeLambdaHttpEvent: (request: LambderSynthesizedRequest, options: {
48
+ invoke: boolean;
49
+ }) => APIGatewayProxyEventV2;
50
+ /**
51
+ * The body envelope LambderCaller sends, minus the fields only a browser has
52
+ * a value for, as JSON. A plain payload arrives already serialized (the
53
+ * compression decision needed its JSON) and is spliced in rather than
54
+ * parsed and stringified a second time; a compressed one rides as its two
55
+ * fields.
56
+ */
57
+ export declare const buildEnvelopeJson: (fields: {
58
+ apiName: string;
59
+ version?: string;
60
+ csrf?: string;
61
+ siteHost: string;
62
+ /** The payload's own JSON, when it goes plainly. */
63
+ payloadJson?: string;
64
+ /** The compressed pair, when it goes compressed. */
65
+ compressed?: LambderCompressedBrotliPayload | LambderCompressedGzipPayload | null;
66
+ guardInputs?: Record<string, unknown>;
67
+ idempotencyKey?: string;
68
+ }) => string;
69
+ /** The decoded HTTP answer of a Lambda result: status, lowercased headers, cookies and the body bytes (decompressed when the callee compressed them). */
70
+ export type LambderLambdaHttpResult = {
71
+ statusCode: number;
72
+ headers: Record<string, string>;
73
+ cookies: string[];
74
+ body: Buffer;
75
+ text: () => string;
76
+ json: () => unknown;
77
+ };
78
+ /** The function's answer as an HTTP result; throws when it is not one, or its compressed body cannot be restored under `maxBodyBytes`. */
79
+ export declare const decodeLambdaHttpResult: (result: unknown, maxBodyBytes: number) => Promise<LambderLambdaHttpResult>;
80
+ /** A Lambda context for an in-process call, the fields a handler might read filled plausibly. */
81
+ export declare const localLambdaContext: (functionName: string, overrides?: Partial<Context>) => Context;
@@ -0,0 +1,187 @@
1
+ /**
2
+ * The two conversions between a request and what Lambda speaks: a request
3
+ * as the API Gateway payload-format-2.0 event it would have delivered, and
4
+ * a function's answer as the decoded HTTP result it stands for. Shared by
5
+ * LambderInvokeCaller (a real invoke through the Lambda SDK) and
6
+ * lambderHandlerTransport (a Lambder handler called in-process, for tests),
7
+ * along with the smaller conversions that belong to a synthesized request:
8
+ * its body envelope, and a carried session as the cookie pair it travels as.
9
+ * Server-only: Buffer and the codec's zlib restore.
10
+ */
11
+ import { restoreBytes } from "../shared/wire/LambderCompressionCodec.js";
12
+ import { bytesToBase64 } from "../shared/util/LambderBase64.js";
13
+ import { buildEnvelopeFields } from "../shared/transport/LambderApiTransport.js";
14
+ /** Marks a synthesized request as an invoke, for guards and hooks that want to tell. Not an authorization. */
15
+ export const LAMBDER_INVOKE_HEADER = "x-lambder-invoke";
16
+ /** The invoking function's name, when the caller runs in Lambda; for the callee's logs. */
17
+ export const LAMBDER_INVOKED_BY_HEADER = "x-lambder-invoked-by";
18
+ /** The value of the marker header; a future incompatible event shape would bump it. */
19
+ export const LAMBDER_INVOKE_PROTOCOL = "1";
20
+ /**
21
+ * The forwarded-address header a gateway writes. This event never writes it:
22
+ * the address it asserts travels in requestContext.http.sourceIp, which is
23
+ * what resolveClientIp reads and the only channel a callee trusts by default.
24
+ */
25
+ const FORWARDED_FOR_HEADER = "x-forwarded-for";
26
+ /** The session's cookie pair for a synthesized event: the token rides as a cookie, the CSRF value in the envelope's `token` field. */
27
+ export const sessionCookies = (session, tokenCookieKey) => session ? [`${tokenCookieKey}=${session.token}`] : undefined;
28
+ const randomRequestId = () => {
29
+ const webCrypto = globalThis.crypto;
30
+ if (webCrypto?.randomUUID)
31
+ return webCrypto.randomUUID();
32
+ return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2, 10)}`;
33
+ };
34
+ /**
35
+ * The payload-format-2.0 event API Gateway would deliver for this request.
36
+ * `invoke: true` adds the invoke marker headers a server-to-server call
37
+ * carries; a browser-shaped request (the handler transport) leaves them off.
38
+ *
39
+ * The client address is `clientIp` and reaches the callee as
40
+ * requestContext.http.sourceIp only. Writing it as x-forwarded-for as well
41
+ * would put the same fact on a channel a callee may be configured to trust
42
+ * (trustedClientIpHeaders), and the header is the one the caller's own
43
+ * `headers` could otherwise have set.
44
+ */
45
+ export const synthesizeLambdaHttpEvent = (request, options) => {
46
+ // The caller's own headers go on first, so the ones this function owns
47
+ // cannot be displaced by them. Forwarding an incoming browser request's
48
+ // headers into `headers` is an ordinary gateway-lambda pattern, and with
49
+ // the spread last it let that end user overwrite the invoke markers and
50
+ // the forwarded address this event is asserting.
51
+ const headers = {};
52
+ for (const [key, value] of Object.entries(request.headers ?? {}))
53
+ headers[key.toLowerCase()] = value;
54
+ // These three the event owns unconditionally, whatever the caller passed:
55
+ // a forwarded address the caller did not assert through `clientIp` is not
56
+ // this event's to make, and the invoke markers say what this event is. A
57
+ // gateway lambda that forwards a browser's headers wholesale would
58
+ // otherwise hand a callee that trusts x-forwarded-for an end-user-chosen
59
+ // ctx.ip, and let a browser-shaped request claim to be an invoke.
60
+ delete headers[FORWARDED_FOR_HEADER];
61
+ delete headers[LAMBDER_INVOKE_HEADER];
62
+ delete headers[LAMBDER_INVOKED_BY_HEADER];
63
+ headers.host = request.host;
64
+ headers["accept-encoding"] = "br, gzip";
65
+ if (options.invoke) {
66
+ headers[LAMBDER_INVOKE_HEADER] = LAMBDER_INVOKE_PROTOCOL;
67
+ const invokedBy = typeof process !== "undefined" ? process.env?.AWS_LAMBDA_FUNCTION_NAME : undefined;
68
+ if (invokedBy)
69
+ headers[LAMBDER_INVOKED_BY_HEADER] = invokedBy;
70
+ }
71
+ const isBinary = Buffer.isBuffer(request.body);
72
+ if (request.body !== undefined && !headers["content-type"]) {
73
+ headers["content-type"] = isBinary ? "application/octet-stream" : "application/json";
74
+ }
75
+ const now = Date.now();
76
+ return {
77
+ version: "2.0",
78
+ routeKey: "$default",
79
+ rawPath: request.path,
80
+ rawQueryString: new URLSearchParams(request.query ?? {}).toString(),
81
+ headers,
82
+ ...(request.cookies?.length ? { cookies: request.cookies } : {}),
83
+ requestContext: {
84
+ accountId: "",
85
+ apiId: options.invoke ? "lambder-invoke" : "lambder-local",
86
+ domainName: request.host,
87
+ domainPrefix: "",
88
+ http: {
89
+ method: request.method,
90
+ path: request.path,
91
+ protocol: "HTTP/1.1",
92
+ sourceIp: request.clientIp ?? "",
93
+ userAgent: options.invoke ? "lambder-invoke" : "lambder-local",
94
+ },
95
+ requestId: randomRequestId(),
96
+ routeKey: "$default",
97
+ stage: "$default",
98
+ time: new Date(now).toISOString(),
99
+ timeEpoch: now,
100
+ },
101
+ ...(request.body !== undefined
102
+ ? { body: isBinary ? bytesToBase64(request.body) : request.body }
103
+ : {}),
104
+ isBase64Encoded: isBinary,
105
+ };
106
+ };
107
+ /**
108
+ * The body envelope LambderCaller sends, minus the fields only a browser has
109
+ * a value for, as JSON. A plain payload arrives already serialized (the
110
+ * compression decision needed its JSON) and is spliced in rather than
111
+ * parsed and stringified a second time; a compressed one rides as its two
112
+ * fields.
113
+ */
114
+ export const buildEnvelopeJson = (fields) => {
115
+ // The field set is buildEnvelopeFields', so a field added to the envelope
116
+ // reaches this sender too; only the payload is this one's own business.
117
+ const withoutPayload = JSON.stringify(buildEnvelopeFields({
118
+ apiName: fields.apiName,
119
+ version: fields.version,
120
+ token: fields.csrf ?? "",
121
+ siteHost: fields.siteHost,
122
+ compressed: fields.compressed,
123
+ guardInputs: fields.guardInputs,
124
+ idempotencyKey: fields.idempotencyKey,
125
+ }));
126
+ if (fields.payloadJson === undefined)
127
+ return withoutPayload;
128
+ return `${withoutPayload.slice(0, -1)},"payload":${fields.payloadJson}}`;
129
+ };
130
+ /** The function's answer as an HTTP result; throws when it is not one, or its compressed body cannot be restored under `maxBodyBytes`. */
131
+ export const decodeLambdaHttpResult = async (result, maxBodyBytes) => {
132
+ if (!result || typeof result !== "object" || typeof result.statusCode !== "number") {
133
+ throw new Error("the function did not answer with an HTTP response object; is it a Lambder app?");
134
+ }
135
+ const raw = result;
136
+ const headers = {};
137
+ const cookies = [...(raw.cookies ?? [])];
138
+ for (const [key, value] of Object.entries(raw.headers ?? {}))
139
+ headers[key.toLowerCase()] = value;
140
+ for (const [key, values] of Object.entries(raw.multiValueHeaders ?? {})) {
141
+ // A v1 answer carries Set-Cookie as a multi-value header; keep the
142
+ // values apart, as the v2 cookies array does, since they contain commas.
143
+ if (key.toLowerCase() === "set-cookie")
144
+ cookies.push(...values);
145
+ headers[key.toLowerCase()] = values.join(", ");
146
+ }
147
+ let body = raw.body ? Buffer.from(raw.body, raw.isBase64Encoded ? "base64" : "utf8") : Buffer.alloc(0);
148
+ const encoding = headers["content-encoding"]?.trim().toLowerCase();
149
+ if (encoding === "br" || encoding === "gzip") {
150
+ // restoreBytes, not restoreText: a route may answer compressed
151
+ // binary (a wasm module, anything it forced compression on), and
152
+ // decoding that as UTF-8 first would replace every byte that is
153
+ // not valid UTF-8 and hand back a silently different body.
154
+ const restored = await restoreBytes(body, encoding, { maxBytes: maxBodyBytes });
155
+ body = Buffer.isBuffer(restored) ? restored : Buffer.from(restored);
156
+ }
157
+ else if (encoding && encoding !== "identity") {
158
+ // "identity" is a legal value meaning no encoding, and a hook or a
159
+ // proxy may set it; treating it as unsupported turned every such
160
+ // invoke into a protocol failure.
161
+ throw new Error(`the answer carries an unsupported Content-Encoding "${encoding}"`);
162
+ }
163
+ return {
164
+ statusCode: raw.statusCode,
165
+ headers,
166
+ cookies,
167
+ body,
168
+ text: () => body.toString("utf8"),
169
+ json: () => JSON.parse(body.toString("utf8")),
170
+ };
171
+ };
172
+ /** A Lambda context for an in-process call, the fields a handler might read filled plausibly. */
173
+ export const localLambdaContext = (functionName, overrides = {}) => ({
174
+ callbackWaitsForEmptyEventLoop: false,
175
+ functionName,
176
+ functionVersion: "$LATEST",
177
+ invokedFunctionArn: `arn:aws:lambda:local:000000000000:function:${functionName}`,
178
+ memoryLimitInMB: "128",
179
+ awsRequestId: randomRequestId(),
180
+ logGroupName: `/aws/lambda/${functionName}`,
181
+ logStreamName: "local",
182
+ getRemainingTimeInMillis: () => 30_000,
183
+ done: () => { },
184
+ fail: () => { },
185
+ succeed: () => { },
186
+ ...overrides,
187
+ });
@@ -0,0 +1,36 @@
1
+ import type { Context } from "aws-lambda";
2
+ import type { LambderApiTransport } from "../shared/transport/LambderApiTransport.js";
3
+ import type { LambderHandler } from "../core/LambderCreateOptions.js";
4
+ export type LambderHandlerTransportOptions = {
5
+ /** The Host the handler sees (ctx.host), and the siteHost the envelope carries when the caller has none. Default: the apiPath's own host when it is absolute, otherwise "localhost". */
6
+ host?: string;
7
+ /** The client IP the handler sees (ctx.ip). Default: "127.0.0.1". */
8
+ clientIp?: string;
9
+ /** Ceiling on what a compressed answer may restore to. Default: 20,000,000. */
10
+ maxResponseBytes?: number;
11
+ /** Fields of the Lambda context the handler receives. */
12
+ context?: Partial<Context>;
13
+ };
14
+ /**
15
+ * A transport that calls a Lambder handler in this process, the way a
16
+ * browser's request would reach it: a browser-shaped API Gateway event (no
17
+ * invoke marker), the real handler, and its answer decoded back, compressed
18
+ * bodies included. With the memory stores, that is an integration test of a
19
+ * real app through the typed caller with no HTTP and no AWS. Wrap it in
20
+ * lambderCookieJarTransport to hold a session across calls.
21
+ *
22
+ * A handler that throws (which a Lambder app never does on the HTTP path,
23
+ * since render() answers its own last-resort 500) produced no answer at all,
24
+ * so the call fails as `protocol` carrying the handler's own error as its
25
+ * cause. API Gateway would have turned it into a bare 502, and synthesizing
26
+ * one here would throw the error away; keeping it is the point of an
27
+ * in-process transport, and there is nowhere in an HTTP answer to put one
28
+ * except the user-facing `message` field, which is the wrong channel for an
29
+ * internal fault.
30
+ *
31
+ * `request.signal` ends the wait, as the transport contract requires. The
32
+ * handler keeps running to completion either way, because a function call in
33
+ * this process cannot be cancelled: what a timeout buys here is the caller's
34
+ * answer, not the callee's attention.
35
+ */
36
+ export declare const lambderHandlerTransport: (handler: LambderHandler, options?: LambderHandlerTransportOptions) => LambderApiTransport;