lambder 7.3.1 → 8.1.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 (237) hide show
  1. package/CHANGELOG.md +1047 -3
  2. package/README.md +46 -21
  3. package/dist/api/LambderApiAnswer.d.ts +18 -22
  4. package/dist/api/LambderApiAnswer.js +6 -7
  5. package/dist/api/LambderApiCallContext.d.ts +21 -8
  6. package/dist/api/LambderApiCallContext.js +22 -4
  7. package/dist/api/LambderApiDefinition.d.ts +4 -3
  8. package/dist/api/LambderApiEnvelope.d.ts +14 -9
  9. package/dist/api/LambderApiEnvelope.js +33 -34
  10. package/dist/api/LambderApiGuards.d.ts +78 -51
  11. package/dist/api/LambderApiGuards.js +34 -36
  12. package/dist/api/LambderApiIdempotency.d.ts +68 -62
  13. package/dist/api/LambderApiIdempotency.js +214 -151
  14. package/dist/api/LambderApiOutputValidationError.d.ts +32 -0
  15. package/dist/api/LambderApiOutputValidationError.js +50 -0
  16. package/dist/api/LambderApiPipeline.d.ts +47 -38
  17. package/dist/api/LambderApiPipeline.js +122 -63
  18. package/dist/api/LambderApiRateLimits.d.ts +201 -54
  19. package/dist/api/LambderApiRateLimits.js +185 -108
  20. package/dist/api/LambderApiRequest.d.ts +27 -21
  21. package/dist/api/LambderApiRequest.js +26 -19
  22. package/dist/api/LambderApiSignature.d.ts +12 -15
  23. package/dist/api/LambderApiSignature.js +28 -51
  24. package/dist/api/LambderApiValidationRefusal.d.ts +9 -9
  25. package/dist/api/LambderApiValidationRefusal.js +10 -10
  26. package/dist/build/ContractTypePrinter.d.ts +85 -0
  27. package/dist/build/ContractTypePrinter.js +402 -0
  28. package/dist/build/freshProcessVerifier.d.ts +13 -0
  29. package/dist/build/freshProcessVerifier.js +19 -0
  30. package/dist/build/moduleLocation.d.ts +11 -0
  31. package/dist/build/moduleLocation.js +6 -0
  32. package/dist/build/writeApiContract.d.ts +78 -0
  33. package/dist/build/writeApiContract.js +302 -0
  34. package/dist/build/writeApiSignatures.d.ts +114 -0
  35. package/dist/build/writeApiSignatures.js +217 -0
  36. package/dist/build/writeFileAtomically.d.ts +8 -0
  37. package/dist/build/writeFileAtomically.js +22 -0
  38. package/dist/build.d.ts +14 -0
  39. package/dist/build.js +11 -0
  40. package/dist/client/LambderCaller.d.ts +13 -44
  41. package/dist/client/LambderCaller.js +77 -84
  42. package/dist/client/LambderReloadLoopBreaker.d.ts +56 -26
  43. package/dist/client/LambderReloadLoopBreaker.js +90 -46
  44. package/dist/client/LambderUploadRunner.d.ts +96 -0
  45. package/dist/client/LambderUploadRunner.js +234 -0
  46. package/dist/client/lambderFetchTransport.d.ts +4 -1
  47. package/dist/client/lambderFetchTransport.js +52 -28
  48. package/dist/client.d.ts +9 -3
  49. package/dist/client.js +6 -1
  50. package/dist/core/Lambder.d.ts +143 -79
  51. package/dist/core/Lambder.js +350 -231
  52. package/dist/core/LambderContext.d.ts +82 -15
  53. package/dist/core/LambderContext.js +107 -20
  54. package/dist/core/LambderCors.d.ts +21 -3
  55. package/dist/core/LambderCors.js +35 -16
  56. package/dist/core/LambderCrashHandling.d.ts +40 -0
  57. package/dist/core/LambderCrashHandling.js +97 -0
  58. package/dist/core/LambderCreateOptions.d.ts +151 -75
  59. package/dist/core/LambderCreateOptions.js +16 -23
  60. package/dist/core/LambderFiles.d.ts +21 -7
  61. package/dist/core/LambderFiles.js +62 -34
  62. package/dist/core/LambderIndexHtml.js +12 -11
  63. package/dist/core/LambderPolicyBuilders.d.ts +17 -5
  64. package/dist/core/LambderPolicyBuilders.js +17 -5
  65. package/dist/core/LambderPublicFiles.d.ts +11 -5
  66. package/dist/core/LambderPublicFiles.js +32 -4
  67. package/dist/core/LambderRequestPath.d.ts +43 -0
  68. package/dist/core/LambderRequestPath.js +63 -0
  69. package/dist/core/LambderResponse.d.ts +26 -5
  70. package/dist/core/LambderResponse.js +157 -70
  71. package/dist/core/LambderResponseBuilder.d.ts +49 -4
  72. package/dist/core/LambderResponseBuilder.js +64 -3
  73. package/dist/core/LambderRouting.d.ts +2 -3
  74. package/dist/core/LambderRouting.js +22 -7
  75. package/dist/core/LambderTemplatingEngine.js +211 -32
  76. package/dist/index.d.ts +25 -8
  77. package/dist/index.js +13 -4
  78. package/dist/invoke/LambderInvokeCaller.d.ts +37 -42
  79. package/dist/invoke/LambderInvokeCaller.js +76 -66
  80. package/dist/invoke/LambderInvokeOutcome.d.ts +27 -26
  81. package/dist/invoke/LambderInvokeOutcome.js +9 -22
  82. package/dist/invoke/LambderLambdaEvent.d.ts +29 -9
  83. package/dist/invoke/LambderLambdaEvent.js +40 -22
  84. package/dist/invoke/lambderHandlerTransport.d.ts +9 -10
  85. package/dist/invoke/lambderHandlerTransport.js +15 -18
  86. package/dist/mock/LambderMockApp.d.ts +67 -83
  87. package/dist/mock/LambderMockApp.js +167 -153
  88. package/dist/mock/LambderMockBrowserCookies.d.ts +24 -28
  89. package/dist/mock/LambderMockBrowserCookies.js +24 -28
  90. package/dist/mock/LambderMockCallRecorder.d.ts +15 -22
  91. package/dist/mock/LambderMockCallRecorder.js +19 -28
  92. package/dist/mock/LambderMockCreateOptions.d.ts +42 -24
  93. package/dist/mock/LambderMockEntryRegistry.d.ts +11 -12
  94. package/dist/mock/LambderMockEntryRegistry.js +24 -29
  95. package/dist/mock/LambderMockFailureInjector.d.ts +3 -6
  96. package/dist/mock/LambderMockFailureInjector.js +3 -6
  97. package/dist/mock/LambderMockTypes.d.ts +78 -108
  98. package/dist/mock/lambderMockInvokeTransport.d.ts +11 -13
  99. package/dist/mock/lambderMockInvokeTransport.js +11 -10
  100. package/dist/mock/lambderMockMswHandler.d.ts +43 -33
  101. package/dist/mock/lambderMockMswHandler.js +50 -39
  102. package/dist/mock/lambderMockUploadMswHandler.d.ts +26 -0
  103. package/dist/mock/lambderMockUploadMswHandler.js +28 -0
  104. package/dist/mock.d.ts +4 -1
  105. package/dist/mock.js +6 -3
  106. package/dist/session/LambderSessionController.d.ts +108 -89
  107. package/dist/session/LambderSessionController.js +187 -168
  108. package/dist/session/LambderSessionCrypto.d.ts +16 -7
  109. package/dist/session/LambderSessionCrypto.js +26 -12
  110. package/dist/session/LambderSessionManager.d.ts +124 -46
  111. package/dist/session/LambderSessionManager.js +262 -137
  112. package/dist/shared/LambderHtml.d.ts +42 -3
  113. package/dist/shared/LambderHtml.js +127 -7
  114. package/dist/shared/LambderHtmlPositions.d.ts +173 -0
  115. package/dist/shared/LambderHtmlPositions.js +652 -0
  116. package/dist/shared/LambderI18n.d.ts +10 -11
  117. package/dist/shared/LambderI18n.js +33 -21
  118. package/dist/shared/contracts/LambderCache.d.ts +66 -0
  119. package/dist/shared/contracts/LambderCache.js +11 -0
  120. package/dist/shared/contracts/LambderFileSource.d.ts +6 -6
  121. package/dist/shared/contracts/LambderFileSource.js +5 -8
  122. package/dist/shared/contracts/LambderIdempotencyStore.d.ts +51 -22
  123. package/dist/shared/contracts/LambderIdempotencyStore.js +4 -5
  124. package/dist/shared/contracts/LambderRateLimiter.d.ts +27 -15
  125. package/dist/shared/contracts/LambderRateLimiter.js +4 -5
  126. package/dist/shared/contracts/LambderSessionStore.d.ts +65 -26
  127. package/dist/shared/contracts/LambderSessionStore.js +5 -6
  128. package/dist/shared/contracts/LambderUploadBucket.d.ts +154 -0
  129. package/dist/shared/contracts/LambderUploadBucket.js +74 -0
  130. package/dist/shared/transport/LambderApiTransport.d.ts +27 -27
  131. package/dist/shared/transport/LambderApiTransport.js +7 -7
  132. package/dist/shared/transport/LambderCookieJar.d.ts +28 -35
  133. package/dist/shared/transport/LambderCookieJar.js +54 -66
  134. package/dist/shared/transport/lambderCookieJarTransport.d.ts +11 -13
  135. package/dist/shared/transport/lambderCookieJarTransport.js +24 -23
  136. package/dist/shared/util/LambderCallAbort.d.ts +5 -5
  137. package/dist/shared/util/LambderCallAbort.js +5 -5
  138. package/dist/shared/util/LambderClientIp.d.ts +27 -11
  139. package/dist/shared/util/LambderClientIp.js +96 -13
  140. package/dist/shared/util/LambderContentDisposition.d.ts +10 -0
  141. package/dist/shared/util/LambderContentDisposition.js +13 -0
  142. package/dist/shared/util/LambderExpiringMap.d.ts +35 -49
  143. package/dist/shared/util/LambderExpiringMap.js +41 -57
  144. package/dist/shared/util/LambderNodeModules.js +6 -7
  145. package/dist/shared/util/LambderOptionChecks.d.ts +4 -4
  146. package/dist/shared/util/LambderOptionChecks.js +4 -4
  147. package/dist/shared/util/LambderResponseBrand.d.ts +5 -5
  148. package/dist/shared/util/LambderResponseBrand.js +5 -5
  149. package/dist/shared/util/LambderTextDigest.d.ts +7 -5
  150. package/dist/shared/util/LambderTextDigest.js +11 -5
  151. package/dist/shared/util/LambderTypeUtilities.d.ts +7 -8
  152. package/dist/shared/util/LambderTypeUtilities.js +3 -3
  153. package/dist/shared/util/boundKeyField.d.ts +20 -0
  154. package/dist/shared/util/boundKeyField.js +34 -0
  155. package/dist/shared/util/canonicalJson.d.ts +11 -0
  156. package/dist/shared/util/canonicalJson.js +28 -0
  157. package/dist/shared/util/joinKeyFields.d.ts +20 -0
  158. package/dist/shared/util/joinKeyFields.js +22 -0
  159. package/dist/shared/wire/LambderAnswerHeaders.d.ts +12 -16
  160. package/dist/shared/wire/LambderAnswerHeaders.js +12 -16
  161. package/dist/shared/wire/LambderApiContract.d.ts +98 -53
  162. package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
  163. package/dist/shared/wire/LambderApiOutcome.js +48 -23
  164. package/dist/shared/wire/LambderApiRefusal.d.ts +45 -27
  165. package/dist/shared/wire/LambderApiRefusal.js +42 -7
  166. package/dist/shared/wire/LambderApiSignature.d.ts +18 -22
  167. package/dist/shared/wire/LambderApiSignature.js +16 -19
  168. package/dist/shared/wire/LambderCallOptions.d.ts +38 -47
  169. package/dist/shared/wire/LambderCallOptions.js +9 -11
  170. package/dist/shared/wire/LambderCompressionCodec.d.ts +29 -34
  171. package/dist/shared/wire/LambderCompressionCodec.js +31 -36
  172. package/dist/shared/wire/LambderCompressionOption.d.ts +9 -9
  173. package/dist/shared/wire/LambderCompressionOption.js +9 -9
  174. package/dist/shared/wire/LambderCrashDetail.d.ts +12 -15
  175. package/dist/shared/wire/LambderCrashDetail.js +12 -15
  176. package/dist/shared/wire/LambderDefaultApiPath.d.ts +6 -0
  177. package/dist/shared/wire/LambderDefaultApiPath.js +6 -0
  178. package/dist/shared/wire/LambderHttpStatus.d.ts +6 -7
  179. package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +89 -0
  180. package/dist/shared/wire/LambderIdempotencyKeyScope.js +146 -0
  181. package/dist/shared/wire/LambderInvokeApiId.d.ts +27 -0
  182. package/dist/shared/wire/LambderInvokeApiId.js +27 -0
  183. package/dist/shared/wire/LambderOutcomeAssertions.d.ts +6 -7
  184. package/dist/shared/wire/LambderOutcomeAssertions.js +6 -7
  185. package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
  186. package/dist/shared/wire/LambderRequestPayload.js +4 -6
  187. package/dist/shared/wire/LambderUploadObjectFields.d.ts +10 -0
  188. package/dist/shared/wire/LambderUploadObjectFields.js +24 -0
  189. package/dist/shared/wire/LambderUploadRefusal.d.ts +9 -0
  190. package/dist/shared/wire/LambderUploadRefusal.js +18 -0
  191. package/dist/shared/wire/LambderUploadSchemas.d.ts +12 -0
  192. package/dist/shared/wire/LambderUploadSchemas.js +30 -0
  193. package/dist/stores/LambderCacheFiller.d.ts +48 -0
  194. package/dist/stores/LambderCacheFiller.js +119 -0
  195. package/dist/stores/LambderCacheKeys.d.ts +26 -0
  196. package/dist/stores/LambderCacheKeys.js +54 -0
  197. package/dist/stores/LambderCacheValues.d.ts +45 -0
  198. package/dist/stores/LambderCacheValues.js +74 -0
  199. package/dist/stores/LambderDdbCache.d.ts +121 -56
  200. package/dist/stores/LambderDdbCache.js +528 -225
  201. package/dist/stores/LambderDdbIdempotencyStore.d.ts +33 -22
  202. package/dist/stores/LambderDdbIdempotencyStore.js +75 -50
  203. package/dist/stores/LambderDdbRateLimiter.d.ts +76 -20
  204. package/dist/stores/LambderDdbRateLimiter.js +151 -39
  205. package/dist/stores/LambderDdbSdk.d.ts +43 -31
  206. package/dist/stores/LambderDdbSdk.js +80 -38
  207. package/dist/stores/LambderDdbSessionStore.d.ts +27 -14
  208. package/dist/stores/LambderDdbSessionStore.js +119 -47
  209. package/dist/stores/LambderHttpFileSource.d.ts +15 -6
  210. package/dist/stores/LambderHttpFileSource.js +15 -13
  211. package/dist/stores/LambderMemoryCache.d.ts +49 -0
  212. package/dist/stores/LambderMemoryCache.js +113 -0
  213. package/dist/stores/LambderMemoryIdempotencyStore.d.ts +13 -12
  214. package/dist/stores/LambderMemoryIdempotencyStore.js +31 -30
  215. package/dist/stores/LambderMemoryRateLimiter.d.ts +8 -9
  216. package/dist/stores/LambderMemoryRateLimiter.js +14 -13
  217. package/dist/stores/LambderMemorySessionStore.d.ts +14 -11
  218. package/dist/stores/LambderMemorySessionStore.js +38 -19
  219. package/dist/stores/LambderMemoryUploadBucket.d.ts +99 -0
  220. package/dist/stores/LambderMemoryUploadBucket.js +219 -0
  221. package/dist/stores/LambderS3FileSource.d.ts +21 -6
  222. package/dist/stores/LambderS3FileSource.js +12 -7
  223. package/dist/stores/LambderS3UploadBucket.d.ts +73 -0
  224. package/dist/stores/LambderS3UploadBucket.js +144 -0
  225. package/dist/stores/LambderSdkInstallHint.d.ts +11 -0
  226. package/dist/stores/LambderSdkInstallHint.js +14 -0
  227. package/dist/testing/LambderTestApp.d.ts +23 -25
  228. package/dist/testing/LambderTestApp.js +22 -24
  229. package/dist/testing/LambderTestVisitor.d.ts +10 -12
  230. package/dist/testing/LambderTestVisitor.js +15 -15
  231. package/dist/testing.d.ts +3 -0
  232. package/dist/testing.js +2 -0
  233. package/package.json +26 -3
  234. package/dist/api/LambderApiPolicyEngine.d.ts +0 -47
  235. package/dist/api/LambderApiPolicyEngine.js +0 -85
  236. package/dist/shared/util/LambderKeyFields.d.ts +0 -32
  237. package/dist/shared/util/LambderKeyFields.js +0 -34
