lambder 7.2.5 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1021 -3
- package/README.md +43 -21
- package/dist/api/LambderApiAnswer.d.ts +18 -22
- package/dist/api/LambderApiAnswer.js +6 -7
- package/dist/api/LambderApiCallContext.d.ts +21 -8
- package/dist/api/LambderApiCallContext.js +22 -4
- package/dist/api/LambderApiDefinition.d.ts +4 -3
- package/dist/api/LambderApiEnvelope.d.ts +14 -9
- package/dist/api/LambderApiEnvelope.js +33 -34
- package/dist/api/LambderApiGuards.d.ts +78 -51
- package/dist/api/LambderApiGuards.js +34 -36
- package/dist/api/LambderApiIdempotency.d.ts +74 -61
- package/dist/api/LambderApiIdempotency.js +226 -151
- package/dist/api/LambderApiOutputValidationError.d.ts +32 -0
- package/dist/api/LambderApiOutputValidationError.js +50 -0
- package/dist/api/LambderApiPipeline.d.ts +77 -39
- package/dist/api/LambderApiPipeline.js +135 -62
- package/dist/api/LambderApiRateLimits.d.ts +208 -54
- package/dist/api/LambderApiRateLimits.js +197 -108
- package/dist/api/LambderApiRequest.d.ts +27 -21
- package/dist/api/LambderApiRequest.js +26 -19
- package/dist/api/LambderApiSignature.d.ts +12 -15
- package/dist/api/LambderApiSignature.js +28 -51
- package/dist/api/LambderApiValidationRefusal.d.ts +9 -9
- package/dist/api/LambderApiValidationRefusal.js +10 -10
- package/dist/build/freshProcessVerifier.d.ts +13 -0
- package/dist/build/freshProcessVerifier.js +19 -0
- package/dist/build/writeApiSignatures.d.ts +109 -0
- package/dist/build/writeApiSignatures.js +222 -0
- package/dist/build.d.ts +9 -0
- package/dist/build.js +8 -0
- package/dist/client/LambderCaller.d.ts +13 -44
- package/dist/client/LambderCaller.js +77 -84
- package/dist/client/LambderReloadLoopBreaker.d.ts +56 -26
- package/dist/client/LambderReloadLoopBreaker.js +90 -46
- package/dist/client/lambderFetchTransport.d.ts +4 -1
- package/dist/client/lambderFetchTransport.js +52 -28
- package/dist/client.d.ts +5 -3
- package/dist/client.js +2 -1
- package/dist/core/Lambder.d.ts +161 -69
- package/dist/core/Lambder.js +370 -226
- package/dist/core/LambderContext.d.ts +82 -15
- package/dist/core/LambderContext.js +107 -20
- package/dist/core/LambderCors.d.ts +21 -3
- package/dist/core/LambderCors.js +35 -16
- package/dist/core/LambderCrashHandling.d.ts +40 -0
- package/dist/core/LambderCrashHandling.js +97 -0
- package/dist/core/LambderCreateOptions.d.ts +151 -75
- package/dist/core/LambderCreateOptions.js +16 -23
- package/dist/core/LambderFiles.d.ts +28 -7
- package/dist/core/LambderFiles.js +73 -33
- package/dist/core/LambderIndexHtml.js +12 -11
- package/dist/core/LambderPolicyBuilders.d.ts +17 -5
- package/dist/core/LambderPolicyBuilders.js +17 -5
- package/dist/core/LambderPublicFiles.d.ts +11 -5
- package/dist/core/LambderPublicFiles.js +32 -4
- package/dist/core/LambderRequestPath.d.ts +43 -0
- package/dist/core/LambderRequestPath.js +63 -0
- package/dist/core/LambderResponse.d.ts +26 -5
- package/dist/core/LambderResponse.js +157 -70
- package/dist/core/LambderResponseBuilder.d.ts +49 -4
- package/dist/core/LambderResponseBuilder.js +64 -3
- package/dist/core/LambderRouting.d.ts +2 -3
- package/dist/core/LambderRouting.js +22 -7
- package/dist/core/LambderTemplatingEngine.js +211 -32
- package/dist/index.d.ts +15 -8
- package/dist/index.js +5 -4
- package/dist/invoke/LambderInvokeCaller.d.ts +37 -42
- package/dist/invoke/LambderInvokeCaller.js +76 -66
- package/dist/invoke/LambderInvokeOutcome.d.ts +27 -26
- package/dist/invoke/LambderInvokeOutcome.js +9 -22
- package/dist/invoke/LambderLambdaEvent.d.ts +44 -10
- package/dist/invoke/LambderLambdaEvent.js +80 -37
- package/dist/invoke/lambderHandlerTransport.d.ts +12 -10
- package/dist/invoke/lambderHandlerTransport.js +16 -19
- package/dist/mock/LambderMockApp.d.ts +67 -83
- package/dist/mock/LambderMockApp.js +167 -153
- package/dist/mock/LambderMockBrowserCookies.d.ts +24 -28
- package/dist/mock/LambderMockBrowserCookies.js +24 -28
- package/dist/mock/LambderMockCallRecorder.d.ts +15 -22
- package/dist/mock/LambderMockCallRecorder.js +19 -28
- package/dist/mock/LambderMockCreateOptions.d.ts +42 -24
- package/dist/mock/LambderMockEntryRegistry.d.ts +11 -12
- package/dist/mock/LambderMockEntryRegistry.js +24 -29
- package/dist/mock/LambderMockFailureInjector.d.ts +3 -6
- package/dist/mock/LambderMockFailureInjector.js +3 -6
- package/dist/mock/LambderMockTypes.d.ts +78 -108
- package/dist/mock/lambderMockInvokeTransport.d.ts +11 -13
- package/dist/mock/lambderMockInvokeTransport.js +11 -10
- package/dist/mock/lambderMockMswHandler.d.ts +33 -29
- package/dist/mock/lambderMockMswHandler.js +50 -39
- package/dist/mock.d.ts +3 -1
- package/dist/mock.js +5 -3
- package/dist/session/LambderSessionController.d.ts +108 -89
- package/dist/session/LambderSessionController.js +187 -168
- package/dist/session/LambderSessionCrypto.d.ts +16 -7
- package/dist/session/LambderSessionCrypto.js +26 -12
- package/dist/session/LambderSessionManager.d.ts +136 -47
- package/dist/session/LambderSessionManager.js +280 -139
- package/dist/shared/LambderHtml.d.ts +42 -3
- package/dist/shared/LambderHtml.js +127 -7
- package/dist/shared/LambderHtmlPositions.d.ts +173 -0
- package/dist/shared/LambderHtmlPositions.js +652 -0
- package/dist/shared/LambderI18n.d.ts +10 -11
- package/dist/shared/LambderI18n.js +33 -21
- package/dist/shared/contracts/LambderCache.d.ts +66 -0
- package/dist/shared/contracts/LambderCache.js +11 -0
- package/dist/shared/contracts/LambderFileSource.d.ts +6 -6
- package/dist/shared/contracts/LambderFileSource.js +5 -8
- package/dist/shared/contracts/LambderIdempotencyStore.d.ts +51 -22
- package/dist/shared/contracts/LambderIdempotencyStore.js +4 -5
- package/dist/shared/contracts/LambderRateLimiter.d.ts +27 -15
- package/dist/shared/contracts/LambderRateLimiter.js +4 -5
- package/dist/shared/contracts/LambderSessionStore.d.ts +65 -26
- package/dist/shared/contracts/LambderSessionStore.js +5 -6
- package/dist/shared/transport/LambderApiTransport.d.ts +27 -27
- package/dist/shared/transport/LambderApiTransport.js +7 -7
- package/dist/shared/transport/LambderCookieJar.d.ts +28 -35
- package/dist/shared/transport/LambderCookieJar.js +54 -66
- package/dist/shared/transport/lambderCookieJarTransport.d.ts +11 -13
- package/dist/shared/transport/lambderCookieJarTransport.js +24 -23
- package/dist/shared/util/LambderCallAbort.d.ts +5 -5
- package/dist/shared/util/LambderCallAbort.js +5 -5
- package/dist/shared/util/LambderClientIp.d.ts +27 -11
- package/dist/shared/util/LambderClientIp.js +96 -13
- package/dist/shared/util/LambderExpiringMap.d.ts +35 -49
- package/dist/shared/util/LambderExpiringMap.js +41 -57
- package/dist/shared/util/LambderNodeModules.js +6 -7
- package/dist/shared/util/LambderOptionChecks.d.ts +4 -4
- package/dist/shared/util/LambderOptionChecks.js +4 -4
- package/dist/shared/util/LambderResponseBrand.d.ts +5 -5
- package/dist/shared/util/LambderResponseBrand.js +5 -5
- package/dist/shared/util/LambderTestingDoors.d.ts +29 -0
- package/dist/shared/util/LambderTestingDoors.js +29 -0
- package/dist/shared/util/LambderTypeUtilities.d.ts +7 -8
- package/dist/shared/util/LambderTypeUtilities.js +3 -3
- package/dist/shared/util/boundKeyField.d.ts +20 -0
- package/dist/shared/util/boundKeyField.js +34 -0
- package/dist/shared/util/canonicalJson.d.ts +11 -0
- package/dist/shared/util/canonicalJson.js +28 -0
- package/dist/shared/util/joinKeyFields.d.ts +20 -0
- package/dist/shared/util/joinKeyFields.js +22 -0
- package/dist/shared/wire/LambderAnswerHeaders.d.ts +12 -16
- package/dist/shared/wire/LambderAnswerHeaders.js +12 -16
- package/dist/shared/wire/LambderApiContract.d.ts +107 -32
- package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
- package/dist/shared/wire/LambderApiOutcome.js +48 -23
- package/dist/shared/wire/LambderApiRefusal.d.ts +39 -27
- package/dist/shared/wire/LambderApiRefusal.js +36 -7
- package/dist/shared/wire/LambderApiSignature.d.ts +18 -22
- package/dist/shared/wire/LambderApiSignature.js +16 -19
- package/dist/shared/wire/LambderCallOptions.d.ts +38 -47
- package/dist/shared/wire/LambderCallOptions.js +9 -11
- package/dist/shared/wire/LambderCompressionCodec.d.ts +29 -34
- package/dist/shared/wire/LambderCompressionCodec.js +31 -36
- package/dist/shared/wire/LambderCompressionOption.d.ts +9 -9
- package/dist/shared/wire/LambderCompressionOption.js +9 -9
- package/dist/shared/wire/LambderCrashDetail.d.ts +12 -15
- package/dist/shared/wire/LambderCrashDetail.js +12 -15
- package/dist/shared/wire/LambderDefaultApiPath.d.ts +6 -0
- package/dist/shared/wire/LambderDefaultApiPath.js +6 -0
- package/dist/shared/wire/LambderHttpStatus.d.ts +6 -7
- package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +89 -0
- package/dist/shared/wire/LambderIdempotencyKeyScope.js +146 -0
- package/dist/shared/wire/LambderInvokeApiId.d.ts +27 -0
- package/dist/shared/wire/LambderInvokeApiId.js +27 -0
- package/dist/shared/wire/LambderOutcomeAssertions.d.ts +79 -0
- package/dist/shared/wire/LambderOutcomeAssertions.js +112 -0
- package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
- package/dist/shared/wire/LambderRequestPayload.js +4 -6
- package/dist/stores/LambderCacheFiller.d.ts +48 -0
- package/dist/stores/LambderCacheFiller.js +119 -0
- package/dist/stores/LambderCacheKeys.d.ts +26 -0
- package/dist/stores/LambderCacheKeys.js +54 -0
- package/dist/stores/LambderCacheValues.d.ts +45 -0
- package/dist/stores/LambderCacheValues.js +74 -0
- package/dist/stores/LambderDdbCache.d.ts +121 -56
- package/dist/stores/LambderDdbCache.js +528 -225
- package/dist/stores/LambderDdbIdempotencyStore.d.ts +33 -22
- package/dist/stores/LambderDdbIdempotencyStore.js +75 -50
- package/dist/stores/LambderDdbRateLimiter.d.ts +76 -20
- package/dist/stores/LambderDdbRateLimiter.js +151 -39
- package/dist/stores/LambderDdbSdk.d.ts +43 -31
- package/dist/stores/LambderDdbSdk.js +79 -33
- package/dist/stores/LambderDdbSessionStore.d.ts +27 -14
- package/dist/stores/LambderDdbSessionStore.js +119 -47
- package/dist/stores/LambderHttpFileSource.d.ts +15 -6
- package/dist/stores/LambderHttpFileSource.js +15 -13
- package/dist/stores/LambderMemoryCache.d.ts +49 -0
- package/dist/stores/LambderMemoryCache.js +113 -0
- package/dist/stores/LambderMemoryIdempotencyStore.d.ts +13 -12
- package/dist/stores/LambderMemoryIdempotencyStore.js +31 -30
- package/dist/stores/LambderMemoryRateLimiter.d.ts +8 -9
- package/dist/stores/LambderMemoryRateLimiter.js +14 -13
- package/dist/stores/LambderMemorySessionStore.d.ts +14 -11
- package/dist/stores/LambderMemorySessionStore.js +38 -19
- package/dist/stores/LambderS3FileSource.d.ts +21 -6
- package/dist/stores/LambderS3FileSource.js +12 -7
- package/dist/testing/LambderTestApp.d.ts +176 -0
- package/dist/testing/LambderTestApp.js +204 -0
- package/dist/testing/LambderTestVisitor.d.ts +153 -0
- package/dist/testing/LambderTestVisitor.js +154 -0
- package/dist/testing.d.ts +27 -0
- package/dist/testing.js +24 -0
- package/package.json +20 -3
- package/dist/api/LambderApiPolicyEngine.d.ts +0 -36
- package/dist/api/LambderApiPolicyEngine.js +0 -77
- package/dist/shared/util/LambderKeyFields.d.ts +0 -32
- package/dist/shared/util/LambderKeyFields.js +0 -34
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The small type utilities more than one module needs.
|
|
3
3
|
*
|
|
4
|
-
* Nothing here is Lambder's own vocabulary: these are
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Nothing here is Lambder's own vocabulary: these are shapes TypeScript does
|
|
5
|
+
* not ship, written once so they cannot drift in name or meaning across the
|
|
6
|
+
* modules that use them.
|
|
7
7
|
*/
|
|
8
8
|
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounding the caller-supplied field of a tracker or scope key.
|
|
3
|
+
*
|
|
4
|
+
* The rate-limit engine and the idempotency engine each build a store key
|
|
5
|
+
* around a field only the caller controls: a custom rate-limit key or a
|
|
6
|
+
* session key, a callerIdentity or a session key. A store has a key limit of
|
|
7
|
+
* its own (a DynamoDB partition key stops at 2048 bytes) and refuses a key
|
|
8
|
+
* past it by throwing, and both engines fail open on a store throw by
|
|
9
|
+
* default: a long enough field (a 3,000-character email, a device token)
|
|
10
|
+
* would turn the rate limit or the idempotency off for that caller, in
|
|
11
|
+
* silence, while the table held the field in plain text. So the field is
|
|
12
|
+
* bounded before any store sees it, in one implementation the two engines
|
|
13
|
+
* share, so they cannot drift apart.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The field bounded: `<kind>:<value>` while the value fits, `<kind>:h:<sha256
|
|
17
|
+
* hex>` once it does not. The digest keeps distinct callers on distinct
|
|
18
|
+
* counters and scopes, and a value that fits stays readable in the table.
|
|
19
|
+
*/
|
|
20
|
+
export declare const boundKeyField: (kind: string, value: string) => Promise<string>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounding the caller-supplied field of a tracker or scope key.
|
|
3
|
+
*
|
|
4
|
+
* The rate-limit engine and the idempotency engine each build a store key
|
|
5
|
+
* around a field only the caller controls: a custom rate-limit key or a
|
|
6
|
+
* session key, a callerIdentity or a session key. A store has a key limit of
|
|
7
|
+
* its own (a DynamoDB partition key stops at 2048 bytes) and refuses a key
|
|
8
|
+
* past it by throwing, and both engines fail open on a store throw by
|
|
9
|
+
* default: a long enough field (a 3,000-character email, a device token)
|
|
10
|
+
* would turn the rate limit or the idempotency off for that caller, in
|
|
11
|
+
* silence, while the table held the field in plain text. So the field is
|
|
12
|
+
* bounded before any store sees it, in one implementation the two engines
|
|
13
|
+
* share, so they cannot drift apart.
|
|
14
|
+
*/
|
|
15
|
+
import { joinKeyFields } from "./joinKeyFields.js";
|
|
16
|
+
import { sha256HexOf } from "./LambderTextDigest.js";
|
|
17
|
+
/**
|
|
18
|
+
* The ceiling, in UTF-8 bytes, on the field as it is written into the key;
|
|
19
|
+
* past it, the field is replaced by its digest. Measured escaped, since
|
|
20
|
+
* joinKeyFields doubles every separator and escape character: a field of
|
|
21
|
+
* 1,000 separators is 2,000 bytes in the key. 1024 sits well inside every
|
|
22
|
+
* store's key limit, with the store's prefix and the engine's other fields
|
|
23
|
+
* (API and policy names, a posted idempotency key of at most 200 characters)
|
|
24
|
+
* joined around it.
|
|
25
|
+
*/
|
|
26
|
+
const MAX_KEY_FIELD_BYTES = 1024;
|
|
27
|
+
/**
|
|
28
|
+
* The field bounded: `<kind>:<value>` while the value fits, `<kind>:h:<sha256
|
|
29
|
+
* hex>` once it does not. The digest keeps distinct callers on distinct
|
|
30
|
+
* counters and scopes, and a value that fits stays readable in the table.
|
|
31
|
+
*/
|
|
32
|
+
export const boundKeyField = async (kind, value) => new TextEncoder().encode(joinKeyFields(value)).length > MAX_KEY_FIELD_BYTES
|
|
33
|
+
? `${kind}:h:${await sha256HexOf(value)}`
|
|
34
|
+
: `${kind}:${value}`;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON with object keys sorted at every level, so two values that are the
|
|
3
|
+
* same data hash the same whatever order their keys were built in. Arrays
|
|
4
|
+
* keep their order: a tuple's positions and an enum's values are part of the
|
|
5
|
+
* data. Undefined entries are dropped, as JSON.stringify would drop them.
|
|
6
|
+
*
|
|
7
|
+
* What the API signature digests a schema's description with, and what the
|
|
8
|
+
* idempotency engine fingerprints a request with: two hashes that must not
|
|
9
|
+
* depend on the order a client or a builder happened to write keys in.
|
|
10
|
+
*/
|
|
11
|
+
export declare const canonicalJson: (value: unknown) => string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON with object keys sorted at every level, so two values that are the
|
|
3
|
+
* same data hash the same whatever order their keys were built in. Arrays
|
|
4
|
+
* keep their order: a tuple's positions and an enum's values are part of the
|
|
5
|
+
* data. Undefined entries are dropped, as JSON.stringify would drop them.
|
|
6
|
+
*
|
|
7
|
+
* What the API signature digests a schema's description with, and what the
|
|
8
|
+
* idempotency engine fingerprints a request with: two hashes that must not
|
|
9
|
+
* depend on the order a client or a builder happened to write keys in.
|
|
10
|
+
*/
|
|
11
|
+
export const canonicalJson = (value) => JSON.stringify(sortKeys(value));
|
|
12
|
+
const sortKeys = (value) => {
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return value.map(sortKeys);
|
|
15
|
+
if (value === null || typeof value !== "object")
|
|
16
|
+
return value;
|
|
17
|
+
const source = value;
|
|
18
|
+
// No prototype, so a "__proto__" key (JSON.parse makes it an own key) is
|
|
19
|
+
// kept as data. On a plain object the assignment would set the prototype
|
|
20
|
+
// instead, and two payloads differing only under "__proto__" would share
|
|
21
|
+
// one fingerprint.
|
|
22
|
+
const sorted = Object.create(null);
|
|
23
|
+
for (const key of Object.keys(source).sort()) {
|
|
24
|
+
if (source[key] !== undefined)
|
|
25
|
+
sorted[key] = sortKeys(source[key]);
|
|
26
|
+
}
|
|
27
|
+
return sorted;
|
|
28
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Joining the fields of a tracker or scope key.
|
|
3
|
+
*
|
|
4
|
+
* The rate limiter and the idempotency engine each join fields with a
|
|
5
|
+
* separator, and at least one field is caller data (a policy's rate-limit
|
|
6
|
+
* key, a posted idempotency key). A plain join lets two field lists produce
|
|
7
|
+
* one string, so two callers would share a counter or one would read
|
|
8
|
+
* another's stored answer. The caller's separator is escaped, not refused: a
|
|
9
|
+
* limit that rejects a legal key is a bug of its own.
|
|
10
|
+
*
|
|
11
|
+
* The join is one-way (a key is looked up or compared, never taken apart),
|
|
12
|
+
* unlike LambderDdbCache's reversible sort-key escape, which listSortKeys
|
|
13
|
+
* must decode to exactly what was written; neither stands in for the other.
|
|
14
|
+
* Both engines share this one implementation so they cannot drift apart.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The fields joined into one key, each escaped, so no two distinct field
|
|
18
|
+
* lists can produce the same string.
|
|
19
|
+
*/
|
|
20
|
+
export declare const joinKeyFields: (...fields: string[]) => string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Joining the fields of a tracker or scope key.
|
|
3
|
+
*
|
|
4
|
+
* The rate limiter and the idempotency engine each join fields with a
|
|
5
|
+
* separator, and at least one field is caller data (a policy's rate-limit
|
|
6
|
+
* key, a posted idempotency key). A plain join lets two field lists produce
|
|
7
|
+
* one string, so two callers would share a counter or one would read
|
|
8
|
+
* another's stored answer. The caller's separator is escaped, not refused: a
|
|
9
|
+
* limit that rejects a legal key is a bug of its own.
|
|
10
|
+
*
|
|
11
|
+
* The join is one-way (a key is looked up or compared, never taken apart),
|
|
12
|
+
* unlike LambderDdbCache's reversible sort-key escape, which listSortKeys
|
|
13
|
+
* must decode to exactly what was written; neither stands in for the other.
|
|
14
|
+
* Both engines share this one implementation so they cannot drift apart.
|
|
15
|
+
*/
|
|
16
|
+
/** One field, with the separator and its own escape character escaped. */
|
|
17
|
+
const escapeKeyField = (value) => value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
|
|
18
|
+
/**
|
|
19
|
+
* The fields joined into one key, each escaped, so no two distinct field
|
|
20
|
+
* lists can produce the same string.
|
|
21
|
+
*/
|
|
22
|
+
export const joinKeyFields = (...fields) => fields.map(escapeKeyField).join("|");
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* call wrote so it can be applied onto whichever answer the call ends up
|
|
5
5
|
* with.
|
|
6
6
|
*
|
|
7
|
-
* One implementation
|
|
8
|
-
*
|
|
9
|
-
* functions,
|
|
7
|
+
* One implementation at the bottom of the stack, because every layer above
|
|
8
|
+
* needs the same behavior: LambderResponse's own header methods are these
|
|
9
|
+
* three functions, so a second copy cannot drift from them.
|
|
10
10
|
*/
|
|
11
11
|
/** The header's values under a case-insensitive lookup, or undefined. */
|
|
12
12
|
export declare const getAnswerHeader: (headers: Record<string, string[]>, name: string) => string[] | undefined;
|
|
@@ -25,18 +25,15 @@ export type LambderHeaderTarget = {
|
|
|
25
25
|
* the session controller's Set-Cookie), applied onto the answer once the
|
|
26
26
|
* call has one. Recorded as operations in call order rather than as a map,
|
|
27
27
|
* so `set` replaces what the answer itself carries (a Content-Type, say) and
|
|
28
|
-
* `add` appends to it, exactly as
|
|
29
|
-
* directly.
|
|
28
|
+
* `add` appends to it, exactly as if called on the answer directly.
|
|
30
29
|
*
|
|
31
30
|
* These headers belong to the CALL, not to the response that first carried
|
|
32
|
-
* them:
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* collapse to one; duplicate identical header values carry no meaning in
|
|
39
|
-
* HTTP, so nothing observable is lost.
|
|
31
|
+
* them: an afterRender hook may answer with a different response than the
|
|
32
|
+
* handler produced, and a session cookie written during the call must travel
|
|
33
|
+
* to it. So applying never forgets the operations, and applying them twice
|
|
34
|
+
* is a no-op (`add` skips a value the header already carries). The cost is
|
|
35
|
+
* that two identical `add` values under one name collapse to one, which HTTP
|
|
36
|
+
* gives no meaning to anyway.
|
|
40
37
|
*/
|
|
41
38
|
export declare class LambderAnswerHeaders {
|
|
42
39
|
private operations;
|
|
@@ -50,9 +47,8 @@ export declare class LambderAnswerHeaders {
|
|
|
50
47
|
get size(): number;
|
|
51
48
|
/**
|
|
52
49
|
* Applies the recorded operations, in order, onto anything that reads and
|
|
53
|
-
* writes headers: a LambderResponse, or a header map through applyInto
|
|
54
|
-
*
|
|
55
|
-
* drift apart.
|
|
50
|
+
* writes headers: a LambderResponse, or a header map through applyInto,
|
|
51
|
+
* so both targets share one definition of what an operation does.
|
|
56
52
|
*/
|
|
57
53
|
applyTo(target: LambderHeaderTarget, fromIndex?: number): void;
|
|
58
54
|
/** The same, onto an answer's plain header map. */
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* call wrote so it can be applied onto whichever answer the call ends up
|
|
5
5
|
* with.
|
|
6
6
|
*
|
|
7
|
-
* One implementation
|
|
8
|
-
*
|
|
9
|
-
* functions,
|
|
7
|
+
* One implementation at the bottom of the stack, because every layer above
|
|
8
|
+
* needs the same behavior: LambderResponse's own header methods are these
|
|
9
|
+
* three functions, so a second copy cannot drift from them.
|
|
10
10
|
*/
|
|
11
11
|
/** The header's values under a case-insensitive lookup, or undefined. */
|
|
12
12
|
export const getAnswerHeader = (headers, name) => {
|
|
@@ -40,18 +40,15 @@ export const addAnswerHeader = (headers, name, value) => {
|
|
|
40
40
|
* the session controller's Set-Cookie), applied onto the answer once the
|
|
41
41
|
* call has one. Recorded as operations in call order rather than as a map,
|
|
42
42
|
* so `set` replaces what the answer itself carries (a Content-Type, say) and
|
|
43
|
-
* `add` appends to it, exactly as
|
|
44
|
-
* directly.
|
|
43
|
+
* `add` appends to it, exactly as if called on the answer directly.
|
|
45
44
|
*
|
|
46
45
|
* These headers belong to the CALL, not to the response that first carried
|
|
47
|
-
* them:
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* collapse to one; duplicate identical header values carry no meaning in
|
|
54
|
-
* HTTP, so nothing observable is lost.
|
|
46
|
+
* them: an afterRender hook may answer with a different response than the
|
|
47
|
+
* handler produced, and a session cookie written during the call must travel
|
|
48
|
+
* to it. So applying never forgets the operations, and applying them twice
|
|
49
|
+
* is a no-op (`add` skips a value the header already carries). The cost is
|
|
50
|
+
* that two identical `add` values under one name collapse to one, which HTTP
|
|
51
|
+
* gives no meaning to anyway.
|
|
55
52
|
*/
|
|
56
53
|
export class LambderAnswerHeaders {
|
|
57
54
|
operations = [];
|
|
@@ -69,9 +66,8 @@ export class LambderAnswerHeaders {
|
|
|
69
66
|
get size() { return this.operations.length; }
|
|
70
67
|
/**
|
|
71
68
|
* Applies the recorded operations, in order, onto anything that reads and
|
|
72
|
-
* writes headers: a LambderResponse, or a header map through applyInto
|
|
73
|
-
*
|
|
74
|
-
* drift apart.
|
|
69
|
+
* writes headers: a LambderResponse, or a header map through applyInto,
|
|
70
|
+
* so both targets share one definition of what an operation does.
|
|
75
71
|
*/
|
|
76
72
|
applyTo(target, fromIndex = 0) {
|
|
77
73
|
for (const operation of this.operations.slice(fromIndex)) {
|
|
@@ -38,7 +38,14 @@ export type LambderApiResponseConfig = {
|
|
|
38
38
|
sessionExpired?: boolean;
|
|
39
39
|
notAuthorized?: boolean;
|
|
40
40
|
message?: any;
|
|
41
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* A refusal message, or a plain string when writing
|
|
43
|
+
* (`res.api(null, { errorMessage: "..." })`): the envelope goes out with
|
|
44
|
+
* the message object either way. Read off the wire it can still be a
|
|
45
|
+
* string, or no message at all, wherever a Lambder server did not write
|
|
46
|
+
* the body (a hand-built mock answer, a proxy); refusalMessageOf reads
|
|
47
|
+
* whatever arrives as a message.
|
|
48
|
+
*/
|
|
42
49
|
errorMessage?: LambderAppRefusalMessage | string;
|
|
43
50
|
logList?: any[];
|
|
44
51
|
/**
|
|
@@ -57,16 +64,76 @@ export type LambderApiResponseConfig = {
|
|
|
57
64
|
* indistinguishable from an endpoint that answered nothing on purpose.
|
|
58
65
|
*/
|
|
59
66
|
export type LambderApiNullAnswerConfig = LambderNonEmptyOptionMap<Pick<LambderApiResponseConfig, "versionExpired" | "sessionExpired" | "notAuthorized" | "errorMessage" | "message">> & LambderApiResponseConfig;
|
|
60
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* The API wire envelope both sides speak: res.api() emits it, LambderCaller
|
|
69
|
+
* parses it. `apiVersion` is always there (null when the server set none):
|
|
70
|
+
* it is how a reader tells a Lambder envelope from another JSON answer, such
|
|
71
|
+
* as API Gateway's own `{ "message": ... }` errors, so an answer without it
|
|
72
|
+
* reads as a server failure.
|
|
73
|
+
*/
|
|
61
74
|
export type LambderApiEnvelopeBody<T> = LambderApiResponseConfig & {
|
|
62
|
-
apiVersion
|
|
75
|
+
apiVersion: string | null;
|
|
63
76
|
payload?: T | null;
|
|
64
77
|
};
|
|
78
|
+
/** A value that is already JSON, recursive structures such as z.json() included. */
|
|
79
|
+
type LambderJsonValue = string | number | boolean | null | LambderJsonValue[] | {
|
|
80
|
+
[key: string]: LambderJsonValue;
|
|
81
|
+
};
|
|
82
|
+
/** An array item once it has been through JSON: what an object would drop, an array writes as null. */
|
|
83
|
+
type LambderJsonArrayItemOf<T> = T extends undefined | symbol | ((...args: any[]) => unknown) ? null : LambderJsonOf<T>;
|
|
84
|
+
/**
|
|
85
|
+
* The keys of object T that JSON may leave out: those whose value may be
|
|
86
|
+
* undefined, since JSON.stringify omits such a key. Distributed over K, the
|
|
87
|
+
* keys of T, one at a time. An index signature is never one of them: a
|
|
88
|
+
* record's undefined entries are left out, which its value type already
|
|
89
|
+
* says once undefined is dropped from it.
|
|
90
|
+
*/
|
|
91
|
+
type LambderJsonOmissibleKeys<T, K extends keyof T = keyof T> = K extends keyof T ? (string extends K ? never : number extends K ? never : undefined extends T[K] ? K : never) : never;
|
|
92
|
+
/**
|
|
93
|
+
* The type a value has once it has been through JSON: what an API's output
|
|
94
|
+
* reaches a client as. A Date becomes its string (through toJSON), a
|
|
95
|
+
* function, a symbol or an undefined member is dropped (written as null in
|
|
96
|
+
* an array), and a bigint, which JSON.stringify refuses, is never. A key
|
|
97
|
+
* whose value may be undefined is optional, since JSON leaves it out then,
|
|
98
|
+
* and a Map or a Set, whose entries are not properties, is written as an
|
|
99
|
+
* empty object. `unknown` stays unknown, and a type that is already JSON
|
|
100
|
+
* maps to itself, which is also what lets a recursive one such as z.json()
|
|
101
|
+
* resolve.
|
|
102
|
+
*
|
|
103
|
+
* An object is mapped in two steps. The omissible keys are made optional
|
|
104
|
+
* first, through the key types alone, and the mapping over the result then
|
|
105
|
+
* keeps each key's modifiers. Deciding optionality inside the mapping, per
|
|
106
|
+
* key, would need the mapped value of every key before the object's own
|
|
107
|
+
* keys were known, which a recursive type (a tree of its own nodes) cannot
|
|
108
|
+
* give without recursing for ever.
|
|
109
|
+
*/
|
|
110
|
+
export type LambderJsonOf<T> = unknown extends T ? T : T extends LambderJsonValue ? T : T extends {
|
|
111
|
+
toJSON(): infer TJson;
|
|
112
|
+
} ? LambderJsonOf<TJson> : T extends undefined | bigint | symbol | ((...args: any[]) => unknown) ? never : T extends readonly unknown[] ? {
|
|
113
|
+
[K in keyof T]: LambderJsonArrayItemOf<T[K]>;
|
|
114
|
+
} : T extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> ? {} : T extends object ? (Partial<Pick<T, LambderJsonOmissibleKeys<T>>> & Omit<T, LambderJsonOmissibleKeys<T>>) extends infer TKeyed ? {
|
|
115
|
+
[K in keyof TKeyed as K extends string | number ? (string extends K ? K : number extends K ? K : [LambderJsonOf<TKeyed[K]>] extends [never] ? never : K) : never]: LambderJsonOf<TKeyed[K]>;
|
|
116
|
+
} : never : never;
|
|
117
|
+
/**
|
|
118
|
+
* What an API's output reaches the client as: LambderJsonOf of the schema's
|
|
119
|
+
* output, except at the top, where an envelope carries no payload at all
|
|
120
|
+
* rather than a JSON `undefined`. A void or undefined output keeps its type,
|
|
121
|
+
* so a handler and a mock handler answer nothing, and an output that may be
|
|
122
|
+
* undefined keeps that member, which LambderJsonOf drops as it would a
|
|
123
|
+
* member of an object.
|
|
124
|
+
*/
|
|
125
|
+
export type LambderJsonOutputOf<T> = [T] extends [void] ? T : (undefined extends T ? undefined : never) | LambderJsonOf<T>;
|
|
65
126
|
/**
|
|
66
127
|
* One contract entry as addApi/addSessionApi record it: the payload types,
|
|
67
128
|
* the mode, and every declarative option exactly as written. Options that
|
|
68
129
|
* were not written are absent rather than undefined, so `keyof` an entry
|
|
69
130
|
* lists only what the endpoint declared.
|
|
131
|
+
*
|
|
132
|
+
* `In` is what a client sends (the input schema's z.input: a field with a
|
|
133
|
+
* default is optional, a transform's source type is what is posted) and `Out`
|
|
134
|
+
* what it receives (the output schema's z.output as JSON, see
|
|
135
|
+
* LambderJsonOf). The handler's own types are the other side of each, and
|
|
136
|
+
* are not recorded here.
|
|
70
137
|
*/
|
|
71
138
|
export type LambderContractEntry<In, Out, Mode extends LambderApiMode, GuardInputs = never, Guards = never, RateLimit = never, Idempotency = never> = {
|
|
72
139
|
input: In;
|
|
@@ -94,43 +161,33 @@ export type LambderMergeContract<Old, Name extends string, Entry> = Old & {
|
|
|
94
161
|
* ```
|
|
95
162
|
*
|
|
96
163
|
* Chaining leaves the contract an intersection one member deep per endpoint
|
|
97
|
-
* (LambderMergeContract above), and every `C[K]`
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* 182-endpoint app one indexed access measured ~3,000 type instantiations
|
|
103
|
-
* and one mock registration ~18,000.
|
|
164
|
+
* (LambderMergeContract above), and every `C[K]` against a type parameter
|
|
165
|
+
* resolves the property across all of them. The reading helpers below are
|
|
166
|
+
* built on that lookup, so each pays it again per endpoint: in a 182-endpoint
|
|
167
|
+
* app one indexed access costs ~3,000 type instantiations and one mock
|
|
168
|
+
* registration ~18,000.
|
|
104
169
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* `interface ... extends` spelling is the point.
|
|
113
|
-
*
|
|
114
|
-
* Diagnostics are the same ones, and they read better: a message naming the
|
|
115
|
-
* contract prints the interface by name, where the intersection is printed
|
|
116
|
-
* as a truncated spill of entries.
|
|
170
|
+
* An interface's members are declared, so they resolve once for the whole
|
|
171
|
+
* declaration: the same access costs ~6 instantiations instead, roughly
|
|
172
|
+
* halving such an app's frontend type check time. The alias form
|
|
173
|
+
* (`type C = LambderFlattenContract<...>`) does NOT do this: a mapped type
|
|
174
|
+
* stays deferred and each lookup pays in full, so the `interface ... extends`
|
|
175
|
+
* spelling is the point. Diagnostics also print the interface by name rather
|
|
176
|
+
* than a truncated spill of entries.
|
|
117
177
|
*
|
|
118
178
|
* Every endpoint name must be a string literal for an interface to extend
|
|
119
179
|
* the result, which registration through addApi/addSessionApi guarantees.
|
|
120
180
|
*
|
|
121
|
-
* Two things
|
|
181
|
+
* Two things that look like tidying undo it:
|
|
122
182
|
*
|
|
123
183
|
* - `@typescript-eslint/no-empty-object-type` reports the empty body as
|
|
124
|
-
* "equivalent to its supertype" and its fix is a type alias,
|
|
125
|
-
*
|
|
126
|
-
* line rather than taking the fix.
|
|
184
|
+
* "equivalent to its supertype" and its fix is a type alias, the one
|
|
185
|
+
* spelling that collapses nothing. Disable the rule on the line instead.
|
|
127
186
|
* - Extending anything but a mapped type loses the inferable index signature.
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* that, along with the flattened contract being the same type member for
|
|
133
|
-
* member.
|
|
187
|
+
* A hand-written `interface C { ... }` has none, so it is not assignable to
|
|
188
|
+
* LambderApiContractShape, and initLambderMock<C>, LambderCaller<C> and
|
|
189
|
+
* LambderInvokeCaller<C> reject it. api-contract.test.ts pins this, and
|
|
190
|
+
* that the flattened contract is the same type member for member.
|
|
134
191
|
*/
|
|
135
192
|
export type LambderFlattenContract<C> = {
|
|
136
193
|
[K in keyof C]: C[K];
|
|
@@ -149,6 +206,23 @@ export type LambderContractKeysWithMode<C, M extends LambderApiMode> = {
|
|
|
149
206
|
export type LambderContractGuardsOf<C, K extends keyof C> = C[K] extends {
|
|
150
207
|
guards: infer G;
|
|
151
208
|
} ? G : never;
|
|
209
|
+
/**
|
|
210
|
+
* The endpoint names whose guards option names guard N, in any of its three
|
|
211
|
+
* forms and whatever else it declares beside it. What a test that calls
|
|
212
|
+
* every endpoint behind one guard loops over, and what a list meant to hold
|
|
213
|
+
* exactly those endpoints is checked against. `satisfies` refuses a name the
|
|
214
|
+
* guard does not cover; a missing name needs a check of its own:
|
|
215
|
+
*
|
|
216
|
+
* ```ts
|
|
217
|
+
* type AdminApi = LambderContractKeysWithGuard<Contract, "platformAdmin">;
|
|
218
|
+
* const ADMIN_APIS = ["admin.listUsers", "admin.deleteUser"] as const satisfies readonly AdminApi[];
|
|
219
|
+
* // Fails to compile while an endpoint behind the guard is left off the list.
|
|
220
|
+
* const adminApisComplete: [Exclude<AdminApi, (typeof ADMIN_APIS)[number]>] extends [never] ? true : false = true;
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
export type LambderContractKeysWithGuard<C, N extends string> = {
|
|
224
|
+
[K in keyof C]: N extends LambderGuardNamesIn<LambderContractGuardsOf<C, K>> ? K : never;
|
|
225
|
+
}[keyof C] & string;
|
|
152
226
|
/** Every guard name any endpoint of the contract declares, or only the endpoints of mode M. */
|
|
153
227
|
export type LambderContractGuardNames<C, M extends LambderApiMode = LambderApiMode> = {
|
|
154
228
|
[K in LambderContractKeysWithMode<C, M>]: LambderGuardNamesIn<LambderContractGuardsOf<C, K>>;
|
|
@@ -181,3 +255,4 @@ export type LambderContractRateLimitNames<C, M extends LambderApiMode = LambderA
|
|
|
181
255
|
export type LambderContractIdempotencyOf<C, K extends keyof C> = C[K] extends {
|
|
182
256
|
idempotency: infer I;
|
|
183
257
|
} ? I : never;
|
|
258
|
+
export {};
|
|
@@ -5,14 +5,13 @@
|
|
|
5
5
|
* over a direct Lambda invoke) receive the same envelope and must read it
|
|
6
6
|
* the same way: which status is a crash, which is a rejected input, in what
|
|
7
7
|
* order the envelope flags are honoured, what a non-envelope body means.
|
|
8
|
-
* Both hand their answer to resolveApiOutcome and act on the result;
|
|
9
|
-
* side effects
|
|
10
|
-
*
|
|
11
|
-
* entry resolves it.
|
|
8
|
+
* Both hand their answer to resolveApiOutcome and act on the result; their
|
|
9
|
+
* side effects (handlers, cookie clearing, error reporting) stay with them.
|
|
10
|
+
* Pure and dependency-free, so the browser entry can include it.
|
|
12
11
|
*/
|
|
13
12
|
import type { z } from "zod";
|
|
14
13
|
import type { LambderApiEnvelopeBody } from "./LambderApiContract.js";
|
|
15
|
-
import type
|
|
14
|
+
import { type LambderAppRefusalMessage } from "./LambderApiRefusal.js";
|
|
16
15
|
/**
|
|
17
16
|
* The 422 body's `zodError` as it survives JSON: a ZodError's name and
|
|
18
17
|
* message, and its issues spelled out. Not a ZodError instance (it has no
|
|
@@ -37,18 +36,17 @@ type LambderApiFailureFields = {
|
|
|
37
36
|
ok: false;
|
|
38
37
|
/** HTTP status, when a response was received. */
|
|
39
38
|
status?: number;
|
|
40
|
-
/** Envelope errorMessage, when the server provided one. */
|
|
41
|
-
errorMessage?: LambderAppRefusalMessage
|
|
42
|
-
/** Seconds to wait before retrying, from the response's Retry-After header (rate-limit refusals send it). */
|
|
39
|
+
/** Envelope errorMessage, when the server provided one: always the message object, a plain string having been read as one (refusalMessageOf). */
|
|
40
|
+
errorMessage?: LambderAppRefusalMessage;
|
|
41
|
+
/** Seconds to wait before retrying, from the response's Retry-After header (rate-limit refusals send it, and so may a 503). */
|
|
43
42
|
retryAfterSeconds?: number;
|
|
44
43
|
/**
|
|
45
44
|
* The answer's logList, when it carried one: the envelope's on a success
|
|
46
45
|
* or an envelope refusal, the parsed 500 body's on a server failure, and
|
|
47
|
-
* the validation body's on a 422
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* printed the logs of the answer whose logs matter most, a 500.
|
|
46
|
+
* the validation body's on a 422. It is on every arm so a caller surfaces
|
|
47
|
+
* logs in ONE place, right after reading the answer, rather than per
|
|
48
|
+
* outcome, where early returns would skip the logs that matter most: a
|
|
49
|
+
* 500's.
|
|
52
50
|
*/
|
|
53
51
|
logList?: unknown[];
|
|
54
52
|
};
|
|
@@ -69,30 +67,32 @@ export type LambderApiValidationFailure = LambderApiFailureFields & {
|
|
|
69
67
|
reason: 'validation';
|
|
70
68
|
zodError: LambderValidationError;
|
|
71
69
|
};
|
|
72
|
-
/** The server answered, and the envelope itself says the call is refused. Always carries that envelope. */
|
|
70
|
+
/** The server answered, and the envelope itself says the call is refused. Always carries that envelope, and an `errorMessage` refusal always carries its message. */
|
|
73
71
|
export type LambderApiEnvelopeFailure<T> = LambderApiFailureFields & {
|
|
74
|
-
reason: 'versionExpired' | 'sessionExpired' | 'notAuthorized' | 'errorMessage';
|
|
75
72
|
response: LambderApiEnvelopeBody<T>;
|
|
76
|
-
}
|
|
73
|
+
} & ({
|
|
74
|
+
reason: 'versionExpired' | 'sessionExpired' | 'notAuthorized';
|
|
75
|
+
} | {
|
|
76
|
+
reason: 'errorMessage';
|
|
77
|
+
errorMessage: LambderAppRefusalMessage;
|
|
78
|
+
});
|
|
77
79
|
/**
|
|
78
80
|
* Discriminated result of an API call: `ok: true` carries the payload, every
|
|
79
81
|
* failure carries a machine-readable reason, so "the server returned null"
|
|
80
82
|
* and "the request failed" are never conflated.
|
|
81
83
|
*
|
|
82
|
-
* The failure side is discriminated by `reason
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* framework's own reader needed three non-null assertions to say what the
|
|
87
|
-
* union already knew.
|
|
84
|
+
* The failure side is discriminated by `reason`, so narrowing to a reason
|
|
85
|
+
* narrows to what it carries: `zodError` after `reason === 'validation'`,
|
|
86
|
+
* `response` after an envelope reason, `error` after the rest, with no
|
|
87
|
+
* non-null assertion needed.
|
|
88
88
|
*/
|
|
89
89
|
export type LambderApiOutcome<T> = LambderApiSuccessOutcome<T> | LambderApiCallFailure<T> | LambderApiValidationFailure | LambderApiEnvelopeFailure<T>;
|
|
90
90
|
/**
|
|
91
|
-
* What reading one HTTP answer can produce
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
91
|
+
* What reading one HTTP answer can produce: LambderApiOutcome minus the
|
|
92
|
+
* three reasons no answer carries (`network` and `timeout` are the caller's
|
|
93
|
+
* own abort, `unknown` is something throwing around the call). A caller that
|
|
94
|
+
* has handled `server` and `validation` holds a success or an envelope
|
|
95
|
+
* refusal, both of which carry the envelope.
|
|
96
96
|
*/
|
|
97
97
|
export type LambderApiAnswerOutcome<T> = LambderApiSuccessOutcome<T> | (LambderApiCallFailure<T> & {
|
|
98
98
|
reason: 'server';
|
|
@@ -100,10 +100,9 @@ export type LambderApiAnswerOutcome<T> = LambderApiSuccessOutcome<T> | (LambderA
|
|
|
100
100
|
/**
|
|
101
101
|
* What the mapping needs from an HTTP answer, whichever transport produced it.
|
|
102
102
|
*
|
|
103
|
-
* Exactly one of `json()` and `text()` is read per answer,
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* that a non-envelope body is still reportable).
|
|
103
|
+
* Exactly one of `json()` and `text()` is read per answer, since a real
|
|
104
|
+
* Response body may only be read once (a 5xx reads text and parses it
|
|
105
|
+
* itself, so a non-envelope body is still reportable).
|
|
107
106
|
*/
|
|
108
107
|
export type LambderApiHttpAnswer = {
|
|
109
108
|
status: number;
|
|
@@ -116,6 +115,17 @@ export type LambderApiHttpAnswer = {
|
|
|
116
115
|
text: () => Promise<string>;
|
|
117
116
|
/** The answer's Set-Cookie header values, for a transport that can see them (a cookie jar consumes them); absent in a browser. */
|
|
118
117
|
setCookies?: string[];
|
|
118
|
+
/**
|
|
119
|
+
* The CSRF tokens of a transport that keeps the session's cookies itself
|
|
120
|
+
* (a cookie jar), where document.cookie is not where they live: the one
|
|
121
|
+
* it posted, and a read of the one it holds now. LambderCaller judges
|
|
122
|
+
* whether a sessionExpired is about the session the page still holds by
|
|
123
|
+
* these; absent, it compares document.cookie before and after the call.
|
|
124
|
+
*/
|
|
125
|
+
csrfTokens?: {
|
|
126
|
+
posted: string;
|
|
127
|
+
held: () => string;
|
|
128
|
+
};
|
|
119
129
|
};
|
|
120
130
|
/**
|
|
121
131
|
* Reads one HTTP answer into an outcome. A 5xx is a server failure that keeps
|
|
@@ -123,6 +133,8 @@ export type LambderApiHttpAnswer = {
|
|
|
123
133
|
* errorMessage, and a global error handler may add crash and logList); a
|
|
124
134
|
* 422 is a validation failure only with Lambder's validation body; anything
|
|
125
135
|
* else must be a JSON envelope, whose flags are honoured in a fixed order.
|
|
136
|
+
* A failure read off any answer but a 422 carries the answer's Retry-After
|
|
137
|
+
* as retryAfterSeconds.
|
|
126
138
|
*/
|
|
127
139
|
export declare const resolveApiOutcome: <T>(answer: LambderApiHttpAnswer) => Promise<LambderApiAnswerOutcome<T>>;
|
|
128
140
|
export {};
|