lambder 6.0.2 → 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 +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,66 @@
1
+ /**
2
+ * The idempotency vocabulary every part of Lambder shares: what a stored
3
+ * answer looks like, what claiming a scope reports, and the four methods the
4
+ * idempotency engine asks of a store.
5
+ *
6
+ * Kept apart from the engine for the same reason LambderRateLimiter is:
7
+ * a store implements this and nothing else, and importing it from the engine
8
+ * would pull the engine (and through it the refusal machinery) into every
9
+ * store's import graph. Pure and dependency-free, so the mock runtime and the
10
+ * browser entry can resolve it.
11
+ */
12
+ /** A stored answer: what a completed record replays. */
13
+ export type LambderIdempotencyDoneRecord = {
14
+ statusCode: number;
15
+ /** Response headers stored with the record (normalized multi-value map). */
16
+ headers: Record<string, string[]>;
17
+ body: string;
18
+ };
19
+ export type LambderIdempotencyBeginResult = {
20
+ state: "new";
21
+ ownerToken: string;
22
+ } | {
23
+ state: "pending";
24
+ } | ({
25
+ state: "done";
26
+ } & LambderIdempotencyDoneRecord);
27
+ /**
28
+ * What the idempotency engine asks of a store: one record per scope, claimed
29
+ * atomically, settled by the claim's owner. LambderDdbIdempotencyStore and
30
+ * LambderMemoryIdempotencyStore implement it; an app may bring its own.
31
+ *
32
+ * The rules an implementation has to keep are the ones tests/store-conformance
33
+ * asserts against every implementation, and the three that are easy to get
34
+ * wrong are worth naming here.
35
+ *
36
+ * A read hands back a COPY of the record, never the stored object, because a
37
+ * caller applies its own headers onto what it gets back.
38
+ *
39
+ * A write takes a copy too: complete() must not keep the caller's record or
40
+ * its headers map, because that object goes on being used after the call
41
+ * returns (the pipeline writes the call's own headers into it on the way
42
+ * out). A store that retained it would let one request's Set-Cookie become
43
+ * part of the stored answer and replay to everybody else. A store that goes
44
+ * over the wire gets this for free, since serializing IS the copy; one that
45
+ * keeps the record in the process has to make it.
46
+ *
47
+ * Size decides before ownership does, so an answer too big to store reports
48
+ * "too-large" even when the claim has meanwhile been lost. The engine
49
+ * releases the claim either way, so the order only shows in which reason it
50
+ * is told, but an implementation that inverted it would disagree with every
51
+ * other one.
52
+ */
53
+ export interface LambderIdempotencyStore {
54
+ /** The stored answer when a completed, unexpired record exists, null otherwise (absent, pending, or expired). */
55
+ peek(scopeKey: string): Promise<LambderIdempotencyDoneRecord | null>;
56
+ /** Claims the scope: "new" with the ownerToken to settle with, "pending" when another request owns it, "done" with the answer to replay. */
57
+ begin(scopeKey: string, options: {
58
+ pendingTtlSeconds: number;
59
+ }): Promise<LambderIdempotencyBeginResult>;
60
+ /** Stores the answer over the claim: "stored", "too-large" (nothing written; release the claim), or "lost" (the claim expired and a retry took the scope). */
61
+ complete(scopeKey: string, ownerToken: string, record: LambderIdempotencyDoneRecord & {
62
+ ttlSeconds: number;
63
+ }): Promise<"stored" | "too-large" | "lost">;
64
+ /** Releases the claim without storing; a lost claim makes this a silent no-op. */
65
+ abandon(scopeKey: string, ownerToken: string): Promise<void>;
66
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The idempotency vocabulary every part of Lambder shares: what a stored
3
+ * answer looks like, what claiming a scope reports, and the four methods the
4
+ * idempotency engine asks of a store.
5
+ *
6
+ * Kept apart from the engine for the same reason LambderRateLimiter is:
7
+ * a store implements this and nothing else, and importing it from the engine
8
+ * would pull the engine (and through it the refusal machinery) into every
9
+ * store's import graph. Pure and dependency-free, so the mock runtime and the
10
+ * browser entry can resolve it.
11
+ */
12
+ export {};
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The rate-limit vocabulary every part of Lambder shares: the fixed windows a
3
+ * policy may cap, the policy shape, what an exceeded check reports, and the
4
+ * one method the rate-limit engine asks of a limiter.
5
+ *
6
+ * Kept apart from the DynamoDB limiter on purpose. The engine needs only this
7
+ * table, and importing it from the store would pull the DynamoDB SDK loader
8
+ * into the engine's import graph, which is what kept the policy layer from
9
+ * running anywhere but inside a Lambda. Pure and dependency-free, so the
10
+ * mock runtime and the browser entry can resolve it.
11
+ */
12
+ /**
13
+ * The fixed windows a policy may cap, smallest first (the evaluation order),
14
+ * with their length. The policy type derives from this table, so the two can
15
+ * never drift.
16
+ */
17
+ export declare const RATE_LIMIT_WINDOWS: readonly [{
18
+ readonly key: "perMin";
19
+ readonly seconds: 60;
20
+ }, {
21
+ readonly key: "per10Min";
22
+ readonly seconds: number;
23
+ }, {
24
+ readonly key: "perHour";
25
+ readonly seconds: number;
26
+ }, {
27
+ readonly key: "perDay";
28
+ readonly seconds: number;
29
+ }, {
30
+ readonly key: "perWeek";
31
+ readonly seconds: number;
32
+ }, {
33
+ readonly key: "perMonth";
34
+ readonly seconds: number;
35
+ }];
36
+ export type LambderRateLimitWindow = (typeof RATE_LIMIT_WINDOWS)[number]["key"];
37
+ /** Per-window caps. A window that is absent or 0 is not enforced. */
38
+ export type LambderRateLimitPolicy = Partial<Record<LambderRateLimitWindow, number>>;
39
+ /**
40
+ * The window that refused: which one, its limit, and the epoch second at
41
+ * which that fixed window resets (Retry-After derives from it).
42
+ */
43
+ export type LambderRateLimitExceeded = {
44
+ window: LambderRateLimitWindow;
45
+ limit: number;
46
+ resetAt: number;
47
+ };
48
+ /** `false` when allowed, otherwise the window whose limit was hit. */
49
+ export type LambderRateLimitResult = false | LambderRateLimitExceeded;
50
+ /**
51
+ * What the rate-limit engine asks of a limiter: count one attempt against
52
+ * every window the policy caps and say whether any of them is over. The
53
+ * DynamoDB limiter and the in-memory one implement it; an app may bring its
54
+ * own (Redis, a database) by implementing this one method.
55
+ *
56
+ * The engine validates a policy before it ever reaches here, so every capped
57
+ * window arrives as a non-negative whole number and a limiter never has to
58
+ * invent an answer for a nonsense one. A caller reaching isRateLimited
59
+ * directly owes the same precondition: the two shipped implementations
60
+ * disagree on a negative cap (one refuses the first attempt, the other allows
61
+ * it) because neither was ever meant to be asked.
62
+ *
63
+ * The tracker key is bounded there too: the variable half of it (a custom
64
+ * key handler's return, a session key) is replaced by its own sha256 past
65
+ * 1024 UTF-8 bytes, so an implementation with a key limit of its own never
66
+ * meets a key it has to refuse. That matters because a limiter's refusal is a
67
+ * throw, and a throw is what failOpen swallows into no limit at all.
68
+ */
69
+ export interface LambderRateLimiter {
70
+ isRateLimited(trackerKey: string, policy: LambderRateLimitPolicy): Promise<LambderRateLimitResult>;
71
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The rate-limit vocabulary every part of Lambder shares: the fixed windows a
3
+ * policy may cap, the policy shape, what an exceeded check reports, and the
4
+ * one method the rate-limit engine asks of a limiter.
5
+ *
6
+ * Kept apart from the DynamoDB limiter on purpose. The engine needs only this
7
+ * table, and importing it from the store would pull the DynamoDB SDK loader
8
+ * into the engine's import graph, which is what kept the policy layer from
9
+ * running anywhere but inside a Lambda. Pure and dependency-free, so the
10
+ * mock runtime and the browser entry can resolve it.
11
+ */
12
+ /**
13
+ * The fixed windows a policy may cap, smallest first (the evaluation order),
14
+ * with their length. The policy type derives from this table, so the two can
15
+ * never drift.
16
+ */
17
+ export const RATE_LIMIT_WINDOWS = [
18
+ { key: "perMin", seconds: 60 },
19
+ { key: "per10Min", seconds: 10 * 60 },
20
+ { key: "perHour", seconds: 60 * 60 },
21
+ { key: "perDay", seconds: 24 * 60 * 60 },
22
+ { key: "perWeek", seconds: 7 * 24 * 60 * 60 },
23
+ { key: "perMonth", seconds: 30 * 24 * 60 * 60 },
24
+ ];
@@ -0,0 +1,72 @@
1
+ /**
2
+ * What a session is at rest, and what the session manager asks of the place
3
+ * it rests in.
4
+ *
5
+ * The manager owns the model (token format, hashing, expiry, sliding
6
+ * writes, dataRefresh, regeneration); a store owns nothing but the five
7
+ * operations below, keyed by the two hashes. LambderDdbSessionStore is the
8
+ * DynamoDB implementation and LambderMemorySessionStore the in-memory one;
9
+ * an app may bring its own (Redis, a database) by implementing this
10
+ * interface. Records written by one store read back through another with
11
+ * the same shape, because the shape is the manager's, not the store's.
12
+ */
13
+ /**
14
+ * A session record. The two hashes are the record's identity: the partition
15
+ * is a salted hash of the app's sessionKey (a user id, say) and the sort key
16
+ * is a hash of the bearer secret the client holds. The raw secrets are never
17
+ * stored, so a read of the store yields no usable credentials.
18
+ */
19
+ export type LambderSessionRecord<SessionData = unknown> = {
20
+ /** Salted sha256 of the sessionKey: the partition every session of one subject shares. */
21
+ sessionKeyHash: string;
22
+ /** sha256 of the sort-key secret the client's session cookie carries. */
23
+ secretHash: string;
24
+ /** sha256 of the raw CSRF token the client's csrf cookie carries. */
25
+ csrfTokenHash: string;
26
+ /** The app's own key for the subject (a user id), as given to createSession. */
27
+ sessionKey: string;
28
+ data: SessionData;
29
+ /** Epoch seconds. */
30
+ createdAt: number;
31
+ expiresAt: number;
32
+ lastAccessedAt: number;
33
+ ttlInSeconds: number;
34
+ /**
35
+ * When `data` must be renewed via the dataRefresh callback (epoch seconds).
36
+ * Only present when dataRefresh is configured; independent of the
37
+ * session's own expiresAt.
38
+ */
39
+ dataExpiresAt?: number;
40
+ };
41
+ export interface LambderSessionStore<SessionData = unknown> {
42
+ /**
43
+ * Whether records live only for as long as this process does. The session
44
+ * manager reads it to refuse a non-cryptographic LambderSessionCrypto
45
+ * over a store that outlives the process: hashing that is not hashing is
46
+ * survivable in memory for a development run and never survivable at
47
+ * rest, where the records would be usable credentials.
48
+ */
49
+ readonly isMemoryOnly: boolean;
50
+ /**
51
+ * The record under the two hashes, or null. A failing read throws; the
52
+ * manager wraps it as a LambderSessionReadError.
53
+ *
54
+ * A store MAY hand back a record that is past its expiresAt: a DynamoDB
55
+ * TTL deletes within days rather than at the second, and an implementation
56
+ * over a plain table has nothing that sweeps at all. Expiry is the
57
+ * manager's to enforce, and it does, on every read. A store that drops
58
+ * expired records itself is doing housekeeping, not policy.
59
+ */
60
+ get(sessionKeyHash: string, secretHash: string): Promise<LambderSessionRecord<SessionData> | null>;
61
+ /** Writes the record, replacing any under the same hashes. */
62
+ put(record: LambderSessionRecord<SessionData>): Promise<void>;
63
+ delete(sessionKeyHash: string, secretHash: string): Promise<void>;
64
+ /** Every secretHash stored under the partition: the sessions of one subject. */
65
+ listSecretHashes(sessionKeyHash: string): Promise<string[]>;
66
+ /**
67
+ * Stamps dataExpiresAt on one record, only if it still exists: neither
68
+ * resurrects a session deleted in between nor overwrites a concurrent
69
+ * write. A record that no longer exists is skipped silently.
70
+ */
71
+ markDataExpired(sessionKeyHash: string, secretHash: string, at: number): Promise<void>;
72
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * What a session is at rest, and what the session manager asks of the place
3
+ * it rests in.
4
+ *
5
+ * The manager owns the model (token format, hashing, expiry, sliding
6
+ * writes, dataRefresh, regeneration); a store owns nothing but the five
7
+ * operations below, keyed by the two hashes. LambderDdbSessionStore is the
8
+ * DynamoDB implementation and LambderMemorySessionStore the in-memory one;
9
+ * an app may bring its own (Redis, a database) by implementing this
10
+ * interface. Records written by one store read back through another with
11
+ * the same shape, because the shape is the manager's, not the store's.
12
+ */
13
+ export {};
@@ -0,0 +1,139 @@
1
+ import type { LambderApiHttpAnswer } from "../wire/LambderApiOutcome.js";
2
+ import type { LambderCompressedGzipPayload, LambderCompressedBrotliPayload } from "../wire/LambderRequestPayload.js";
3
+ /**
4
+ * What LambderCaller hands its transport: the envelope fields of one call
5
+ * plus what a transport may need beside them. Cookies and the client IP are
6
+ * for transports that build the request themselves (in-process, mock); the
7
+ * browser's fetch attaches its own cookies and ignores both.
8
+ */
9
+ export type LambderApiTransportRequest = {
10
+ apiPath: string;
11
+ apiName: string;
12
+ version?: string;
13
+ /** The CSRF token the caller read from its cookie; "" when it holds none. */
14
+ token: string;
15
+ /**
16
+ * The cookie name the caller reads that token from. A transport that
17
+ * fills the token in itself (the cookie-jar decorator, where there is no
18
+ * document to read) needs the same name, and taking it from the caller is
19
+ * what keeps the two from being configured apart.
20
+ */
21
+ csrfCookieKey?: string;
22
+ siteHost: string;
23
+ /** The payload, when it goes plainly. */
24
+ payload?: unknown;
25
+ /** The compressed pair, when it goes compressed instead of `payload`. */
26
+ compressed?: LambderCompressedGzipPayload | LambderCompressedBrotliPayload;
27
+ guardInputs?: Record<string, unknown>;
28
+ idempotencyKey?: string;
29
+ headers?: Record<string, string>;
30
+ /** Cookie header pairs, `name=value`. */
31
+ cookies?: string[];
32
+ clientIp?: string;
33
+ signal?: AbortSignal;
34
+ };
35
+ /**
36
+ * Why a transport could not deliver a call. `network` is the default reading
37
+ * of a rejection: nothing came back. `protocol` says the call reached the
38
+ * callee and no answer came of it, either because what came back was not one
39
+ * or because the callee threw instead of answering, which is a server or
40
+ * wiring fault and should not be reported to a developer as flaky
41
+ * connectivity. `timeout` belongs to the caller, which knows whether its own
42
+ * abort fired.
43
+ */
44
+ export type LambderTransportFailureReason = "network" | "protocol";
45
+ /**
46
+ * A transport saying why it failed, instead of leaving the caller to assume.
47
+ * Thrown by a transport; the caller reads `reason` and keeps `cause`, so the
48
+ * thing that actually went wrong survives the trip.
49
+ */
50
+ export declare class LambderTransportFailure extends Error {
51
+ readonly isLambderTransportFailure: true;
52
+ readonly reason: LambderTransportFailureReason;
53
+ constructor(reason: LambderTransportFailureReason, message: string, options?: {
54
+ cause?: unknown;
55
+ });
56
+ }
57
+ /** Brand-based type guard, so a duplicate install of the package still matches. */
58
+ export declare const isLambderTransportFailure: (err: unknown) => err is LambderTransportFailure;
59
+ /**
60
+ * Delivers one call and hands back the answer in the accessor form
61
+ * resolveApiOutcome() reads. What a transport owes its caller, since nothing
62
+ * but this contract stands between a call and a wrong outcome:
63
+ *
64
+ * - **Any HTTP status is an answer.** A 4xx or 5xx resolves, status and body
65
+ * included, because resolveApiOutcome() is the one place that reads what a
66
+ * status means. A transport that rejects on a status throws away the
67
+ * envelope a refusal, a validation failure or a crash arrived in.
68
+ * - **A rejection is a transport failure.** The caller reports it as
69
+ * `network` unless the transport threw a LambderTransportFailure naming
70
+ * another reason, or `timeout` when the caller's own abort fired. That is
71
+ * the channel for the real cause too: a LambderTransportFailure keeps it as
72
+ * `cause`, where the caller's `outcome.error` carries it.
73
+ * - **`request.signal` must be honoured**, by rejecting as soon as it aborts.
74
+ * It is the only thing that makes the caller's `timeoutMs` and its per-call
75
+ * `signal` mean anything: a transport that ignores it leaves a call waiting
76
+ * for as long as the callee takes, whatever the caller asked for. Work
77
+ * already begun need not be cancellable (an in-process handler is not); the
78
+ * obligation is to stop waiting, not to stop the callee.
79
+ * - **Timeouts and retries belong to the caller.** A transport starts no
80
+ * clock of its own and retries nothing, so one call is one delivery
81
+ * attempt and an idempotency key means what it says.
82
+ *
83
+ * Four transports ship: fetch (lambderFetchTransport, the browser default),
84
+ * an in-process Lambder handler (lambderHandlerTransport, for tests), the
85
+ * mock runtime (LambderMockApp.transport), and a cookie-jar decorator over
86
+ * any of them (lambderCookieJarTransport).
87
+ */
88
+ export type LambderApiTransport = (request: LambderApiTransportRequest) => Promise<LambderApiHttpAnswer>;
89
+ /**
90
+ * The fields of the request envelope, in the order they go on the wire: the
91
+ * one statement of what a call sends, for every sender there is.
92
+ *
93
+ * Two senders write it. A transport hands the payload over as a value
94
+ * (buildTransportEnvelope, below); LambderInvokeCaller has already serialized
95
+ * its payload to decide whether to compress it, and splices that JSON onto the
96
+ * end rather than parsing and stringifying it a second time
97
+ * (buildEnvelopeJson, in invoke/LambderLambdaEvent.ts). The splice sits on top
98
+ * of this function precisely so that a new envelope field cannot be added to
99
+ * one sender and missed by the other, which nothing on the wire would catch.
100
+ */
101
+ export declare const buildEnvelopeFields: (fields: {
102
+ apiName: string;
103
+ version?: string;
104
+ /** The CSRF token, as the envelope names it. */
105
+ token: string;
106
+ siteHost: string;
107
+ /**
108
+ * The plain payload's slot: `{ payload }` from a sender holding the value,
109
+ * and nothing from one that splices its own JSON in afterwards. A
110
+ * compressed payload replaces it.
111
+ */
112
+ payloadSlot?: {
113
+ payload: unknown;
114
+ };
115
+ compressed?: LambderCompressedGzipPayload | LambderCompressedBrotliPayload | null;
116
+ guardInputs?: Record<string, unknown>;
117
+ idempotencyKey?: string;
118
+ }) => Record<string, unknown>;
119
+ /**
120
+ * The body envelope every transport posts, as a plain object: the same
121
+ * fields whichever transport carries them, so the server and the mock
122
+ * runtime read one shape.
123
+ */
124
+ export declare const buildTransportEnvelope: (request: LambderApiTransportRequest) => Record<string, unknown>;
125
+ /**
126
+ * Where a call is actually going, read off its apiPath: an absolute one names
127
+ * the host and the scheme the request reaches, a relative one only its path.
128
+ * Both the cookie-jar decorator (a cookie's scope is the host and path of the
129
+ * request, not of the page) and the in-process handler transport (whose event
130
+ * needs a path, not a URL, or no route matches) split it this way.
131
+ */
132
+ type LambderApiPathTarget = {
133
+ host?: string;
134
+ path: string;
135
+ /** Whether the target speaks https; undefined when the apiPath names no scheme. */
136
+ secure?: boolean;
137
+ };
138
+ export declare const resolveApiPathTarget: (apiPath: string) => LambderApiPathTarget;
139
+ export {};
@@ -0,0 +1,65 @@
1
+ /**
2
+ * A transport saying why it failed, instead of leaving the caller to assume.
3
+ * Thrown by a transport; the caller reads `reason` and keeps `cause`, so the
4
+ * thing that actually went wrong survives the trip.
5
+ */
6
+ export class LambderTransportFailure extends Error {
7
+ isLambderTransportFailure = true;
8
+ reason;
9
+ constructor(reason, message, options = {}) {
10
+ super(message, options.cause !== undefined ? { cause: options.cause } : undefined);
11
+ this.name = "LambderTransportFailure";
12
+ this.reason = reason;
13
+ }
14
+ }
15
+ /** Brand-based type guard, so a duplicate install of the package still matches. */
16
+ export const isLambderTransportFailure = (err) => err instanceof Error && err.isLambderTransportFailure === true;
17
+ /**
18
+ * The fields of the request envelope, in the order they go on the wire: the
19
+ * one statement of what a call sends, for every sender there is.
20
+ *
21
+ * Two senders write it. A transport hands the payload over as a value
22
+ * (buildTransportEnvelope, below); LambderInvokeCaller has already serialized
23
+ * its payload to decide whether to compress it, and splices that JSON onto the
24
+ * end rather than parsing and stringifying it a second time
25
+ * (buildEnvelopeJson, in invoke/LambderLambdaEvent.ts). The splice sits on top
26
+ * of this function precisely so that a new envelope field cannot be added to
27
+ * one sender and missed by the other, which nothing on the wire would catch.
28
+ */
29
+ export const buildEnvelopeFields = (fields) => ({
30
+ apiName: fields.apiName,
31
+ version: fields.version,
32
+ token: fields.token,
33
+ siteHost: fields.siteHost,
34
+ ...(fields.compressed ?? fields.payloadSlot ?? {}),
35
+ ...(fields.guardInputs !== undefined ? { guardInputs: fields.guardInputs } : {}),
36
+ ...(fields.idempotencyKey !== undefined ? { idempotencyKey: fields.idempotencyKey } : {}),
37
+ });
38
+ /**
39
+ * The body envelope every transport posts, as a plain object: the same
40
+ * fields whichever transport carries them, so the server and the mock
41
+ * runtime read one shape.
42
+ */
43
+ export const buildTransportEnvelope = (request) => buildEnvelopeFields({
44
+ apiName: request.apiName,
45
+ version: request.version,
46
+ token: request.token,
47
+ siteHost: request.siteHost,
48
+ payloadSlot: { payload: request.payload },
49
+ compressed: request.compressed,
50
+ guardInputs: request.guardInputs,
51
+ idempotencyKey: request.idempotencyKey,
52
+ });
53
+ export const resolveApiPathTarget = (apiPath) => {
54
+ // Only an absolute URL carries a host. Parsing "/api" would need a base,
55
+ // and inventing one invents a host to go with it.
56
+ if (!/^https?:\/\//i.test(apiPath))
57
+ return { path: apiPath };
58
+ try {
59
+ const url = new URL(apiPath);
60
+ return { host: url.hostname, path: url.pathname, secure: url.protocol === "https:" };
61
+ }
62
+ catch {
63
+ return { path: apiPath };
64
+ }
65
+ };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * The cookie jar a transport carries (LambderCookieJar), and the Set-Cookie
3
+ * parsing behind it. tough-cookie is reached from this module and no other, so
4
+ * a bundle that never carries a jar drops it.
5
+ */
6
+ /** A cookie as the jar holds it. Epoch milliseconds for `expires`, and `host` set only for a host-only cookie. */
7
+ export type LambderStoredCookie = {
8
+ name: string;
9
+ value: string;
10
+ /** The Domain attribute, without its leading dot; undefined for a host-only cookie. */
11
+ domain: string | undefined;
12
+ /** The host that set it, when it carried no Domain. Absent when the jar never learned a host. */
13
+ host?: string;
14
+ path: string;
15
+ /** Epoch milliseconds; undefined for a browser-session cookie. */
16
+ expires: number | undefined;
17
+ httpOnly: boolean;
18
+ /** Withheld from a target known to be plain http; a target of unknown scheme (in-process, mock) still carries it. */
19
+ secure: boolean;
20
+ };
21
+ /** Where a call is going, as far as a cookie's scope is concerned. An absent field is one the caller could not know, and matches anything. */
22
+ type LambderCookieTarget = {
23
+ host?: string;
24
+ path?: string;
25
+ /** False only for a target known to speak plain http, which neither accepts a Secure cookie nor sends one. */
26
+ secure?: boolean;
27
+ };
28
+ /**
29
+ * One Set-Cookie header value, read the way a browser reads it. `requestPath`
30
+ * is the path the answer came from, which decides the default Path. Returns
31
+ * null for a header no browser would keep.
32
+ */
33
+ export declare const parseSetCookie: (header: string, now: number, requestPath?: string) => LambderStoredCookie | null;
34
+ /**
35
+ * A browser's cookie storage, for transports that have no browser: the
36
+ * in-process handler transport in a Node test, and the mock runtime's direct
37
+ * transport. It stores what an answer's Set-Cookie headers set, honours their
38
+ * expiry and deletion, and hands back the Cookie pairs the next request should
39
+ * carry. One jar is one browser; two jars are two.
40
+ *
41
+ * The rules themselves are tough-cookie's, which is the reference
42
+ * implementation of RFC 6265 and carries the public suffix list: domain and
43
+ * path matching, default-path, Max-Age against Expires, Secure, HttpOnly, and
44
+ * the __Host-/__Secure- prefixes. That list is the part worth importing rather
45
+ * than writing. A hand-rolled check can tell that `Domain=com` is a registry
46
+ * suffix by counting labels, and cannot tell that `co.uk` is one, so a
47
+ * hand-rolled jar either trusts `Domain=co.uk` or bans every two-label domain.
48
+ *
49
+ * What stays Lambder's is the shape of the questions a transport asks: whole
50
+ * Set-Cookie header lists in (storeSetCookies), `name=value` pairs out
51
+ * (cookiePairs), and a target given as a host and path rather than a URL,
52
+ * since a transport that never speaks HTTP has no URL to give. A field the
53
+ * caller omits is one it could not know, and an unknown field matches
54
+ * anything: a jar pointed at a single host is the ordinary case, and refusing
55
+ * to answer it until it can name that host would make the common setup the
56
+ * awkward one.
57
+ *
58
+ * SameSite is stored but never consulted. It answers "did another site
59
+ * initiate this", and a transport call has no initiating site: every call here
60
+ * is same-site by construction.
61
+ */
62
+ export declare class LambderCookieJar {
63
+ private readonly jar;
64
+ private readonly now;
65
+ private readonly host;
66
+ /** `host` is the host this jar is the browser of: the sender of every answer and the target of every request that names none. */
67
+ constructor(options?: {
68
+ now?: () => number;
69
+ host?: string;
70
+ });
71
+ /** The host a call is about, or the stand-in when neither the call nor the jar names one. */
72
+ private hostFor;
73
+ /**
74
+ * Applies Set-Cookie header values as a browser would: stores, replaces,
75
+ * and deletes on an expiry in the past. `request` says where the answer
76
+ * came from. Its `host` is the sending host, which every Domain is checked
77
+ * against, and its `path` is the default Path of a cookie that names none.
78
+ *
79
+ * A Domain the sender is not under does not narrow a cookie, it voids it
80
+ * (RFC 6265 section 5.3 step 6), and so does a Domain that is a public
81
+ * suffix. Both are how evil.example.com would otherwise plant a cookie
82
+ * that bank.example.com is handed on the next call.
83
+ */
84
+ storeSetCookies(headers: readonly string[], request?: LambderCookieTarget): void;
85
+ /** Every live cookie. */
86
+ list(): LambderStoredCookie[];
87
+ /**
88
+ * The Cookie header pairs the next request carries, as `name=value`, in
89
+ * the order RFC 6265 section 5.4 puts them in: the longest Path first,
90
+ * and among equal paths the one set first. Servers that read only the
91
+ * first value of a repeated name depend on that order, and so does any
92
+ * test reasoning about which of two same-named cookies wins.
93
+ *
94
+ * Only the cookies whose scope covers the target travel. A field the
95
+ * target leaves out is one the caller could not know, and matches
96
+ * anything: a caller that cannot name its own host still gets the cookies
97
+ * of the one host its jar talks to.
98
+ */
99
+ cookiePairs(target?: LambderCookieTarget): string[];
100
+ /**
101
+ * One cookie's value as a page's script would read it: HttpOnly cookies
102
+ * are invisible unless asked for, which is how the transport fills in the
103
+ * CSRF token the caller would have read from document.cookie.
104
+ */
105
+ get(name: string, options?: {
106
+ includeHttpOnly?: boolean;
107
+ } & LambderCookieTarget): string | undefined;
108
+ /**
109
+ * The live cookies whose scope reaches this target, in RFC 6265 send
110
+ * order. Delegated to tough-cookie whenever the target names a host,
111
+ * which is the case worth getting exactly right; an unnamed host falls
112
+ * back to every cookie the jar holds, filtered by the rules that do not
113
+ * need one and ordered by the same rule.
114
+ */
115
+ private matchingCookies;
116
+ /** Number of live cookies. */
117
+ get size(): number;
118
+ /** Forgets every cookie: the browser's storage cleared. */
119
+ clear(): void;
120
+ }
121
+ export {};