@@ -12,17 +12,17 @@ import { LambderTestVisitor } from "./LambderTestVisitor.js";
12
12
  * stores put under it in place, and as many simulated browsers in front of it
13
13
  * as a test needs. No HTTP, no AWS, and nothing in the app restructured.
14
14
  *
15
- * The app's own declarations all run as written: its guards, its named
16
- * rate-limit policies, its idempotency settings, its session salt, cookie
17
- * options and dataRefresh, its hooks and error handlers. Only where things
18
- * rest is replaced, and from the moment this is created the stores the app
19
- * was configured with are out of the instance's reach, so a test cannot touch
20
- * a production table even by mistake. What the app reaches on its own (its
21
- * database, a mailer) is the app's to replace.
15
+ * The app's own declarations all run as written: guards, named rate-limit
16
+ * policies, idempotency settings, session salt, cookie options and
17
+ * dataRefresh, hooks and error handlers. Only where things rest is replaced:
18
+ * once this is created, the stores the app was configured with are out of the
19
+ * instance's reach, so a test cannot touch a production table by mistake.
20
+ * What the app reaches on its own (its database, a mailer) is the app's to
21
+ * replace.
22
22
  *
23
23
  * The sibling of LambderMockApp, which serves a contract from mock handlers:
24
24
  * the same verbs (`signIn`, `signOut`, `expireSessionData`, `reset`) over the
