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.
- package/CHANGELOG.md +2316 -0
- package/README.md +60 -33
- package/dist/api/LambderApiAnswer.d.ts +40 -0
- package/dist/api/LambderApiAnswer.js +19 -0
- package/dist/api/LambderApiCallContext.d.ts +38 -0
- package/dist/api/LambderApiCallContext.js +13 -0
- package/dist/api/LambderApiDefinition.d.ts +18 -0
- package/dist/api/LambderApiDefinition.js +1 -0
- package/dist/api/LambderApiEnvelope.d.ts +67 -0
- package/dist/api/LambderApiEnvelope.js +180 -0
- package/dist/api/LambderApiGuards.d.ts +302 -0
- package/dist/api/LambderApiGuards.js +134 -0
- package/dist/api/LambderApiIdempotency.d.ts +122 -0
- package/dist/api/LambderApiIdempotency.js +330 -0
- package/dist/api/LambderApiPipeline.d.ts +134 -0
- package/dist/api/LambderApiPipeline.js +221 -0
- package/dist/api/LambderApiPolicyEngine.d.ts +36 -0
- package/dist/api/LambderApiPolicyEngine.js +77 -0
- package/dist/api/LambderApiRateLimits.d.ts +206 -0
- package/dist/api/LambderApiRateLimits.js +239 -0
- package/dist/api/LambderApiRequest.d.ts +101 -0
- package/dist/api/LambderApiRequest.js +129 -0
- package/dist/api/LambderApiValidationRefusal.d.ts +32 -0
- package/dist/api/LambderApiValidationRefusal.js +40 -0
- package/dist/client/LambderCaller.d.ts +62 -55
- package/dist/client/LambderCaller.js +147 -90
- package/dist/client/lambderFetchTransport.d.ts +9 -0
- package/dist/client/lambderFetchTransport.js +71 -0
- package/dist/client.d.ts +20 -10
- package/dist/client.js +11 -5
- package/dist/core/Lambder.d.ts +117 -253
- package/dist/core/Lambder.js +374 -341
- package/dist/core/LambderContext.d.ts +54 -44
- package/dist/core/LambderContext.js +41 -110
- package/dist/core/LambderCreateOptions.d.ts +285 -0
- package/dist/core/LambderCreateOptions.js +44 -0
- package/dist/core/LambderFiles.d.ts +1 -45
- package/dist/core/LambderFiles.js +18 -38
- package/dist/core/LambderIndexHtml.d.ts +37 -0
- package/dist/core/LambderIndexHtml.js +87 -0
- package/dist/core/LambderPolicyBuilders.d.ts +17 -0
- package/dist/core/LambderPolicyBuilders.js +16 -0
- package/dist/core/LambderPublicFiles.d.ts +5 -2
- package/dist/core/LambderPublicFiles.js +7 -2
- package/dist/core/LambderResolver.d.ts +8 -6
- package/dist/core/LambderResponse.d.ts +29 -11
- package/dist/core/LambderResponse.js +96 -49
- package/dist/core/LambderResponseBuilder.d.ts +18 -14
- package/dist/core/LambderResponseBuilder.js +19 -25
- package/dist/core/LambderRouting.d.ts +18 -7
- package/dist/core/LambderRouting.js +17 -7
- package/dist/core/LambderTemplatingEngine.d.ts +0 -62
- package/dist/core/LambderTemplatingEngine.js +7 -3
- package/dist/index.d.ts +85 -32
- package/dist/index.js +44 -16
- package/dist/invoke/LambderInvokeCaller.d.ts +46 -139
- package/dist/invoke/LambderInvokeCaller.js +140 -335
- package/dist/invoke/LambderInvokeOutcome.d.ts +165 -0
- package/dist/invoke/LambderInvokeOutcome.js +129 -0
- package/dist/invoke/LambderLambdaEvent.d.ts +81 -0
- package/dist/invoke/LambderLambdaEvent.js +187 -0
- package/dist/invoke/lambderHandlerTransport.d.ts +36 -0
- package/dist/invoke/lambderHandlerTransport.js +89 -0
- package/dist/mock/LambderMockApp.d.ts +352 -0
- package/dist/mock/LambderMockApp.js +815 -0
- package/dist/mock/LambderMockBrowserCookies.d.ts +55 -0
- package/dist/mock/LambderMockBrowserCookies.js +76 -0
- package/dist/mock/LambderMockCallRecorder.d.ts +85 -0
- package/dist/mock/LambderMockCallRecorder.js +183 -0
- package/dist/mock/LambderMockCreateOptions.d.ts +161 -0
- package/dist/mock/LambderMockCreateOptions.js +9 -0
- package/dist/mock/LambderMockEntryRegistry.d.ts +52 -0
- package/dist/mock/LambderMockEntryRegistry.js +126 -0
- package/dist/mock/LambderMockFailureInjector.d.ts +60 -0
- package/dist/mock/LambderMockFailureInjector.js +138 -0
- package/dist/mock/LambderMockTypes.d.ts +421 -0
- package/dist/mock/LambderMockTypes.js +8 -0
- package/dist/mock/lambderMockConsoleLogger.d.ts +16 -0
- package/dist/mock/lambderMockConsoleLogger.js +35 -0
- package/dist/mock/lambderMockInvokeTransport.d.ts +50 -0
- package/dist/mock/lambderMockInvokeTransport.js +52 -0
- package/dist/mock/lambderMockMswHandler.d.ts +99 -0
- package/dist/mock/lambderMockMswHandler.js +126 -0
- package/dist/mock.d.ts +34 -0
- package/dist/mock.js +27 -0
- package/dist/session/LambderSessionController.d.ts +199 -30
- package/dist/session/LambderSessionController.js +396 -82
- package/dist/session/LambderSessionCrypto.d.ts +66 -0
- package/dist/session/LambderSessionCrypto.js +101 -0
- package/dist/session/LambderSessionManager.d.ts +118 -80
- package/dist/session/LambderSessionManager.js +212 -184
- package/dist/shared/LambderI18n.d.ts +6 -6
- package/dist/shared/LambderI18n.js +1 -1
- package/dist/shared/contracts/LambderFileSource.d.ts +33 -0
- package/dist/shared/contracts/LambderFileSource.js +19 -0
- package/dist/shared/contracts/LambderIdempotencyStore.d.ts +66 -0
- package/dist/shared/contracts/LambderIdempotencyStore.js +12 -0
- package/dist/shared/contracts/LambderRateLimiter.d.ts +71 -0
- package/dist/shared/contracts/LambderRateLimiter.js +24 -0
- package/dist/shared/contracts/LambderSessionStore.d.ts +72 -0
- package/dist/shared/contracts/LambderSessionStore.js +13 -0
- package/dist/shared/transport/LambderApiTransport.d.ts +139 -0
- package/dist/shared/transport/LambderApiTransport.js +65 -0
- package/dist/shared/transport/LambderCookieJar.d.ts +121 -0
- package/dist/shared/transport/LambderCookieJar.js +246 -0
- package/dist/shared/transport/lambderCookieJarTransport.d.ts +30 -0
- package/dist/shared/transport/lambderCookieJarTransport.js +60 -0
- package/dist/shared/util/LambderBase64.d.ts +10 -0
- package/dist/shared/util/LambderBase64.js +27 -0
- package/dist/shared/util/LambderCallAbort.d.ts +62 -0
- package/dist/shared/util/LambderCallAbort.js +80 -0
- package/dist/shared/util/LambderClientIp.d.ts +32 -0
- package/dist/shared/util/LambderClientIp.js +56 -0
- package/dist/shared/util/LambderExpiringMap.d.ts +119 -0
- package/dist/shared/util/LambderExpiringMap.js +217 -0
- package/dist/shared/util/LambderKeyFields.d.ts +32 -0
- package/dist/shared/util/LambderKeyFields.js +34 -0
- package/dist/shared/util/LambderNodeModules.d.ts +9 -0
- package/dist/shared/util/LambderNodeModules.js +39 -0
- package/dist/shared/util/LambderOptionChecks.d.ts +17 -0
- package/dist/shared/util/LambderOptionChecks.js +33 -0
- package/dist/shared/util/LambderResponseBrand.d.ts +20 -0
- package/dist/shared/util/LambderResponseBrand.js +18 -0
- package/dist/shared/util/LambderTextDigest.d.ts +17 -0
- package/dist/shared/util/LambderTextDigest.js +34 -0
- package/dist/shared/util/LambderTypeUtilities.d.ts +33 -0
- package/dist/shared/util/LambderTypeUtilities.js +8 -0
- package/dist/shared/wire/LambderAnswerHeaders.d.ts +60 -0
- package/dist/shared/wire/LambderAnswerHeaders.js +94 -0
- package/dist/shared/wire/LambderApiContract.d.ts +129 -0
- package/dist/shared/wire/LambderApiOptionValues.d.ts +39 -0
- package/dist/shared/wire/LambderApiOptionValues.js +11 -0
- package/dist/shared/wire/LambderApiOutcome.d.ts +128 -0
- package/dist/shared/{LambderApiOutcome.js → wire/LambderApiOutcome.js} +16 -9
- package/dist/shared/{LambderApiError.d.ts → wire/LambderApiRefusal.d.ts} +48 -26
- package/dist/shared/{LambderApiError.js → wire/LambderApiRefusal.js} +13 -11
- package/dist/shared/wire/LambderCallOptions.d.ts +171 -0
- package/dist/shared/wire/LambderCallOptions.js +17 -0
- package/dist/shared/{LambderCompressionCodec.d.ts → wire/LambderCompressionCodec.d.ts} +10 -6
- package/dist/shared/{LambderCompressionCodec.js → wire/LambderCompressionCodec.js} +67 -23
- package/dist/shared/{LambderCompressionOption.d.ts → wire/LambderCompressionOption.d.ts} +1 -1
- package/dist/shared/{LambderCompressionOption.js → wire/LambderCompressionOption.js} +3 -4
- package/dist/shared/{LambderCrashDetail.d.ts → wire/LambderCrashDetail.d.ts} +10 -0
- package/dist/shared/{LambderCrashDetail.js → wire/LambderCrashDetail.js} +30 -0
- package/dist/shared/wire/LambderHttpStatus.d.ts +12 -0
- package/dist/shared/wire/LambderHttpStatus.js +1 -0
- package/dist/shared/{LambderRequestPayload.d.ts → wire/LambderRequestPayload.d.ts} +25 -17
- package/dist/shared/{LambderRequestPayload.js → wire/LambderRequestPayload.js} +29 -52
- package/dist/shared/wire/LambderSessionCookieNames.d.ts +9 -0
- package/dist/shared/wire/LambderSessionCookieNames.js +9 -0
- package/dist/stores/LambderDdbCache.d.ts +12 -9
- package/dist/stores/LambderDdbCache.js +56 -47
- package/dist/stores/{LambderDdbIdempotency.d.ts → LambderDdbIdempotencyStore.d.ts} +41 -31
- package/dist/stores/LambderDdbIdempotencyStore.js +319 -0
- package/dist/stores/LambderDdbRateLimiter.d.ts +30 -49
- package/dist/stores/LambderDdbRateLimiter.js +47 -45
- package/dist/stores/LambderDdbSdk.d.ts +83 -6
- package/dist/stores/LambderDdbSdk.js +83 -2
- package/dist/stores/LambderDdbSessionStore.d.ts +65 -0
- package/dist/stores/LambderDdbSessionStore.js +161 -0
- package/dist/stores/LambderHttpFileSource.d.ts +1 -1
- package/dist/stores/LambderHttpFileSource.js +10 -1
- package/dist/stores/LambderLocalFileSource.d.ts +15 -0
- package/dist/stores/LambderLocalFileSource.js +28 -0
- package/dist/stores/LambderMemoryIdempotencyStore.d.ts +63 -0
- package/dist/stores/LambderMemoryIdempotencyStore.js +113 -0
- package/dist/stores/LambderMemoryRateLimiter.d.ts +34 -0
- package/dist/stores/LambderMemoryRateLimiter.js +64 -0
- package/dist/stores/LambderMemorySessionStore.d.ts +48 -0
- package/dist/stores/LambderMemorySessionStore.js +74 -0
- package/dist/stores/LambderS3FileSource.d.ts +1 -1
- package/dist/stores/LambderS3FileSource.js +1 -1
- package/package.json +21 -19
- package/dist/client/LambderMSW.d.ts +0 -69
- package/dist/client/LambderMSW.js +0 -121
- package/dist/policies/LambderApiGuards.d.ts +0 -256
- package/dist/policies/LambderApiGuards.js +0 -94
- package/dist/policies/LambderApiIdempotency.d.ts +0 -58
- package/dist/policies/LambderApiIdempotency.js +0 -219
- package/dist/policies/LambderApiPolicies.d.ts +0 -42
- package/dist/policies/LambderApiPolicies.js +0 -52
- package/dist/policies/LambderApiRateLimits.d.ts +0 -132
- package/dist/policies/LambderApiRateLimits.js +0 -119
- package/dist/shared/LambderApiContract.d.ts +0 -57
- package/dist/shared/LambderApiOutcome.d.ts +0 -69
- package/dist/shared/LambderCallOptions.d.ts +0 -71
- package/dist/shared/LambderCallOptions.js +0 -16
- package/dist/shared/node-polyfills.d.ts +0 -4
- package/dist/shared/node-polyfills.js +0 -58
- package/dist/stores/LambderDdbIdempotency.js +0 -229
- package/dist/testing.d.ts +0 -9
- package/dist/testing.js +0 -8
- /package/dist/shared/{LambderApiContract.js → wire/LambderApiContract.js} +0 -0
- /package/dist/{core → shared/wire}/LambderCookie.d.ts +0 -0
- /package/dist/{core → shared/wire}/LambderCookie.js +0 -0
|
@@ -76,3 +76,33 @@ export const errorFromCrashDetail = (crash) => {
|
|
|
76
76
|
cause = build(entry, cause);
|
|
77
77
|
return build(crash, cause);
|
|
78
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* A thrown value as an Error, so a reporter or a `cause` chain always holds
|
|
81
|
+
* one. An Error passes through; anything else becomes an Error whose message
|
|
82
|
+
* describes the value the way describeCrash does, JSON where String() cannot
|
|
83
|
+
* (a null-prototype object or a throwing toString must not make the
|
|
84
|
+
* coercion itself throw). One implementation, rather than
|
|
85
|
+
* `err instanceof Error ? err : new Error(...)` spelled at every site with a
|
|
86
|
+
* fallback of its own.
|
|
87
|
+
*/
|
|
88
|
+
export const coerceToError = (value, fallbackMessage = "Unknown error") => {
|
|
89
|
+
if (value instanceof Error)
|
|
90
|
+
return value;
|
|
91
|
+
let message;
|
|
92
|
+
if (typeof value === "string")
|
|
93
|
+
message = value;
|
|
94
|
+
else {
|
|
95
|
+
try {
|
|
96
|
+
message = JSON.stringify(value);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
try {
|
|
100
|
+
message = String(value);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
message = undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return new Error(message || fallbackMessage);
|
|
108
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP status codes a Lambder response may carry.
|
|
3
|
+
*
|
|
4
|
+
* In `shared/` rather than beside LambderResponse because it is HTTP
|
|
5
|
+
* vocabulary with no relationship to the server's response class, and the
|
|
6
|
+
* places that need it include ones a browser bundle reaches
|
|
7
|
+
* (LambderApiRefusal, the mock's contract types). Importing it from `core/`
|
|
8
|
+
* pulled `core/LambderResponse.ts` into the type graph of `lambder/client`,
|
|
9
|
+
* and with it `aws-lambda`, so a browser-only consumer needed
|
|
10
|
+
* `@types/aws-lambda` resolvable to typecheck a status union.
|
|
11
|
+
*/
|
|
12
|
+
export type LambderHttpStatusCode = 100 | 101 | 200 | 201 | 202 | 203 | 204 | 206 | 300 | 301 | 302 | 303 | 304 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 408 | 409 | 410 | 412 | 413 | 415 | 416 | 418 | 422 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* LambderCompressionCodec gives stored records, so a malicious or truncated
|
|
26
26
|
* body fails instead of expanding without limit.
|
|
27
27
|
*/
|
|
28
|
-
import type { LambderCompressionOption } from "./LambderCompressionOption.js";
|
|
28
|
+
import type { LambderCompressionOption, LambderCompressionSettings } from "./LambderCompressionOption.js";
|
|
29
29
|
/** Envelope field carrying the base64 gzip of the payload's JSON. */
|
|
30
30
|
export declare const COMPRESSED_PAYLOAD_GZ_FIELD = "payloadGz";
|
|
31
31
|
/**
|
|
@@ -71,18 +71,18 @@ export declare const DEFAULT_REQUEST_COMPRESSION_SETTINGS: LambderRequestCompres
|
|
|
71
71
|
*/
|
|
72
72
|
export declare const DEFAULT_MAX_RESTORED_PAYLOAD_BYTES = 20000000;
|
|
73
73
|
/**
|
|
74
|
-
* The
|
|
75
|
-
* the
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
74
|
+
* The threshold one call is judged against, or null when its payload goes
|
|
75
|
+
* plainly: the caller's configured setting unless the call overrode it, and
|
|
76
|
+
* `compressRequest: true` means "whatever the size", which is a threshold of
|
|
77
|
+
* zero rather than a separate path.
|
|
78
|
+
*
|
|
79
|
+
* Both callers decide this, and the three-line ternary they each wrote is the
|
|
80
|
+
* one place a caller can get the override backwards, so it is written once
|
|
81
|
+
* beside the compressors it feeds.
|
|
82
82
|
*/
|
|
83
|
-
export declare const
|
|
84
|
-
|
|
85
|
-
}) | null
|
|
83
|
+
export declare const resolveRequestCompressionMinBytes: (compressRequest: boolean | undefined, settings: {
|
|
84
|
+
minBytes: number;
|
|
85
|
+
} | null | undefined) => number | null;
|
|
86
86
|
/** True when this runtime can compress request payloads (browsers, and Node 18+). */
|
|
87
87
|
export declare const isRequestCompressionAvailable: () => boolean;
|
|
88
88
|
/**
|
|
@@ -94,10 +94,18 @@ export declare const isRequestCompressionAvailable: () => boolean;
|
|
|
94
94
|
* bigger, so the compressed form is only ever sent when it is smaller.
|
|
95
95
|
*/
|
|
96
96
|
export declare const compressPayloadGzip: (json: string, minBytes: number) => Promise<LambderCompressedGzipPayload | null>;
|
|
97
|
+
/** Request Brotli when `requestCompression: true` on LambderInvokeCaller: the HTTP request threshold, at the quality every other Lambder site uses. */
|
|
98
|
+
export declare const DEFAULT_INVOKE_REQUEST_COMPRESSION_SETTINGS: LambderCompressionSettings;
|
|
97
99
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
100
|
+
* Brotli one payload's JSON for sending, or null when the plain JSON should
|
|
101
|
+
* go instead: compressPayloadGzip with Brotli, for a caller where both ends
|
|
102
|
+
* are Node (LambderInvokeCaller). The threshold and the only-when-smaller
|
|
103
|
+
* rule are compressPayloadWith's, shared with the gzip side.
|
|
104
|
+
*
|
|
105
|
+
* Beside its gzip twin rather than inside the invoke caller, because the two
|
|
106
|
+
* are one wire format with one set of rules and a reader comparing them
|
|
107
|
+
* should not have to open two files. It needs Buffer and zlib, which the
|
|
108
|
+
* codec loads lazily, so this module stays resolvable from the browser entry
|
|
109
|
+
* and a bundle that never calls this drops it with the codec behind it.
|
|
102
110
|
*/
|
|
103
|
-
export declare const
|
|
111
|
+
export declare const compressPayloadBrotli: (json: string, minBytes: number, quality: number) => Promise<LambderCompressedBrotliPayload | null>;
|
|
@@ -1,30 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* When a LambderCaller call's payload clears the configured size, the caller
|
|
5
|
-
* sends the payload's JSON as `payloadGz` (gzip bytes, base64) beside
|
|
6
|
-
* `payloadBytes` (its UTF-8 byte length) in place of `payload`, and the
|
|
7
|
-
* server restores it before anything reads the payload. A Node caller
|
|
8
|
-
* (LambderInvokeCaller) sends `payloadBr` instead, Brotli under the same
|
|
9
|
-
* rules; the server accepts either. Everything else in the envelope
|
|
10
|
-
* (apiName, version, token, siteHost, guardInputs, idempotencyKey) stays
|
|
11
|
-
* plain text, so routing, logging and request mocking are unaffected.
|
|
12
|
-
*
|
|
13
|
-
* Base64 inside the JSON envelope, rather than a binary body with
|
|
14
|
-
* Content-Encoding: API Gateway hands a binary request body to Lambda
|
|
15
|
-
* base64-encoded anyway, so binary saves nothing against Lambda's ~6MB
|
|
16
|
-
* invoke payload cap while adding a content-type negotiation that gateways,
|
|
17
|
-
* CDNs and mock servers each treat differently. Base64's 4/3 overhead
|
|
18
|
-
* applies to bytes that already shrank several times over.
|
|
19
|
-
*
|
|
20
|
-
* gzip rather than Brotli because the browser's CompressionStream offers
|
|
21
|
-
* gzip and deflate only; responses, compressed by Node, do prefer Brotli.
|
|
22
|
-
*
|
|
23
|
-
* `payloadBytes` is not bookkeeping: it bounds the server's decompression
|
|
24
|
-
* and the restored length must match it exactly, the same guarantee
|
|
25
|
-
* LambderCompressionCodec gives stored records, so a malicious or truncated
|
|
26
|
-
* body fails instead of expanding without limit.
|
|
27
|
-
*/
|
|
1
|
+
import { bytesToBase64 } from "../util/LambderBase64.js";
|
|
2
|
+
import { compressText } from "./LambderCompressionCodec.js";
|
|
28
3
|
/** Envelope field carrying the base64 gzip of the payload's JSON. */
|
|
29
4
|
export const COMPRESSED_PAYLOAD_GZ_FIELD = "payloadGz";
|
|
30
5
|
/**
|
|
@@ -51,17 +26,6 @@ export const DEFAULT_REQUEST_COMPRESSION_SETTINGS = { minBytes: 4096 };
|
|
|
51
26
|
* compressible body cannot exhaust the function's memory.
|
|
52
27
|
*/
|
|
53
28
|
export const DEFAULT_MAX_RESTORED_PAYLOAD_BYTES = 20_000_000;
|
|
54
|
-
/** Buffer where there is one (Node); otherwise chunked so a large payload cannot overflow the argument list of String.fromCharCode. */
|
|
55
|
-
const bytesToBase64 = (bytes) => {
|
|
56
|
-
if (typeof Buffer !== "undefined")
|
|
57
|
-
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
|
|
58
|
-
const chunkSize = 0x8000;
|
|
59
|
-
let binary = "";
|
|
60
|
-
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
61
|
-
binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
|
|
62
|
-
}
|
|
63
|
-
return btoa(binary);
|
|
64
|
-
};
|
|
65
29
|
/**
|
|
66
30
|
* The two rules every compressed payload follows, whichever algorithm made
|
|
67
31
|
* the bytes: nothing below the threshold is compressed, and the compressed
|
|
@@ -72,7 +36,7 @@ const bytesToBase64 = (bytes) => {
|
|
|
72
36
|
* gzip, zlib for Brotli (LambderInvokeCaller); both go through here so the
|
|
73
37
|
* rules cannot drift between them.
|
|
74
38
|
*/
|
|
75
|
-
|
|
39
|
+
const compressPayloadWith = async (json, minBytes, field, compress) => {
|
|
76
40
|
const encoded = new TextEncoder().encode(json);
|
|
77
41
|
if (encoded.length < minBytes)
|
|
78
42
|
return null;
|
|
@@ -81,6 +45,19 @@ export const compressPayloadWith = async (json, minBytes, field, compress) => {
|
|
|
81
45
|
return null;
|
|
82
46
|
return { [field]: base64, [COMPRESSED_PAYLOAD_BYTES_FIELD]: encoded.length };
|
|
83
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* The threshold one call is judged against, or null when its payload goes
|
|
50
|
+
* plainly: the caller's configured setting unless the call overrode it, and
|
|
51
|
+
* `compressRequest: true` means "whatever the size", which is a threshold of
|
|
52
|
+
* zero rather than a separate path.
|
|
53
|
+
*
|
|
54
|
+
* Both callers decide this, and the three-line ternary they each wrote is the
|
|
55
|
+
* one place a caller can get the override backwards, so it is written once
|
|
56
|
+
* beside the compressors it feeds.
|
|
57
|
+
*/
|
|
58
|
+
export const resolveRequestCompressionMinBytes = (compressRequest, settings) => compressRequest === true ? 0
|
|
59
|
+
: compressRequest === false ? null
|
|
60
|
+
: settings?.minBytes ?? null;
|
|
84
61
|
/** True when this runtime can compress request payloads (browsers, and Node 18+). */
|
|
85
62
|
export const isRequestCompressionAvailable = () => typeof CompressionStream !== "undefined" && typeof btoa !== "undefined";
|
|
86
63
|
/**
|
|
@@ -95,18 +72,18 @@ export const compressPayloadGzip = (json, minBytes) => compressPayloadWith(json,
|
|
|
95
72
|
const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
96
73
|
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
97
74
|
});
|
|
75
|
+
/** Request Brotli when `requestCompression: true` on LambderInvokeCaller: the HTTP request threshold, at the quality every other Lambder site uses. */
|
|
76
|
+
export const DEFAULT_INVOKE_REQUEST_COMPRESSION_SETTINGS = { minBytes: 4096, quality: 5 };
|
|
98
77
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
78
|
+
* Brotli one payload's JSON for sending, or null when the plain JSON should
|
|
79
|
+
* go instead: compressPayloadGzip with Brotli, for a caller where both ends
|
|
80
|
+
* are Node (LambderInvokeCaller). The threshold and the only-when-smaller
|
|
81
|
+
* rule are compressPayloadWith's, shared with the gzip side.
|
|
82
|
+
*
|
|
83
|
+
* Beside its gzip twin rather than inside the invoke caller, because the two
|
|
84
|
+
* are one wire format with one set of rules and a reader comparing them
|
|
85
|
+
* should not have to open two files. It needs Buffer and zlib, which the
|
|
86
|
+
* codec loads lazily, so this module stays resolvable from the browser entry
|
|
87
|
+
* and a bundle that never calls this drops it with the codec behind it.
|
|
103
88
|
*/
|
|
104
|
-
export const
|
|
105
|
-
const binary = atob(payloadGz);
|
|
106
|
-
const bytes = new Uint8Array(binary.length);
|
|
107
|
-
for (let i = 0; i < binary.length; i += 1) {
|
|
108
|
-
bytes[i] = binary.charCodeAt(i);
|
|
109
|
-
}
|
|
110
|
-
const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
|
|
111
|
-
return JSON.parse(await new Response(stream).text());
|
|
112
|
-
};
|
|
89
|
+
export const compressPayloadBrotli = (json, minBytes, quality) => compressPayloadWith(json, minBytes, COMPRESSED_PAYLOAD_BR_FIELD, (bytes) => compressText(Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength), "br", quality));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session cookie names an app uses unless it configures its own. One
|
|
3
|
+
* definition, in shared, because the server writes these names and the
|
|
4
|
+
* browser caller, the invoke caller and the mock runtime all read them: the
|
|
5
|
+
* two sides must agree byte for byte, and a second copy is a place for them
|
|
6
|
+
* to drift apart with every test still green.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_SESSION_TOKEN_COOKIE_KEY = "LMDRSESSIONTKID";
|
|
9
|
+
export declare const DEFAULT_SESSION_CSRF_COOKIE_KEY = "LMDRSESSIONCSTK";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session cookie names an app uses unless it configures its own. One
|
|
3
|
+
* definition, in shared, because the server writes these names and the
|
|
4
|
+
* browser caller, the invoke caller and the mock runtime all read them: the
|
|
5
|
+
* two sides must agree byte for byte, and a second copy is a place for them
|
|
6
|
+
* to drift apart with every test still green.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_SESSION_TOKEN_COOKIE_KEY = "LMDRSESSIONTKID";
|
|
9
|
+
export const DEFAULT_SESSION_CSRF_COOKIE_KEY = "LMDRSESSIONCSTK";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
2
|
-
import { type LambderCompressionOption } from "../shared/LambderCompressionOption.js";
|
|
2
|
+
import { type LambderCompressionOption } from "../shared/wire/LambderCompressionOption.js";
|
|
3
3
|
export interface LambderDdbCacheOptions {
|
|
4
4
|
tableName: string;
|
|
5
|
+
/** Region the client is created for on first use; the SDK's default chain otherwise. */
|
|
5
6
|
region?: string;
|
|
6
7
|
/** Partition key prefix, keeps cache items separated from other systems in a shared table. Default: "CACHE". */
|
|
7
8
|
keyPrefix?: string;
|
|
@@ -18,6 +19,12 @@ export interface LambderDdbCacheOptions {
|
|
|
18
19
|
maxValueBytes?: number;
|
|
19
20
|
memoryMaxBytes?: number;
|
|
20
21
|
client?: DynamoDBClient;
|
|
22
|
+
/**
|
|
23
|
+
* The clock entries are expired against, injectable the way the rate
|
|
24
|
+
* limiter's and the idempotency store's are, so a test can cross a TTL
|
|
25
|
+
* boundary without moving the world's clock.
|
|
26
|
+
*/
|
|
27
|
+
now?: () => number;
|
|
21
28
|
}
|
|
22
29
|
/**
|
|
23
30
|
* Where a value lives. A plain string addresses one entry, as it always has.
|
|
@@ -59,7 +66,7 @@ export interface LambderDdbCacheListOptions {
|
|
|
59
66
|
* Table shape: string hash key `pk`, string range key `sk`, TTL on
|
|
60
67
|
* `expiresAt`. Items are prefixed `CACHE#<namespace>#` by default, so the
|
|
61
68
|
* table can be shared with LambderDdbRateLimiter (`RL#`) and
|
|
62
|
-
*
|
|
69
|
+
* LambderDdbIdempotencyStore (`IDEM#`) without key collisions.
|
|
63
70
|
*
|
|
64
71
|
* A key may also be a `{ pk, sk }` pair, which groups entries under one
|
|
65
72
|
* partition so `deletePartition` and `listSortKeys` can work on the group
|
|
@@ -72,19 +79,16 @@ export declare class LambderDdbCache {
|
|
|
72
79
|
readonly tableName: string;
|
|
73
80
|
readonly keyPrefix: string;
|
|
74
81
|
readonly namespace: string;
|
|
75
|
-
/** The
|
|
76
|
-
private readonly
|
|
77
|
-
private readonly region;
|
|
78
|
-
private readyPromise;
|
|
82
|
+
/** The SDK and the client, loaded and created the first time the table is touched (see LambderDdbSdk). */
|
|
83
|
+
private readonly ready;
|
|
79
84
|
private readonly defaultTtlSeconds;
|
|
80
85
|
private readonly chunkBytes;
|
|
81
86
|
private readonly compression;
|
|
82
87
|
private readonly maxValueBytes;
|
|
83
88
|
private readonly memory;
|
|
84
89
|
private readonly inFlight;
|
|
90
|
+
private readonly now;
|
|
85
91
|
constructor(options: LambderDdbCacheOptions);
|
|
86
|
-
/** The SDK and the client, loaded and created the first time the table is touched (see LambderDdbSdk). */
|
|
87
|
-
private ready;
|
|
88
92
|
get<T>(key: LambderCacheKey): Promise<T | undefined>;
|
|
89
93
|
private getByAddress;
|
|
90
94
|
has(key: LambderCacheKey): Promise<boolean>;
|
|
@@ -145,5 +149,4 @@ export declare class LambderDdbCache {
|
|
|
145
149
|
/** Drop every in-memory copy belonging to one partition. */
|
|
146
150
|
private forgetPartition;
|
|
147
151
|
private nowSeconds;
|
|
148
|
-
private isConditionalFailure;
|
|
149
152
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getCrypto } from "../shared/
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { createDynamoClientLoader, isConditionalCheckFailure } from "./LambderDdbSdk.js";
|
|
2
|
+
import { getCrypto } from "../shared/util/LambderNodeModules.js";
|
|
3
|
+
import { assertPositiveInteger } from "../shared/util/LambderOptionChecks.js";
|
|
4
|
+
import { compressText, restoreText, LambderCompressionError } from "../shared/wire/LambderCompressionCodec.js";
|
|
5
|
+
import { resolveCompressionOption, } from "../shared/wire/LambderCompressionOption.js";
|
|
5
6
|
import { LRUCache } from "lru-cache";
|
|
6
7
|
const DEFAULT_TTL_SECONDS = 365 * 24 * 60 * 60;
|
|
7
8
|
const DEFAULT_CHUNK_BYTES = 350 * 1024;
|
|
@@ -19,6 +20,25 @@ const BATCH_WRITE_LIMIT = 25;
|
|
|
19
20
|
const MAX_BATCH_RETRIES = 8;
|
|
20
21
|
/** Every value compressed by default; see the `compression` option. */
|
|
21
22
|
const COMPRESSION_DEFAULTS = { minBytes: 0, quality: 5 };
|
|
23
|
+
/**
|
|
24
|
+
* A stored entry that cannot be trusted: chunks that do not add up to what
|
|
25
|
+
* the manifest describes, bytes that fail its checksum, or a restore the
|
|
26
|
+
* codec would not vouch for (a LambderCompressionError, which is the same
|
|
27
|
+
* answer in the compression layer's own words).
|
|
28
|
+
*
|
|
29
|
+
* A type rather than "anything thrown while reading the entry", because the
|
|
30
|
+
* two are treated in opposite ways: a corrupt entry is dropped so the next
|
|
31
|
+
* reader refills it, while a throttled or failed Query is the table being
|
|
32
|
+
* busy. Deleting a healthy 2MB entry over one throttle orphans its chunks
|
|
33
|
+
* until their TTL and sends every later reader to the origin, which is the
|
|
34
|
+
* load the cache exists to absorb.
|
|
35
|
+
*/
|
|
36
|
+
class LambderCacheIntegrityError extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "LambderCacheIntegrityError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
22
42
|
/**
|
|
23
43
|
* `#` separates the store's own item-key segments, so a caller's `#` is
|
|
24
44
|
* escaped rather than refused: `~` becomes `~0` and `#` becomes `~1`. An
|
|
@@ -30,11 +50,11 @@ const COMPRESSION_DEFAULTS = { minBytes: 0, quality: 5 };
|
|
|
30
50
|
*/
|
|
31
51
|
const encodeSortKey = (value) => value.replace(/~/g, "~0").replace(/#/g, "~1");
|
|
32
52
|
const decodeSortKey = (value) => value.replace(/~([01])/g, (_match, code) => code === "0" ? "~" : "#");
|
|
33
|
-
// Node builtins are loaded lazily through
|
|
53
|
+
// Node builtins are loaded lazily through LambderNodeModules so this module can
|
|
34
54
|
// sit in a frontend bundle's import graph (via the package root) without
|
|
35
55
|
// breaking; using the cache at runtime still requires Node. Brotli helpers
|
|
36
|
-
// are shared with
|
|
37
|
-
// ../shared/LambderCompressionCodec.js.
|
|
56
|
+
// are shared with LambderDdbIdempotencyStore and LambderDdbSessionStore via
|
|
57
|
+
// ../shared/wire/LambderCompressionCodec.js.
|
|
38
58
|
const requireCrypto = async () => {
|
|
39
59
|
const crypto = await getCrypto();
|
|
40
60
|
if (!crypto)
|
|
@@ -49,12 +69,6 @@ const randomUUID = async () => {
|
|
|
49
69
|
const crypto = await requireCrypto();
|
|
50
70
|
return crypto.randomUUID();
|
|
51
71
|
};
|
|
52
|
-
const positiveInteger = (value, name) => {
|
|
53
|
-
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
54
|
-
throw new Error(`${name} must be a positive safe integer`);
|
|
55
|
-
}
|
|
56
|
-
return value;
|
|
57
|
-
};
|
|
58
72
|
const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
59
73
|
/**
|
|
60
74
|
* Persistent JSON cache backed by DynamoDB.
|
|
@@ -71,7 +85,7 @@ const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, mil
|
|
|
71
85
|
* Table shape: string hash key `pk`, string range key `sk`, TTL on
|
|
72
86
|
* `expiresAt`. Items are prefixed `CACHE#<namespace>#` by default, so the
|
|
73
87
|
* table can be shared with LambderDdbRateLimiter (`RL#`) and
|
|
74
|
-
*
|
|
88
|
+
* LambderDdbIdempotencyStore (`IDEM#`) without key collisions.
|
|
75
89
|
*
|
|
76
90
|
* A key may also be a `{ pk, sk }` pair, which groups entries under one
|
|
77
91
|
* partition so `deletePartition` and `listSortKeys` can work on the group
|
|
@@ -84,16 +98,15 @@ export class LambderDdbCache {
|
|
|
84
98
|
tableName;
|
|
85
99
|
keyPrefix;
|
|
86
100
|
namespace;
|
|
87
|
-
/** The
|
|
88
|
-
|
|
89
|
-
region;
|
|
90
|
-
readyPromise;
|
|
101
|
+
/** The SDK and the client, loaded and created the first time the table is touched (see LambderDdbSdk). */
|
|
102
|
+
ready;
|
|
91
103
|
defaultTtlSeconds;
|
|
92
104
|
chunkBytes;
|
|
93
105
|
compression;
|
|
94
106
|
maxValueBytes;
|
|
95
107
|
memory;
|
|
96
108
|
inFlight = new Map();
|
|
109
|
+
now;
|
|
97
110
|
constructor(options) {
|
|
98
111
|
if (!options.tableName.trim())
|
|
99
112
|
throw new Error("tableName is required");
|
|
@@ -103,29 +116,22 @@ export class LambderDdbCache {
|
|
|
103
116
|
if (Buffer.byteLength(this.namespace, "utf8") > 128) {
|
|
104
117
|
throw new Error("namespace must be at most 128 UTF-8 bytes");
|
|
105
118
|
}
|
|
106
|
-
this.defaultTtlSeconds =
|
|
107
|
-
this.chunkBytes =
|
|
119
|
+
this.defaultTtlSeconds = assertPositiveInteger(options.defaultTtlSeconds ?? DEFAULT_TTL_SECONDS, "defaultTtlSeconds");
|
|
120
|
+
this.chunkBytes = assertPositiveInteger(options.chunkBytes ?? DEFAULT_CHUNK_BYTES, "chunkBytes");
|
|
108
121
|
if (this.chunkBytes > MAX_SAFE_CHUNK_BYTES) {
|
|
109
122
|
throw new Error(`chunkBytes must not exceed ${MAX_SAFE_CHUNK_BYTES}`);
|
|
110
123
|
}
|
|
111
124
|
this.compression = resolveCompressionOption(options.compression, COMPRESSION_DEFAULTS);
|
|
112
|
-
this.maxValueBytes =
|
|
125
|
+
this.maxValueBytes = assertPositiveInteger(options.maxValueBytes ?? DEFAULT_MAX_VALUE_BYTES, "maxValueBytes");
|
|
113
126
|
const memoryMaxBytes = options.memoryMaxBytes ?? DEFAULT_MEMORY_BYTES;
|
|
114
127
|
this.memory = memoryMaxBytes === 0
|
|
115
128
|
? null
|
|
116
129
|
: new LRUCache({
|
|
117
|
-
maxSize:
|
|
130
|
+
maxSize: assertPositiveInteger(memoryMaxBytes, "memoryMaxBytes"),
|
|
118
131
|
sizeCalculation: (entry) => entry.stored.length,
|
|
119
132
|
});
|
|
120
|
-
this.
|
|
121
|
-
this.
|
|
122
|
-
}
|
|
123
|
-
/** The SDK and the client, loaded and created the first time the table is touched (see LambderDdbSdk). */
|
|
124
|
-
ready() {
|
|
125
|
-
this.readyPromise ??= loadDynamoClientSdk("LambderDdbCache")
|
|
126
|
-
.then((sdk) => ({ sdk, client: this.providedClient ?? new sdk.DynamoDBClient({ region: this.region }) }))
|
|
127
|
-
.catch((error) => { this.readyPromise = undefined; throw error; });
|
|
128
|
-
return this.readyPromise;
|
|
133
|
+
this.now = options.now ?? (() => Date.now());
|
|
134
|
+
this.ready = createDynamoClientLoader({ user: "LambderDdbCache", region: options.region, client: options.client });
|
|
129
135
|
}
|
|
130
136
|
async get(key) {
|
|
131
137
|
return await this.getByAddress(this.normalizeKey(key));
|
|
@@ -150,10 +156,10 @@ export class LambderDdbCache {
|
|
|
150
156
|
try {
|
|
151
157
|
const stored = manifest.inlineData ?? await this.readChunks(pk, address, manifest);
|
|
152
158
|
if (stored.length !== manifest.storedBytes) {
|
|
153
|
-
throw new
|
|
159
|
+
throw new LambderCacheIntegrityError("stored byte length does not match manifest");
|
|
154
160
|
}
|
|
155
161
|
if (await sha256(stored) !== manifest.checksum) {
|
|
156
|
-
throw new
|
|
162
|
+
throw new LambderCacheIntegrityError("stored checksum does not match manifest");
|
|
157
163
|
}
|
|
158
164
|
const json = await this.decode(stored, manifest.encoding, manifest.uncompressedBytes);
|
|
159
165
|
const parsed = JSON.parse(json);
|
|
@@ -161,6 +167,12 @@ export class LambderDdbCache {
|
|
|
161
167
|
return parsed;
|
|
162
168
|
}
|
|
163
169
|
catch (error) {
|
|
170
|
+
// Only an entry this read can prove wrong is dropped. Everything
|
|
171
|
+
// else is the table answering badly, which is the manifest read's
|
|
172
|
+
// own behaviour one line up: it propagates, and getOrSet's
|
|
173
|
+
// fail-open handles it as the infrastructure failure it is.
|
|
174
|
+
if (!(error instanceof LambderCacheIntegrityError || error instanceof LambderCompressionError))
|
|
175
|
+
throw error;
|
|
164
176
|
await this.invalidateManifest(pk, address, manifest.version);
|
|
165
177
|
console.warn(`Ignoring corrupt DynamoDB cache entry in ${this.namespace}`, error);
|
|
166
178
|
return undefined;
|
|
@@ -181,7 +193,7 @@ export class LambderDdbCache {
|
|
|
181
193
|
return await this.setByAddress(this.normalizeKey(key), value, options);
|
|
182
194
|
}
|
|
183
195
|
async setByAddress(address, value, options) {
|
|
184
|
-
const ttlSeconds =
|
|
196
|
+
const ttlSeconds = assertPositiveInteger(options.ttlSeconds ?? this.defaultTtlSeconds, "ttlSeconds");
|
|
185
197
|
const json = JSON.stringify(value);
|
|
186
198
|
if (json === undefined)
|
|
187
199
|
throw new Error("Cache value must be JSON-serializable");
|
|
@@ -272,7 +284,7 @@ export class LambderDdbCache {
|
|
|
272
284
|
async listSortKeys(partition, options = {}) {
|
|
273
285
|
const pk = await this.partitionKey(this.normalizePartition(partition));
|
|
274
286
|
const prefix = `${SORT_KEY_MARKER}${encodeSortKey(options.prefix ?? "")}`;
|
|
275
|
-
const limit = options.limit === undefined ? undefined :
|
|
287
|
+
const limit = options.limit === undefined ? undefined : assertPositiveInteger(options.limit, "limit");
|
|
276
288
|
const nowSeconds = this.nowSeconds();
|
|
277
289
|
const items = await this.queryItems(pk, { prefix, projection: "#sk, #expiresAt", extraNames: { "#expiresAt": "expiresAt" } });
|
|
278
290
|
const sortKeys = [];
|
|
@@ -330,8 +342,8 @@ export class LambderDdbCache {
|
|
|
330
342
|
}
|
|
331
343
|
}
|
|
332
344
|
async fill(address, factory, options) {
|
|
333
|
-
const leaseSeconds =
|
|
334
|
-
const waitForFillMs =
|
|
345
|
+
const leaseSeconds = assertPositiveInteger(options.leaseSeconds ?? 15, "leaseSeconds");
|
|
346
|
+
const waitForFillMs = assertPositiveInteger(options.waitForFillMs ?? 5_000, "waitForFillMs");
|
|
335
347
|
const pk = await this.partitionKey(address.partition);
|
|
336
348
|
const owner = await randomUUID();
|
|
337
349
|
if (await this.acquireLease(pk, address, owner, leaseSeconds)) {
|
|
@@ -384,7 +396,7 @@ export class LambderDdbCache {
|
|
|
384
396
|
return true;
|
|
385
397
|
}
|
|
386
398
|
catch (error) {
|
|
387
|
-
if (
|
|
399
|
+
if (isConditionalCheckFailure(error))
|
|
388
400
|
return false;
|
|
389
401
|
throw error;
|
|
390
402
|
}
|
|
@@ -401,7 +413,7 @@ export class LambderDdbCache {
|
|
|
401
413
|
}));
|
|
402
414
|
}
|
|
403
415
|
catch (error) {
|
|
404
|
-
if (!
|
|
416
|
+
if (!isConditionalCheckFailure(error)) {
|
|
405
417
|
console.warn(`Failed to release DynamoDB cache lease in ${this.namespace}`, error);
|
|
406
418
|
}
|
|
407
419
|
}
|
|
@@ -470,11 +482,11 @@ export class LambderDdbCache {
|
|
|
470
482
|
.map((item) => ({ sk: item.sk.S, data: Buffer.from(item.data.B) }));
|
|
471
483
|
chunks.sort((left, right) => left.sk.localeCompare(right.sk));
|
|
472
484
|
if (chunks.length !== manifest.chunkCount) {
|
|
473
|
-
throw new
|
|
485
|
+
throw new LambderCacheIntegrityError(`DynamoDB cache entry is missing chunks (${chunks.length}/${manifest.chunkCount})`);
|
|
474
486
|
}
|
|
475
487
|
for (let index = 0; index < chunks.length; index += 1) {
|
|
476
488
|
if (chunks[index]?.sk !== this.chunkSortKey(address, manifest.version, index)) {
|
|
477
|
-
throw new
|
|
489
|
+
throw new LambderCacheIntegrityError(`DynamoDB cache entry has an invalid chunk index at ${index}`);
|
|
478
490
|
}
|
|
479
491
|
}
|
|
480
492
|
return Buffer.concat(chunks.map((chunk) => chunk.data), manifest.storedBytes);
|
|
@@ -520,7 +532,7 @@ export class LambderDdbCache {
|
|
|
520
532
|
}));
|
|
521
533
|
}
|
|
522
534
|
catch (error) {
|
|
523
|
-
if (!
|
|
535
|
+
if (!isConditionalCheckFailure(error)) {
|
|
524
536
|
console.warn(`Failed to invalidate corrupt DynamoDB cache manifest in ${this.namespace}`, error);
|
|
525
537
|
}
|
|
526
538
|
}
|
|
@@ -549,7 +561,7 @@ export class LambderDdbCache {
|
|
|
549
561
|
remember(key, stored, encoding, uncompressedBytes, expiresAt) {
|
|
550
562
|
if (!this.memory)
|
|
551
563
|
return;
|
|
552
|
-
const ttl = expiresAt * 1000 -
|
|
564
|
+
const ttl = expiresAt * 1000 - this.now();
|
|
553
565
|
if (ttl <= 0)
|
|
554
566
|
return;
|
|
555
567
|
this.memory.set(key, { stored, encoding, uncompressedBytes, expiresAt }, { ttl });
|
|
@@ -616,9 +628,6 @@ export class LambderDdbCache {
|
|
|
616
628
|
}
|
|
617
629
|
}
|
|
618
630
|
nowSeconds() {
|
|
619
|
-
return Math.floor(
|
|
620
|
-
}
|
|
621
|
-
isConditionalFailure(error) {
|
|
622
|
-
return !!error && typeof error === "object" && "name" in error && error.name === "ConditionalCheckFailedException";
|
|
631
|
+
return Math.floor(this.now() / 1000);
|
|
623
632
|
}
|
|
624
633
|
}
|