25
- * real handlers instead.
25
+ * real handlers.
26
26
  *
27
27
  * Time is not this class's: fake `Date` with the test runner
28
28
  * (`vi.useFakeTimers({ toFake: ["Date"] })`), which moves the framework, the
@@ -50,16 +50,15 @@ export class LambderTestApp {
50
50
  resetCount = 0;
51
51
  crashList = [];
52
52
  /**
53
- * The call a crash happened under. The app answers a crash with a 500
54
- * that says nothing about it, so the error has to travel beside the
55
- * answer, and with calls running concurrently (a duplicate sent while
56
- * the original is in flight is an ordinary idempotency test) only the
57
- * async context says which call a crash belongs to.
53
+ * The call a crash happened under. The app's 500 says nothing about the
54
+ * crash, so the error travels beside the answer, and with concurrent
55
+ * calls (a duplicate sent while the original is in flight is an ordinary
56
+ * idempotency test) only the async context says which call it belongs to.
58
57
  */
59
58
  crashScope = new AsyncLocalStorage();
60
59
  constructor(lambder, options = {}) {
61
- // Only a Lambder instance of this same package copy answers to the
62
- // key, so the failure is said here rather than as "is not a function".
60
+ // Only a Lambder instance from this same package copy answers to the
61
+ // key; saying so here beats a bare "is not a function".
63
62
  if (typeof lambder?.[LAMBDER_BACKEND_SWAP] !== "function") {
64
63
  throw new Error("lambderTestApp: expected a Lambder instance (what initLambder().create() returns), from the same installed copy of lambder as lambder/testing.");
65
64
  }
@@ -94,11 +93,11 @@ export class LambderTestApp {
94
93
  return { result: await this.crashScope.run(scope, run), crash: scope.crash };
95
94
  },
96
95
  issueSession: async (host, sessionKey, data, ttlSeconds) => {
97
- // Through a session controller on a request context, the way a
98
- // login handler does it, so the cookies are the ones the app's
99
- // own cookie options produce for that host.
96
+ // Through a session controller on a request context, as a
97
+ // login handler does, so the cookies are what the app's own
98
+ // cookie options produce for that host.
100
99
  const event = synthesizeLambdaHttpEvent({ method: "GET", path: "/", host }, { invoke: false });
101
- const ctx = createContext(event, localLambdaContext("lambder-test"), lambder.apiPath);
100
+ const ctx = createContext(event, localLambdaContext("lambder-test"), { apiPath: lambder.apiPath });
102
101
  const created = await lambder.getSessionController(ctx).issueSession(sessionKey, data, ttlSeconds);
103
102
  const headers = {};
104
103
  ctx.responseHeaders.applyInto(headers);
@@ -109,11 +108,10 @@ export class LambderTestApp {
109
108
  /**
110
109
  * Every error the app threw while answering a request since the last
111
110
  * reset, in order: what reached its global error handler, or the
112
- * framework's last-resort 500. The answers themselves say nothing about
113
- * what was thrown, so this is where a test reads it, and
114
- * `expect(app.crashes).toEqual([])` is how one says nothing crashed.
115
- * A refusal is not a crash, and neither is an error an `event()` rejects
116
- * with, which the test already holds.
111
+ * framework's last-resort 500. The answers say nothing about what was
112
+ * thrown, so a test reads it here; `expect(app.crashes).toEqual([])`
113
+ * says nothing crashed. A refusal is not a crash, and neither is an
114
+ * error an `event()` rejects with, which the test already holds.
117
115
  */
118
116
  get crashes() {
119
117
  return this.crashList;
@@ -100,10 +100,9 @@ export declare class LambderTestVisitor<TContract extends LambderApiContractShap
100
100
  * The full outcome, never throwing: LambderCaller.apiOutcome, through
101
101
  * this visitor. Pair it with assertApiSuccess / assertApiFailure.
102
102
  *
103
- * One thing is added to what the caller hands back. When the app crashed
104
- * answering the call, the outcome is the `server` failure any client
105
- * would get, whose error says "Request failed: 500" and nothing else;
106
- * here that error's `cause` is what the app actually threw, stack
103
+ * When the app crashed answering the call, the outcome is the `server`
104
+ * failure any client would get, whose error says only "Request failed:
105
+ * 500"; here that error's `cause` is what the app actually threw, stack
107
106
  * included, so a failing test points at the line in the handler.
108
107
  */
109
108
  readonly apiOutcome: LambderCaller<TContract, TProvidedGuards>["apiOutcome"];
@@ -117,10 +116,9 @@ export declare class LambderTestVisitor<TContract extends LambderApiContractShap
117
116
  });
118
117
  /**
119
118
  * This visitor's cookies, to inspect or clear; every call and request
120
- * reads and fills them. Emptied by the test app's reset(), which a
121
- * visitor notices here, the next time anything asks for its cookies: a
122
- * jar still holding the token of an emptied store would read as signed
123
- * in until an answer said otherwise.
119
+ * reads and fills them. The test app's reset() empties them, noticed here
120
+ * the next time anything asks: a jar still holding the token of an
121
+ * emptied store would read as signed in until an answer said otherwise.
124
122
  */
125
123
  get jar(): LambderCookieJar;
126
124
  /**
@@ -144,10 +142,10 @@ export declare class LambderTestVisitor<TContract extends LambderApiContractShap
144
142
  * tokens too, as LambderMockApp.signIn does.
145
143
  *
146
144
  * Throws when the cookies do not stick. An app that scopes its session
147
- * cookie to a domain (`cookie: { domain: ".example.com" }`) writes one
148
- * this visitor's host is not under, a browser on that host would drop it,
149
- * and so does the jar; left silent, every session call after it answers
150
- * sessionExpired with nothing to say why.
145
+ * cookie to a domain (`cookie: { domain: ".example.com" }`) this visitor's
146
+ * host is not under writes a cookie a browser on that host would drop, and
147
+ * the jar drops it too; left silent, every later session call would
148
+ * answer sessionExpired with nothing to say why.
151
149
  */
152
150
  signIn(sessionKey: string, data: TSessionData, options?: {
153
151
  ttlSeconds?: number;
@@ -31,10 +31,9 @@ export class LambderTestVisitor {
31
31
  * The full outcome, never throwing: LambderCaller.apiOutcome, through
32
32
  * this visitor. Pair it with assertApiSuccess / assertApiFailure.
33
33
  *
34
- * One thing is added to what the caller hands back. When the app crashed
35
- * answering the call, the outcome is the `server` failure any client
36
- * would get, whose error says "Request failed: 500" and nothing else;
37
- * here that error's `cause` is what the app actually threw, stack
34
+ * When the app crashed answering the call, the outcome is the `server`
35
+ * failure any client would get, whose error says only "Request failed:
36
+ * 500"; here that error's `cause` is what the app actually threw, stack
38
37
  * included, so a failing test points at the line in the handler.
39
38
  */
40
39
  apiOutcome;
@@ -53,17 +52,19 @@ export class LambderTestVisitor {
53
52
  const withVisitorHeaders = (request) => handlerTransport({ ...request, headers: { ...this.headers, ...request.headers } });
54
53
  this.caller = new LambderCaller({
55
54
  apiPath: wiring.apiPath,
56
- isCorsEnabled: false,
57
55
  apiVersion: options.apiVersion,
58
56
  apiSignatures: options.apiSignatures,
59
57
  guardInputsProvider: options.guardInputsProvider,
60
58
  logListHandler: () => { },
61
59
  // The jar is read per call rather than captured, so a call made
62
60
  // after the test app was reset carries none of the old cookies.
63
- transport: (request) => lambderCookieJarTransport(withVisitorHeaders, { jar: this.jar, host: this.host })(request),
61
+ // The visitor's session lives in that jar, so a CSRF token the
62
+ // caller read from a page's document.cookie (a test with a DOM)
63
+ // is dropped and the jar's own is posted.
64
+ transport: (request) => lambderCookieJarTransport(withVisitorHeaders, { jar: this.jar, host: this.host })({ ...request, token: "" }),
64
65
  });
65
66
  // The caller names the CSRF cookie the jar transport fills its token
66
- // from, so an app that renamed its session cookies has to be matched
67
+ // from, so an app with custom session cookie names has to be matched
67
68
  // here or every session call would post an empty token.
68
69
  if (wiring.sessionCookieNames) {
69
70
  this.caller.setSessionCookieKey(wiring.sessionCookieNames.tokenCookieKey, wiring.sessionCookieNames.csrfCookieKey);
@@ -78,10 +79,9 @@ export class LambderTestVisitor {
78
79
  }
79
80
  /**
80
81
  * This visitor's cookies, to inspect or clear; every call and request
81
- * reads and fills them. Emptied by the test app's reset(), which a
82
- * visitor notices here, the next time anything asks for its cookies: a
83
- * jar still holding the token of an emptied store would read as signed
84
- * in until an answer said otherwise.
82
+ * reads and fills them. The test app's reset() empties them, noticed here
83
+ * the next time anything asks: a jar still holding the token of an
84
+ * emptied store would read as signed in until an answer said otherwise.
85
85
  */
86
86
  get jar() {
87
87
  const resetCount = this.wiring.resetCount();
@@ -134,10 +134,10 @@ export class LambderTestVisitor {
134
134
  * tokens too, as LambderMockApp.signIn does.
135
135
  *
136
136
  * Throws when the cookies do not stick. An app that scopes its session
137
- * cookie to a domain (`cookie: { domain: ".example.com" }`) writes one
138
- * this visitor's host is not under, a browser on that host would drop it,
139
- * and so does the jar; left silent, every session call after it answers
140
- * sessionExpired with nothing to say why.
137
+ * cookie to a domain (`cookie: { domain: ".example.com" }`) this visitor's
138
+ * host is not under writes a cookie a browser on that host would drop, and
139
+ * the jar drops it too; left silent, every later session call would
140
+ * answer sessionExpired with nothing to say why.
141
141
  */
142
142
  async signIn(sessionKey, data, options = {}) {
143
143
  if (!this.wiring.sessionCookieNames)
package/dist/testing.d.ts CHANGED
@@ -18,6 +18,9 @@ export type { LambderExpectedFailure } from "./shared/wire/LambderOutcomeAsserti
18
18
  export { LambderMemorySessionStore } from "./stores/LambderMemorySessionStore.js";
19
19
  export { LambderMemoryRateLimiter } from "./stores/LambderMemoryRateLimiter.js";
20
20
  export { LambderMemoryIdempotencyStore } from "./stores/LambderMemoryIdempotencyStore.js";
21
+ export { LambderMemoryCache } from "./stores/LambderMemoryCache.js";
22
+ export { LambderMemoryUploadBucket } from "./stores/LambderMemoryUploadBucket.js";
23
+ export type { LambderMemoryUploadBucketOptions, LambderMemoryUploadObject } from "./stores/LambderMemoryUploadBucket.js";
21
24
  export { LambderLocalFileSource } from "./stores/LambderLocalFileSource.js";
22
25
  export { LambderCookieJar } from "./shared/transport/LambderCookieJar.js";
23
26
  export { LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
package/dist/testing.js CHANGED
@@ -18,6 +18,8 @@ export { assertApiSuccess, assertApiFailure } from "./shared/wire/LambderOutcome
18
18
  export { LambderMemorySessionStore } from "./stores/LambderMemorySessionStore.js";
19
19
  export { LambderMemoryRateLimiter } from "./stores/LambderMemoryRateLimiter.js";
20
20
  export { LambderMemoryIdempotencyStore } from "./stores/LambderMemoryIdempotencyStore.js";
21
+ export { LambderMemoryCache } from "./stores/LambderMemoryCache.js";
22
+ export { LambderMemoryUploadBucket } from "./stores/LambderMemoryUploadBucket.js";
21
23
  export { LambderLocalFileSource } from "./stores/LambderLocalFileSource.js";
22
24
  export { LambderCookieJar } from "./shared/transport/LambderCookieJar.js";
23
25
  export { LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "7.3.1",
3
+ "version": "8.1.1",
4
4
  "sideEffects": false,
5
5
  "description": "Opinionated serverless web framework for TypeScript on AWS Lambda: type-safe APIs from Zod schemas, DynamoDB sessions, and declarative rate limits, authorization guards and idempotency.",
6
6
  "keywords": [
@@ -53,6 +53,10 @@
53
53
  "types": "./dist/testing.d.ts",
54
54
  "default": "./dist/testing.js"
55
55
  },
56
+ "./build": {
57
+ "types": "./dist/build.d.ts",
58
+ "default": "./dist/build.js"
59
+ },
56
60
  "./package.json": "./package.json"
57
61
  },
58
62
  "typesVersions": {
@@ -65,15 +69,20 @@
65
69
  ],
66
70
  "testing": [
67
71
  "./dist/testing.d.ts"
72
+ ],
73
+ "build": [
74
+ "./dist/build.d.ts"
68
75
  ]
69
76
  }
70
77
  },
71
78
  "browser": {
72
79
  "async_hooks": false,
80
+ "child_process": false,
73
81
  "fs": false,
74
82
  "path": false,
75
83
  "zlib": false,
76
- "crypto": false
84
+ "crypto": false,
85
+ "url": false
77
86
  },
78
87
  "files": [
79
88
  "dist",
@@ -101,11 +110,14 @@
101
110
  "tough-cookie": "^6.0.2"
102
111
  },
103
112
  "peerDependencies": {
104
- "@aws-sdk/client-dynamodb": "^3.574.0",
113
+ "@aws-sdk/client-dynamodb": "^3.868.0",
105
114
  "@aws-sdk/client-lambda": "^3.574.0",
106
115
  "@aws-sdk/client-s3": "^3.574.0",
107
116
  "@aws-sdk/lib-dynamodb": "^3.574.0",
117
+ "@aws-sdk/s3-presigned-post": "^3.574.0",
118
+ "@aws-sdk/s3-request-presigner": "^3.574.0",
108
119
  "msw": "^2.0.0",
120
+ "typescript": ">=5.4",
109
121
  "zod": "^4.6.2"
110
122
  },
111
123
  "peerDependenciesMeta": {
@@ -121,8 +133,17 @@
121
133
  "@aws-sdk/lib-dynamodb": {
122
134
  "optional": true
123
135
  },
136
+ "@aws-sdk/s3-presigned-post": {
137
+ "optional": true
138
+ },
139
+ "@aws-sdk/s3-request-presigner": {
140
+ "optional": true
141
+ },
124
142
  "msw": {
125
143
  "optional": true
144
+ },
145
+ "typescript": {
146
+ "optional": true
126
147
  }
127
148
  },
128
149
  "devDependencies": {
@@ -130,6 +151,8 @@
130
151
  "@aws-sdk/client-lambda": "^3.1131.0",
131
152
  "@aws-sdk/client-s3": "^3.1127.0",
132
153
  "@aws-sdk/lib-dynamodb": "^3.913.0",
154
+ "@aws-sdk/s3-presigned-post": "^3.1141.0",
155
+ "@aws-sdk/s3-request-presigner": "^3.1141.0",
133
156
  "@types/js-cookie": "^3.0.6",
134
157
  "@types/mime-types": "^2.1.4",
135
158
  "@types/node": "^24.9.0",
@@ -1,47 +0,0 @@
1
- import type { LambderApiRequest } from "./LambderApiRequest.js";
2
- import type { LambderApiCallContext, LambderApiCallTrace } from "./LambderApiCallContext.js";
3
- import type { LambderApiAnswer } from "./LambderApiAnswer.js";
4
- import type { LambderApiDefinition } from "./LambderApiDefinition.js";
5
- import { type LambderApiGuard } from "./LambderApiGuards.js";
6
- import { type LambderApiRateLimitPolicyConfig, type LambderApiRateLimitsConfig } from "./LambderApiRateLimits.js";
7
- import { type LambderApiIdempotencyConfig } from "./LambderApiIdempotency.js";
8
- import type { LambderRateLimiter } from "../shared/contracts/LambderRateLimiter.js";
9
- import type { LambderIdempotencyStore } from "../shared/contracts/LambderIdempotencyStore.js";
10
- import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
11
- /**
12
- * Runtime side of the declarative API options: composes the three policy
13
- * subsystems (rate limits in ./LambderApiRateLimits.ts, guards in
14
- * ./LambderApiGuards.ts, idempotency in ./LambderApiIdempotency.ts), asserts
15
- * registrations against them at startup, and executes them around handlers
16
- * at request time. Owned by LambderApiPipeline; apps interact through the
17
- * create() options (rateLimits, guards, idempotency) and the per-API
18
- * options.
19
- */
20
- export declare class LambderApiPolicyEngine {
21
- private rateLimits;
22
- private guards;
23
- private idempotency;
24
- /** True once any of the three subsystems was configured. */
25
- get isConfigured(): boolean;
26
- configureRateLimits(config: LambderApiRateLimitsConfig<Record<string, LambderApiRateLimitPolicyConfig>>): void;
27
- configureGuards(guards: Record<string, LambderApiGuard<any, any, any>>): void;
28
- configureIdempotency(config: LambderApiIdempotencyConfig): void;
29
- /** The backend swap, handed on to the two subsystems that hold a store. Each answers whether it had a place for one. */
30
- [LAMBDER_BACKEND_SWAP](backends: {
31
- rateLimiter?: LambderRateLimiter;
32
- idempotencyStore?: LambderIdempotencyStore;
33
- }): {
34
- rateLimits: boolean;
35
- idempotency: boolean;
36
- };
37
- /** Startup validation of one API's declarative options. */
38
- assertRegistration(definition: LambderApiDefinition): void;
39
- /** The rate-limit policies that can be checked before the session is read: see LambderApiRateLimitsEngine.run. */
40
- runSessionlessRateLimits(request: LambderApiRequest, ctx: LambderApiCallContext, definition: LambderApiDefinition): Promise<void>;
41
- /** The remaining rate limits, then guards, in declared order. Refusals throw; the trace records each guard as it runs. */
42
- runPreflight(request: LambderApiRequest, ctx: LambderApiCallContext, definition: LambderApiDefinition, trace: LambderApiCallTrace): Promise<void>;
43
- /** Idempotency replay fast path, run before the preflight: see LambderApiIdempotencyEngine.findReplay. */
44
- findReplay(request: LambderApiRequest, ctx: LambderApiCallContext, definition: LambderApiDefinition, trace: LambderApiCallTrace): Promise<LambderApiAnswer | null>;
45
- /** Idempotency claim/replay wrapper around handler execution: see LambderApiIdempotencyEngine.withIdempotency. */
46
- withIdempotency(request: LambderApiRequest, ctx: LambderApiCallContext, definition: LambderApiDefinition, trace: LambderApiCallTrace, exec: () => Promise<LambderApiAnswer>): Promise<LambderApiAnswer>;
47
- }
@@ -1,85 +0,0 @@
1
- import { LambderApiGuardsEngine } from "./LambderApiGuards.js";
2
- import { LambderApiRateLimitsEngine } from "./LambderApiRateLimits.js";
3
- import { LambderApiIdempotencyEngine } from "./LambderApiIdempotency.js";
4
- import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
5
- /** An API that asks for idempotency: declared, and not the explicit `false` opt-out. */
6
- const usesIdempotency = (definition) => definition.idempotency !== undefined && definition.idempotency !== false;
7
- /**
8
- * Runtime side of the declarative API options: composes the three policy
9
- * subsystems (rate limits in ./LambderApiRateLimits.ts, guards in
10
- * ./LambderApiGuards.ts, idempotency in ./LambderApiIdempotency.ts), asserts
11
- * registrations against them at startup, and executes them around handlers
12
- * at request time. Owned by LambderApiPipeline; apps interact through the
13
- * create() options (rateLimits, guards, idempotency) and the per-API
14
- * options.
15
- */
16
- export class LambderApiPolicyEngine {
17
- rateLimits = new LambderApiRateLimitsEngine();
18
- guards = new LambderApiGuardsEngine();
19
- idempotency = new LambderApiIdempotencyEngine();
20
- /** True once any of the three subsystems was configured. */
21
- get isConfigured() {
22
- return this.rateLimits.isConfigured || this.guards.isConfigured || this.idempotency.isConfigured;
23
- }
24
- configureRateLimits(config) {
25
- this.rateLimits.configure(config);
26
- }
27
- configureGuards(guards) {
28
- this.guards.configure(guards);
29
- }
30
- configureIdempotency(config) {
31
- this.idempotency.configure(config);
32
- }
33
- /** The backend swap, handed on to the two subsystems that hold a store. Each answers whether it had a place for one. */
34
- [LAMBDER_BACKEND_SWAP](backends) {
35
- return {
36
- rateLimits: backends.rateLimiter ? this.rateLimits[LAMBDER_BACKEND_SWAP](backends.rateLimiter) : false,
37
- idempotency: backends.idempotencyStore ? this.idempotency[LAMBDER_BACKEND_SWAP](backends.idempotencyStore) : false,
38
- };
39
- }
40
- /** Startup validation of one API's declarative options. */
41
- assertRegistration(definition) {
42
- const { name, mode } = definition;
43
- // Each subsystem reports its own absence. One combined message would
44
- // tell an API that declares guards on an instance with no guards map
45
- // that none of the three was configured, which reads as a question
46
- // about all three when only one of them is missing.
47
- if (definition.rateLimit !== undefined && !this.rateLimits.isConfigured) {
48
- throw new Error(`Lambder: API "${name}" declares rateLimit but no rateLimits option was configured at creation.`);
49
- }
50
- if (definition.guards !== undefined && !this.guards.isConfigured) {
51
- throw new Error(`Lambder: API "${name}" declares guards but no guards option was configured at creation.`);
52
- }
53
- this.rateLimits.assertRegistration(name, mode, definition.rateLimit);
54
- this.guards.assertRegistration(name, mode, definition.guards);
55
- // `idempotency: false` is an explicit opt-out, not a use: it asks for
56
- // nothing and so needs no store behind it.
57
- if (usesIdempotency(definition)) {
58
- if (!this.idempotency.isConfigured) {
59
- throw new Error(`Lambder: API "${name}" declares idempotency but no idempotency store was configured at creation.`);
60
- }
61
- this.idempotency.assertRegistration(name, definition.idempotency);
62
- }
63
- }
64
- /** The rate-limit policies that can be checked before the session is read: see LambderApiRateLimitsEngine.run. */
65
- async runSessionlessRateLimits(request, ctx, definition) {
66
- await this.rateLimits.run(definition.name, request, ctx, definition.rateLimit, "beforeSession");
67
- }
68
- /** The remaining rate limits, then guards, in declared order. Refusals throw; the trace records each guard as it runs. */
69
- async runPreflight(request, ctx, definition, trace) {
70
- await this.rateLimits.run(definition.name, request, ctx, definition.rateLimit, "afterSession");
71
- await this.guards.run(request, ctx, definition.guards, trace);
72
- }
73
- /** Idempotency replay fast path, run before the preflight: see LambderApiIdempotencyEngine.findReplay. */
74
- async findReplay(request, ctx, definition, trace) {
75
- if (!definition.idempotency)
76
- return null;
77
- return await this.idempotency.findReplay(definition.name, request, ctx, trace);
78
- }
79
- /** Idempotency claim/replay wrapper around handler execution: see LambderApiIdempotencyEngine.withIdempotency. */
80
- async withIdempotency(request, ctx, definition, trace, exec) {
81
- if (!definition.idempotency)
82
- return await exec();
83
- return await this.idempotency.withIdempotency(definition.name, request, ctx, definition.idempotency, trace, exec);
84
- }
85
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * Joining the fields of a tracker or scope key.
3
- *
4
- * Both the rate limiter and the idempotency engine build a key by joining a
5
- * few fields with a separator, and at least one field in each is caller data:
6
- * a rate-limit key returned by a policy handler, an idempotency key posted
7
- * with the request. A plain join lets two different field lists produce one
8
- * string, so two callers land on one counter or one caller reads another's
9
- * stored answer.
10
- *
11
- * Escaping the caller's use of the separator rather than rejecting or
12
- * reserving it: the caller chose the character for its own reasons, and a
13
- * limit that refuses a legal key is a bug of its own.
14
- *
15
- * The join is one-way. Nothing here reads a key back apart, because nothing
16
- * needs to: a key is looked up, counted against or compared, never taken to
17
- * pieces. The escaping is there so that distinct field lists cannot collide,
18
- * not so that the fields can be recovered.
19
- *
20
- * LambderDdbCache's sort-key encoding looks similar and is deliberately the
21
- * other thing: a reversible escape, because a cached entry's sort key is
22
- * handed back to the caller by listSortKeys and therefore has to decode to
23
- * exactly what was written. Two schemes, two jobs; neither should be reached
24
- * for in the other's place.
25
- *
26
- * Written once because the two engines had drifted, one escaping and one not.
27
- */
28
- /**
29
- * The fields joined into one key, each escaped, so no two distinct field
30
- * lists can produce the same string.
31
- */
32
- export declare const joinKeyFields: (...fields: string[]) => string;
@@ -1,34 +0,0 @@
1
- /**
2
- * Joining the fields of a tracker or scope key.
3
- *
4
- * Both the rate limiter and the idempotency engine build a key by joining a
5
- * few fields with a separator, and at least one field in each is caller data:
6
- * a rate-limit key returned by a policy handler, an idempotency key posted
7
- * with the request. A plain join lets two different field lists produce one
8
- * string, so two callers land on one counter or one caller reads another's
9
- * stored answer.
10
- *
11
- * Escaping the caller's use of the separator rather than rejecting or
12
- * reserving it: the caller chose the character for its own reasons, and a
13
- * limit that refuses a legal key is a bug of its own.
14
- *
15
- * The join is one-way. Nothing here reads a key back apart, because nothing
16
- * needs to: a key is looked up, counted against or compared, never taken to
17
- * pieces. The escaping is there so that distinct field lists cannot collide,
18
- * not so that the fields can be recovered.
19
- *
20
- * LambderDdbCache's sort-key encoding looks similar and is deliberately the
21
- * other thing: a reversible escape, because a cached entry's sort key is
22
- * handed back to the caller by listSortKeys and therefore has to decode to
23
- * exactly what was written. Two schemes, two jobs; neither should be reached
24
- * for in the other's place.
25
- *
26
- * Written once because the two engines had drifted, one escaping and one not.
27
- */
28
- /** One field, with the separator and its own escape character escaped. */
29
- const escapeKeyField = (value) => value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
30
- /**
31
- * The fields joined into one key, each escaped, so no two distinct field
32
- * lists can produce the same string.
33
- */
34
- export const joinKeyFields = (...fields) => fields.map(escapeKeyField).join("|");