lambder 7.3.1 → 8.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1047 -3
- package/README.md +46 -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 +68 -62
- package/dist/api/LambderApiIdempotency.js +214 -151
- package/dist/api/LambderApiOutputValidationError.d.ts +32 -0
- package/dist/api/LambderApiOutputValidationError.js +50 -0
- package/dist/api/LambderApiPipeline.d.ts +47 -38
- package/dist/api/LambderApiPipeline.js +122 -63
- package/dist/api/LambderApiRateLimits.d.ts +201 -54
- package/dist/api/LambderApiRateLimits.js +185 -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/ContractTypePrinter.d.ts +85 -0
- package/dist/build/ContractTypePrinter.js +402 -0
- package/dist/build/freshProcessVerifier.d.ts +13 -0
- package/dist/build/freshProcessVerifier.js +19 -0
- package/dist/build/moduleLocation.d.ts +11 -0
- package/dist/build/moduleLocation.js +6 -0
- package/dist/build/writeApiContract.d.ts +78 -0
- package/dist/build/writeApiContract.js +302 -0
- package/dist/build/writeApiSignatures.d.ts +114 -0
- package/dist/build/writeApiSignatures.js +217 -0
- package/dist/build/writeFileAtomically.d.ts +8 -0
- package/dist/build/writeFileAtomically.js +22 -0
- package/dist/build.d.ts +14 -0
- package/dist/build.js +11 -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/LambderUploadRunner.d.ts +96 -0
- package/dist/client/LambderUploadRunner.js +234 -0
- package/dist/client/lambderFetchTransport.d.ts +4 -1
- package/dist/client/lambderFetchTransport.js +52 -28
- package/dist/client.d.ts +9 -3
- package/dist/client.js +6 -1
- package/dist/core/Lambder.d.ts +143 -79
- package/dist/core/Lambder.js +350 -231
- 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 +21 -7
- package/dist/core/LambderFiles.js +62 -34
- 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 +25 -8
- package/dist/index.js +13 -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 +29 -9
- package/dist/invoke/LambderLambdaEvent.js +40 -22
- package/dist/invoke/lambderHandlerTransport.d.ts +9 -10
- package/dist/invoke/lambderHandlerTransport.js +15 -18
- 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 +43 -33
- package/dist/mock/lambderMockMswHandler.js +50 -39
- package/dist/mock/lambderMockUploadMswHandler.d.ts +26 -0
- package/dist/mock/lambderMockUploadMswHandler.js +28 -0
- package/dist/mock.d.ts +4 -1
- package/dist/mock.js +6 -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 +124 -46
- package/dist/session/LambderSessionManager.js +262 -137
- 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/contracts/LambderUploadBucket.d.ts +154 -0
- package/dist/shared/contracts/LambderUploadBucket.js +74 -0
- 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/LambderContentDisposition.d.ts +10 -0
- package/dist/shared/util/LambderContentDisposition.js +13 -0
- 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/LambderTextDigest.d.ts +7 -5
- package/dist/shared/util/LambderTextDigest.js +11 -5
- 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 +98 -53
- package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
- package/dist/shared/wire/LambderApiOutcome.js +48 -23
- package/dist/shared/wire/LambderApiRefusal.d.ts +45 -27
- package/dist/shared/wire/LambderApiRefusal.js +42 -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 +6 -7
- package/dist/shared/wire/LambderOutcomeAssertions.js +6 -7
- package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
- package/dist/shared/wire/LambderRequestPayload.js +4 -6
- package/dist/shared/wire/LambderUploadObjectFields.d.ts +10 -0
- package/dist/shared/wire/LambderUploadObjectFields.js +24 -0
- package/dist/shared/wire/LambderUploadRefusal.d.ts +9 -0
- package/dist/shared/wire/LambderUploadRefusal.js +18 -0
- package/dist/shared/wire/LambderUploadSchemas.d.ts +12 -0
- package/dist/shared/wire/LambderUploadSchemas.js +30 -0
- 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 +80 -38
- 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/LambderMemoryUploadBucket.d.ts +99 -0
- package/dist/stores/LambderMemoryUploadBucket.js +219 -0
- package/dist/stores/LambderS3FileSource.d.ts +21 -6
- package/dist/stores/LambderS3FileSource.js +12 -7
- package/dist/stores/LambderS3UploadBucket.d.ts +73 -0
- package/dist/stores/LambderS3UploadBucket.js +144 -0
- package/dist/stores/LambderSdkInstallHint.d.ts +11 -0
- package/dist/stores/LambderSdkInstallHint.js +14 -0
- package/dist/testing/LambderTestApp.d.ts +23 -25
- package/dist/testing/LambderTestApp.js +22 -24
- package/dist/testing/LambderTestVisitor.d.ts +10 -12
- package/dist/testing/LambderTestVisitor.js +15 -15
- package/dist/testing.d.ts +3 -0
- package/dist/testing.js +2 -0
- package/package.json +26 -3
- package/dist/api/LambderApiPolicyEngine.d.ts +0 -47
- package/dist/api/LambderApiPolicyEngine.js +0 -85
- package/dist/shared/util/LambderKeyFields.d.ts +0 -32
- package/dist/shared/util/LambderKeyFields.js +0 -34
|
@@ -3,9 +3,9 @@ export type LambderApiRefusalOptions = {
|
|
|
3
3
|
/**
|
|
4
4
|
* User-facing failure detail placed on the API envelope's `errorMessage`
|
|
5
5
|
* field: a refusal message (`{ type: "warning", content: "..." }`, with
|
|
6
|
-
* an app's own `code`) or a plain string
|
|
7
|
-
*
|
|
8
|
-
* visible to the client.
|
|
6
|
+
* an app's own `code`) or a plain string, which becomes an "error"
|
|
7
|
+
* message with that content. Defaults to the error message, so a bare
|
|
8
|
+
* `throw new LambderApiRefusal("...")` is still visible to the client.
|
|
9
9
|
*/
|
|
10
10
|
errorMessage?: LambderAppRefusalMessage | string;
|
|
11
11
|
/** Sets the envelope's `notAuthorized` flag (routed to the caller's notAuthorizedHandler). */
|
|
@@ -26,18 +26,18 @@ export type LambderApiRefusalOptions = {
|
|
|
26
26
|
/**
|
|
27
27
|
* A typed refusal: "this request is denied/invalid" as opposed to "the server
|
|
28
28
|
* crashed". Throw it from anywhere in an API call's call stack (handlers,
|
|
29
|
-
* hooks, or nested helpers
|
|
30
|
-
*
|
|
29
|
+
* hooks, or nested helpers with no access to the per-request resolver) and
|
|
30
|
+
* the render pipeline maps it onto the structured API envelope
|
|
31
31
|
* (`res.api(null, { errorMessage, notAuthorized, sessionExpired })`) instead
|
|
32
|
-
* of routing it through setGlobalErrorHandler
|
|
33
|
-
*
|
|
32
|
+
* of routing it through setGlobalErrorHandler, so refusals never reach crash
|
|
33
|
+
* logging and clients receive a parseable response.
|
|
34
34
|
*
|
|
35
35
|
* Thrown outside an API call (e.g. in a route handler) it behaves like any
|
|
36
36
|
* other error: global error handler, then the default 500.
|
|
37
37
|
*
|
|
38
38
|
* Isomorphic and dependency-free, so shared code (validators, permission
|
|
39
|
-
* checks)
|
|
40
|
-
* browser
|
|
39
|
+
* checks) used by both server and browser builds may throw it; in the
|
|
40
|
+
* browser it is just an Error.
|
|
41
41
|
*/
|
|
42
42
|
export declare class LambderApiRefusal extends Error {
|
|
43
43
|
/**
|
|
@@ -46,7 +46,8 @@ export declare class LambderApiRefusal extends Error {
|
|
|
46
46
|
* across them while this marker does not. The pipeline checks the brand.
|
|
47
47
|
*/
|
|
48
48
|
readonly isLambderApiRefusal = true;
|
|
49
|
-
|
|
49
|
+
/** What the envelope's errorMessage carries: always a message object, a plain string given to the options made into one. */
|
|
50
|
+
readonly errorMessage: LambderAppRefusalMessage;
|
|
50
51
|
readonly notAuthorized?: boolean;
|
|
51
52
|
readonly sessionExpired?: boolean;
|
|
52
53
|
readonly statusCode?: LambderHttpStatusCode;
|
|
@@ -59,11 +60,11 @@ export declare const isLambderApiRefusal: (err: unknown) => err is LambderApiRef
|
|
|
59
60
|
* The standard shape refusals carry on the envelope's errorMessage field.
|
|
60
61
|
* `code` is the refusal's machine-readable identity: clients branch and
|
|
61
62
|
* translate on it and never string-match `content`, which stays the
|
|
62
|
-
* human-readable fallback for codes a client does not know yet.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* as-is, typed as LambderAppRefusalMessage
|
|
66
|
-
*
|
|
63
|
+
* human-readable fallback for codes a client does not know yet. The
|
|
64
|
+
* framework's own refusals carry a LambderRefusalCode; apps put their own
|
|
65
|
+
* typed vocabulary in `code`. The caller's errorMessageHandler receives the
|
|
66
|
+
* object as-is, typed as LambderAppRefusalMessage; a server that wrote a
|
|
67
|
+
* plain string reaches it as `{ type: "error", content }` (refusalMessageOf).
|
|
67
68
|
*/
|
|
68
69
|
export type LambderRefusalMessage<TAppCode extends string = never> = {
|
|
69
70
|
type: "warning" | "error" | "info";
|
|
@@ -71,14 +72,13 @@ export type LambderRefusalMessage<TAppCode extends string = never> = {
|
|
|
71
72
|
* Machine-readable identity of the refusal: a LambderRefusalCode, plus
|
|
72
73
|
* whatever vocabulary the reader names in TAppCode.
|
|
73
74
|
*
|
|
74
|
-
* Parameterized rather than widened with `string & {}
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* where any code is welcome.
|
|
75
|
+
* Parameterized rather than widened with `string & {}`: a union with
|
|
76
|
+
* `string` in it does not narrow, so a `switch(message.code)` could not
|
|
77
|
+
* end in a `default: never` exhaustiveness check, which is what the codes
|
|
78
|
+
* exist for. A client that reads its own vocabulary declares it
|
|
79
|
+
* (`LambderRefusalMessage<"app/not-verified" | ...>`) and gets a checked
|
|
80
|
+
* switch; a value an app WRITES takes LambderAppRefusalMessage, where any
|
|
81
|
+
* code is welcome.
|
|
82
82
|
*/
|
|
83
83
|
code?: LambderRefusalCode | TAppCode;
|
|
84
84
|
title?: string;
|
|
@@ -86,12 +86,22 @@ export type LambderRefusalMessage<TAppCode extends string = never> = {
|
|
|
86
86
|
};
|
|
87
87
|
/**
|
|
88
88
|
* The refusal shape an app authors: any code, with the framework's own still
|
|
89
|
-
* autocompleting.
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
89
|
+
* autocompleting. Every option that takes a message from an app is typed as
|
|
90
|
+
* this (a rate-limit policy's errorMessage, the mock's failure injection);
|
|
91
|
+
* LambderRefusalMessage itself defaults to the framework's codes alone, so a
|
|
92
|
+
* reader's switch over it is exhaustive.
|
|
93
93
|
*/
|
|
94
94
|
export type LambderAppRefusalMessage = LambderRefusalMessage<string & {}>;
|
|
95
|
+
/**
|
|
96
|
+
* An errorMessage as the one shape a reader handles: a plain string becomes
|
|
97
|
+
* an "error" message with that content, and a value that is not a message
|
|
98
|
+
* at all becomes one describing it. The envelope writer applies it, so a
|
|
99
|
+
* Lambder server only ever sends the object. A reader applies it too,
|
|
100
|
+
* because what it reads is wire input no server vouches for: a hand-built
|
|
101
|
+
* mock answer (an MSW handler, a test double) or a proxy that wrote its own
|
|
102
|
+
* body can put anything there.
|
|
103
|
+
*/
|
|
104
|
+
export declare const refusalMessageOf: (message: unknown) => LambderAppRefusalMessage;
|
|
95
105
|
/**
|
|
96
106
|
* Codes the framework stamps on the refusals it authors itself, under the
|
|
97
107
|
* reserved `lambder/` prefix so app codes never collide. Compare against
|
|
@@ -103,6 +113,8 @@ export declare const LAMBDER_REFUSAL_CODES: {
|
|
|
103
113
|
readonly rateLimited: "lambder/rate-limited";
|
|
104
114
|
/** The original of an idempotent request is still processing (409). */
|
|
105
115
|
readonly duplicateInFlight: "lambder/duplicate-in-flight";
|
|
116
|
+
/** The idempotencyKey was already used for a request with a different payload (409). */
|
|
117
|
+
readonly idempotencyKeyReused: "lambder/idempotency-key-reused";
|
|
106
118
|
/** The idempotencyKey is malformed (400). */
|
|
107
119
|
readonly invalidIdempotencyKey: "lambder/invalid-idempotency-key";
|
|
108
120
|
/** No API is registered under the requested name. */
|
|
@@ -111,6 +123,12 @@ export declare const LAMBDER_REFUSAL_CODES: {
|
|
|
111
123
|
readonly invalidRequestPayload: "lambder/invalid-request-payload";
|
|
112
124
|
/** Only the mock runtime emits it: the endpoint is registered as not mocked, with a reason. */
|
|
113
125
|
readonly notMocked: "lambder/not-mocked";
|
|
126
|
+
/** An upload bucket would not sign a ticket for a file with no bytes. */
|
|
127
|
+
readonly uploadEmpty: "lambder/upload-empty";
|
|
128
|
+
/** An upload bucket would not sign a ticket for a content type the rule does not accept. */
|
|
129
|
+
readonly uploadTypeRejected: "lambder/upload-type-rejected";
|
|
130
|
+
/** An upload bucket would not sign a ticket for a file larger than the rule accepts. */
|
|
131
|
+
readonly uploadTooLarge: "lambder/upload-too-large";
|
|
114
132
|
};
|
|
115
133
|
export type LambderRefusalCode = (typeof LAMBDER_REFUSAL_CODES)[keyof typeof LAMBDER_REFUSAL_CODES];
|
|
116
134
|
export type LambderRefuseOptions = {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A typed refusal: "this request is denied/invalid" as opposed to "the server
|
|
3
3
|
* crashed". Throw it from anywhere in an API call's call stack (handlers,
|
|
4
|
-
* hooks, or nested helpers
|
|
5
|
-
*
|
|
4
|
+
* hooks, or nested helpers with no access to the per-request resolver) and
|
|
5
|
+
* the render pipeline maps it onto the structured API envelope
|
|
6
6
|
* (`res.api(null, { errorMessage, notAuthorized, sessionExpired })`) instead
|
|
7
|
-
* of routing it through setGlobalErrorHandler
|
|
8
|
-
*
|
|
7
|
+
* of routing it through setGlobalErrorHandler, so refusals never reach crash
|
|
8
|
+
* logging and clients receive a parseable response.
|
|
9
9
|
*
|
|
10
10
|
* Thrown outside an API call (e.g. in a route handler) it behaves like any
|
|
11
11
|
* other error: global error handler, then the default 500.
|
|
12
12
|
*
|
|
13
13
|
* Isomorphic and dependency-free, so shared code (validators, permission
|
|
14
|
-
* checks)
|
|
15
|
-
* browser
|
|
14
|
+
* checks) used by both server and browser builds may throw it; in the
|
|
15
|
+
* browser it is just an Error.
|
|
16
16
|
*/
|
|
17
17
|
export class LambderApiRefusal extends Error {
|
|
18
18
|
/**
|
|
@@ -21,6 +21,7 @@ export class LambderApiRefusal extends Error {
|
|
|
21
21
|
* across them while this marker does not. The pipeline checks the brand.
|
|
22
22
|
*/
|
|
23
23
|
isLambderApiRefusal = true;
|
|
24
|
+
/** What the envelope's errorMessage carries: always a message object, a plain string given to the options made into one. */
|
|
24
25
|
errorMessage;
|
|
25
26
|
notAuthorized;
|
|
26
27
|
sessionExpired;
|
|
@@ -29,7 +30,7 @@ export class LambderApiRefusal extends Error {
|
|
|
29
30
|
constructor(message, options = {}) {
|
|
30
31
|
super(message, options.cause !== undefined ? { cause: options.cause } : undefined);
|
|
31
32
|
this.name = "LambderApiRefusal";
|
|
32
|
-
this.errorMessage = options.errorMessage ?? message;
|
|
33
|
+
this.errorMessage = refusalMessageOf(options.errorMessage ?? message);
|
|
33
34
|
this.notAuthorized = options.notAuthorized;
|
|
34
35
|
this.sessionExpired = options.sessionExpired;
|
|
35
36
|
this.statusCode = options.statusCode;
|
|
@@ -38,6 +39,32 @@ export class LambderApiRefusal extends Error {
|
|
|
38
39
|
}
|
|
39
40
|
/** Brand-based type guard (see LambderApiRefusal.isLambderApiRefusal). */
|
|
40
41
|
export const isLambderApiRefusal = (err) => err instanceof Error && err.isLambderApiRefusal === true;
|
|
42
|
+
const REFUSAL_MESSAGE_TYPES = ["warning", "error", "info"];
|
|
43
|
+
/**
|
|
44
|
+
* An errorMessage as the one shape a reader handles: a plain string becomes
|
|
45
|
+
* an "error" message with that content, and a value that is not a message
|
|
46
|
+
* at all becomes one describing it. The envelope writer applies it, so a
|
|
47
|
+
* Lambder server only ever sends the object. A reader applies it too,
|
|
48
|
+
* because what it reads is wire input no server vouches for: a hand-built
|
|
49
|
+
* mock answer (an MSW handler, a test double) or a proxy that wrote its own
|
|
50
|
+
* body can put anything there.
|
|
51
|
+
*/
|
|
52
|
+
export const refusalMessageOf = (message) => {
|
|
53
|
+
if (message !== null && typeof message === "object" && typeof message.content === "string") {
|
|
54
|
+
const candidate = message;
|
|
55
|
+
return REFUSAL_MESSAGE_TYPES.includes(candidate.type) ? candidate : { ...candidate, type: "error" };
|
|
56
|
+
}
|
|
57
|
+
if (typeof message === "string")
|
|
58
|
+
return { type: "error", content: message };
|
|
59
|
+
let content;
|
|
60
|
+
try {
|
|
61
|
+
content = JSON.stringify(message) ?? String(message);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
content = String(message);
|
|
65
|
+
}
|
|
66
|
+
return { type: "error", content };
|
|
67
|
+
};
|
|
41
68
|
/**
|
|
42
69
|
* Codes the framework stamps on the refusals it authors itself, under the
|
|
43
70
|
* reserved `lambder/` prefix so app codes never collide. Compare against
|
|
@@ -49,6 +76,8 @@ export const LAMBDER_REFUSAL_CODES = {
|
|
|
49
76
|
rateLimited: "lambder/rate-limited",
|
|
50
77
|
/** The original of an idempotent request is still processing (409). */
|
|
51
78
|
duplicateInFlight: "lambder/duplicate-in-flight",
|
|
79
|
+
/** The idempotencyKey was already used for a request with a different payload (409). */
|
|
80
|
+
idempotencyKeyReused: "lambder/idempotency-key-reused",
|
|
52
81
|
/** The idempotencyKey is malformed (400). */
|
|
53
82
|
invalidIdempotencyKey: "lambder/invalid-idempotency-key",
|
|
54
83
|
/** No API is registered under the requested name. */
|
|
@@ -57,6 +86,12 @@ export const LAMBDER_REFUSAL_CODES = {
|
|
|
57
86
|
invalidRequestPayload: "lambder/invalid-request-payload",
|
|
58
87
|
/** Only the mock runtime emits it: the endpoint is registered as not mocked, with a reason. */
|
|
59
88
|
notMocked: "lambder/not-mocked",
|
|
89
|
+
/** An upload bucket would not sign a ticket for a file with no bytes. */
|
|
90
|
+
uploadEmpty: "lambder/upload-empty",
|
|
91
|
+
/** An upload bucket would not sign a ticket for a content type the rule does not accept. */
|
|
92
|
+
uploadTypeRejected: "lambder/upload-type-rejected",
|
|
93
|
+
/** An upload bucket would not sign a ticket for a file larger than the rule accepts. */
|
|
94
|
+
uploadTooLarge: "lambder/upload-too-large",
|
|
60
95
|
};
|
|
61
96
|
/**
|
|
62
97
|
* Refuse the current API call: a routine business "no" (not found, invalid
|
|
@@ -6,9 +6,8 @@ import type { z } from "zod";
|
|
|
6
6
|
* the digest of its client-facing shape (apiSignatureOf, computed on the
|
|
7
7
|
* server side). A caller given the map sends the value with every call, and
|
|
8
8
|
* the server answers versionExpired when it differs from the digest of what
|
|
9
|
-
* it serves
|
|
10
|
-
*
|
|
11
|
-
* across deploys.
|
|
9
|
+
* it serves, so a client reloads only when an endpoint it calls has changed
|
|
10
|
+
* and otherwise keeps working across deploys.
|
|
12
11
|
*
|
|
13
12
|
* Keys are hashed so the map lists no endpoint names: the names a client
|
|
14
13
|
* calls are in its own code already, and the rest of the surface stays out
|
|
@@ -26,14 +25,12 @@ export declare const API_SIGNATURE_HEX_LENGTH = 16;
|
|
|
26
25
|
* name, cut to API_SIGNATURE_HEX_LENGTH hex characters. Async because
|
|
27
26
|
* WebCrypto's digest is, and it is the only SHA-256 a browser has.
|
|
28
27
|
*
|
|
29
|
-
* Computed on the spot
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* would grow by an entry for every name a request cared to invent and never
|
|
36
|
-
* shrink.
|
|
28
|
+
* Computed on the spot every time, with nothing kept. The digest that
|
|
29
|
+
* describes an endpoint is the generator's, computed once at build time;
|
|
30
|
+
* this is one hash of a short name, nothing beside the request it belongs
|
|
31
|
+
* to. A cache would be keyed by name, and on the server the name comes off
|
|
32
|
+
* the wire before anything checks that it is an endpoint, so the cache would
|
|
33
|
+
* gain an entry for every name a request cared to invent and never shrink.
|
|
37
34
|
*/
|
|
38
35
|
export declare const apiNameKeyOf: (apiName: string) => Promise<string>;
|
|
39
36
|
/** The map's signature for one endpoint, or null when the map holds none for it. */
|
|
@@ -59,23 +56,22 @@ export declare const EXTENSIBLE_ENUM_META_KEY = "x-lambder-extensible-enum";
|
|
|
59
56
|
* widely returned payload (a session, a profile) then reloads only the
|
|
60
57
|
* clients that send the list back, not every client that reads it.
|
|
61
58
|
*
|
|
62
|
-
* Where the enum is input its values still count:
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
59
|
+
* Where the enum is input its values still count: an older client may still
|
|
60
|
+
* send a value dropped from the list, which the server would refuse, so that
|
|
61
|
+
* endpoint's clients must reload. Everything else about the schema is
|
|
62
|
+
* untouched: its type, its validation on both sides, and what it is outside
|
|
63
|
+
* the digest.
|
|
67
64
|
*
|
|
68
65
|
* The mark is a promise the schema makes for its readers, and nothing checks
|
|
69
66
|
* it. A client that switches over every value with no fallback, or indexes a
|
|
70
|
-
* map by one, renders
|
|
71
|
-
*
|
|
67
|
+
* map by one, renders an unknown value as nothing, or throws. Mark only a
|
|
68
|
+
* list whose every reader handles an unknown value on purpose.
|
|
72
69
|
*
|
|
73
70
|
* It is zod metadata (`.meta()`), which zod keeps in one registry on
|
|
74
71
|
* globalThis, so an enum marked in a shared package is read by the digest
|
|
75
|
-
* even when the server resolves another copy of zod. A schema
|
|
76
|
-
* marked enum
|
|
77
|
-
*
|
|
78
|
-
* one.
|
|
72
|
+
* even when the server resolves another copy of zod. A schema rebuilt from a
|
|
73
|
+
* marked enum (`z.enum(marked.options)`, `.exclude()`) carries no mark and
|
|
74
|
+
* counts in full, which costs a reload, never a missed one.
|
|
79
75
|
*
|
|
80
76
|
* @example
|
|
81
77
|
* export const RoleSchema = extensibleEnum(z.enum(["admin", "member"]));
|
|
@@ -12,14 +12,12 @@ const API_NAME_KEY_PREFIX = "lambder-api-name:";
|
|
|
12
12
|
* name, cut to API_SIGNATURE_HEX_LENGTH hex characters. Async because
|
|
13
13
|
* WebCrypto's digest is, and it is the only SHA-256 a browser has.
|
|
14
14
|
*
|
|
15
|
-
* Computed on the spot
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* would grow by an entry for every name a request cared to invent and never
|
|
22
|
-
* shrink.
|
|
15
|
+
* Computed on the spot every time, with nothing kept. The digest that
|
|
16
|
+
* describes an endpoint is the generator's, computed once at build time;
|
|
17
|
+
* this is one hash of a short name, nothing beside the request it belongs
|
|
18
|
+
* to. A cache would be keyed by name, and on the server the name comes off
|
|
19
|
+
* the wire before anything checks that it is an endpoint, so the cache would
|
|
20
|
+
* gain an entry for every name a request cared to invent and never shrink.
|
|
23
21
|
*/
|
|
24
22
|
export const apiNameKeyOf = async (apiName) => (await sha256HexOf(API_NAME_KEY_PREFIX + apiName)).slice(0, API_SIGNATURE_HEX_LENGTH);
|
|
25
23
|
/** The map's signature for one endpoint, or null when the map holds none for it. */
|
|
@@ -57,23 +55,22 @@ export const EXTENSIBLE_ENUM_META_KEY = "x-lambder-extensible-enum";
|
|
|
57
55
|
* widely returned payload (a session, a profile) then reloads only the
|
|
58
56
|
* clients that send the list back, not every client that reads it.
|
|
59
57
|
*
|
|
60
|
-
* Where the enum is input its values still count:
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
58
|
+
* Where the enum is input its values still count: an older client may still
|
|
59
|
+
* send a value dropped from the list, which the server would refuse, so that
|
|
60
|
+
* endpoint's clients must reload. Everything else about the schema is
|
|
61
|
+
* untouched: its type, its validation on both sides, and what it is outside
|
|
62
|
+
* the digest.
|
|
65
63
|
*
|
|
66
64
|
* The mark is a promise the schema makes for its readers, and nothing checks
|
|
67
65
|
* it. A client that switches over every value with no fallback, or indexes a
|
|
68
|
-
* map by one, renders
|
|
69
|
-
*
|
|
66
|
+
* map by one, renders an unknown value as nothing, or throws. Mark only a
|
|
67
|
+
* list whose every reader handles an unknown value on purpose.
|
|
70
68
|
*
|
|
71
69
|
* It is zod metadata (`.meta()`), which zod keeps in one registry on
|
|
72
70
|
* globalThis, so an enum marked in a shared package is read by the digest
|
|
73
|
-
* even when the server resolves another copy of zod. A schema
|
|
74
|
-
* marked enum
|
|
75
|
-
*
|
|
76
|
-
* one.
|
|
71
|
+
* even when the server resolves another copy of zod. A schema rebuilt from a
|
|
72
|
+
* marked enum (`z.enum(marked.options)`, `.exclude()`) carries no mark and
|
|
73
|
+
* counts in full, which costs a reload, never a missed one.
|
|
77
74
|
*
|
|
78
75
|
* @example
|
|
79
76
|
* export const RoleSchema = extensibleEnum(z.enum(["admin", "member"]));
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The per-call options
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* it.
|
|
2
|
+
* The per-call options, the contract-driven typing of a call's arguments, and
|
|
3
|
+
* the runtime merge of guard inputs, shared by the browser caller
|
|
4
|
+
* (LambderCaller) and the server-side invoke caller (LambderInvokeCaller).
|
|
5
|
+
* Both speak the same envelope to the same kind of contract, so what an API
|
|
6
|
+
* demands of its caller (a guardInput-mode guard's value, say) is decided
|
|
7
|
+
* here once and the two cannot drift. Pure types and one dependency-free
|
|
8
|
+
* function, so the browser entry resolves it.
|
|
10
9
|
*/
|
|
11
10
|
import type { LambderContractIdempotencyOf } from "./LambderApiContract.js";
|
|
11
|
+
import type { LambderIdempotencyKeyScope } from "./LambderIdempotencyKeyScope.js";
|
|
12
12
|
type IsAny<T> = 0 extends (1 & T) ? true : false;
|
|
13
13
|
/**
|
|
14
14
|
* The options every call takes, whichever caller sends it. Each caller adds
|
|
@@ -40,19 +40,22 @@ export type LambderSharedCallOptions = {
|
|
|
40
40
|
/**
|
|
41
41
|
* Replay-protection key for APIs declared idempotent on the server.
|
|
42
42
|
* Generate once per logical operation with
|
|
43
|
-
*
|
|
43
|
+
* createIdempotencyKey() and send the same key on retries:
|
|
44
44
|
* duplicates of an in-flight request refuse, and repeats of a completed
|
|
45
45
|
* one replay its stored response instead of re-executing. Must be
|
|
46
46
|
* UNGUESSABLE random (it scopes the replay record for logged-out clients)
|
|
47
47
|
* and at least 16 characters; the server refuses shorter keys with a 400.
|
|
48
48
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
49
|
+
* REQUIRED by the typed contract for an API that declares `idempotency`,
|
|
50
|
+
* since the server runs a keyless call unprotected.
|
|
51
|
+
*
|
|
52
|
+
* A key scope (createIdempotencyKeyScope()) is the easier
|
|
53
|
+
* form: it rotates once an answer settles the operation, so a retry after
|
|
54
|
+
* a dropped connection reuses the key and the next attempt (a corrected
|
|
55
|
+
* form after a refusal included) gets a new one. See
|
|
56
|
+
* LambderIdempotencyKeyScope for which answers settle it.
|
|
54
57
|
*/
|
|
55
|
-
idempotencyKey?: string;
|
|
58
|
+
idempotencyKey?: string | LambderIdempotencyKeyScope;
|
|
56
59
|
};
|
|
57
60
|
/** The payload type one API of a contract takes; `any` for an untyped caller or a name the contract does not know. */
|
|
58
61
|
type LambderContractInputOf<TContract, TApiName> = IsAny<TContract> extends true ? any : TApiName extends keyof TContract ? TContract[TApiName] extends {
|
|
@@ -80,12 +83,12 @@ export type LambderProvidedGuardInputs<TContract, TProvided extends string> = Is
|
|
|
80
83
|
};
|
|
81
84
|
/**
|
|
82
85
|
* Supplies guardInputs for every call from one place (the organization the
|
|
83
|
-
* UI is on, a device token), keyed by guard name; per-call guardInputs
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
86
|
+
* UI is on, a device token), keyed by guard name; per-call guardInputs merge
|
|
87
|
+
* on top. Name the guards it covers in the caller's second type parameter,
|
|
88
|
+
* `new LambderCaller<Contract, "orgPermission">`, and calls to APIs whose
|
|
89
|
+
* guardInput guards are all covered do not require the options argument.
|
|
90
|
+
* May be async; a throw fails the call as an unknown error before anything
|
|
91
|
+
* is sent.
|
|
89
92
|
*/
|
|
90
93
|
export type LambderGuardInputsProvider<TContract, TProvided extends string> = (apiName: keyof TContract & string) => LambderProvidedGuardInputs<TContract, TProvided> | Promise<LambderProvidedGuardInputs<TContract, TProvided>>;
|
|
91
94
|
/** Optional until the caller names provided guards: naming them without a provider would send nothing. */
|
|
@@ -117,11 +120,10 @@ type ContractGuardInputsField<TEntry, TProvided extends string> = [
|
|
|
117
120
|
*
|
|
118
121
|
* Read as "required unless it says false" rather than "required only when it
|
|
119
122
|
* says true", so an entry whose option widened to `boolean` (declared through
|
|
120
|
-
* a spread, or built in a helper) keeps the requirement
|
|
121
|
-
* losing its compile-time half.
|
|
123
|
+
* a spread, or built in a helper) keeps the requirement at compile time.
|
|
122
124
|
*/
|
|
123
125
|
type ContractIdempotencyKeyField<TContract, TApiName> = TApiName extends keyof TContract ? [LambderContractIdempotencyOf<TContract, TApiName>] extends [never] ? {} : [LambderContractIdempotencyOf<TContract, TApiName>] extends [false] ? {} : {
|
|
124
|
-
idempotencyKey: string;
|
|
126
|
+
idempotencyKey: string | LambderIdempotencyKeyScope;
|
|
125
127
|
} : {};
|
|
126
128
|
/**
|
|
127
129
|
* What the contract adds to one call's options: the guardInputs field and the
|
|
@@ -131,41 +133,30 @@ type ContractCallFields<TContract, TApiName, TProvided extends string> = TApiNam
|
|
|
131
133
|
/**
|
|
132
134
|
* The options argument of one call: optional normally, REQUIRED when the
|
|
133
135
|
* contract demands something of it, so forgetting a guard's value or an
|
|
134
|
-
* idempotent API's key is a compile error
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* `{} extends TFields` is the question "is every field the contract added
|
|
139
|
-
* optional": an empty object is assignable to a type whose properties are all
|
|
140
|
-
* optional and to nothing else.
|
|
136
|
+
* idempotent API's key is a compile error rather than a 422 or a replay that
|
|
137
|
+
* never happens. TOptions is the caller's own per-call options type.
|
|
138
|
+
* `{} extends TFields` asks "is every field the contract added optional".
|
|
141
139
|
*/
|
|
142
140
|
type LambderCallOptionsArg<TContract, TApiName, TProvided extends string, TOptions extends {
|
|
143
141
|
guardInputs?: Record<string, unknown>;
|
|
144
142
|
}> = IsAny<TContract> extends true ? [options?: TOptions] : TApiName extends keyof TContract ? ContractCallFields<TContract, TApiName, TProvided> extends infer TFields ? {} extends TFields ? [options?: TOptions & TFields] : [options: TOptions & TFields] : never : [options?: TOptions];
|
|
145
143
|
/**
|
|
146
144
|
* Everything one call passes after the API name: the payload, then the
|
|
147
|
-
* options, both decided by the contract.
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* conditional otherwise.
|
|
155
|
-
*
|
|
156
|
-
* When the options argument is itself mandatory (an uncovered guardInput
|
|
157
|
-
* guard, an idempotent API's key), the payload cannot stay optional in front
|
|
158
|
-
* of it, since a tuple's required element may not follow an optional one.
|
|
159
|
-
* Such a call passes its payload explicitly, `undefined` included.
|
|
145
|
+
* options, both decided by the contract. The payload is optional only when
|
|
146
|
+
* the API's input accepts undefined, so `caller.api("getUser")` against
|
|
147
|
+
* `input: { id: string }` fails to compile rather than drawing a 422. That
|
|
148
|
+
* needs one rest tuple, since TypeScript has no other per-argument
|
|
149
|
+
* conditional. When the options are mandatory, the payload cannot stay
|
|
150
|
+
* optional before them (a tuple's required element may not follow an
|
|
151
|
+
* optional one), so such a call passes it explicitly, `undefined` included.
|
|
160
152
|
*/
|
|
161
153
|
export type LambderCallArgs<TContract, TApiName, TProvided extends string, TOptions extends {
|
|
162
154
|
guardInputs?: Record<string, unknown>;
|
|
163
155
|
}> = LambderCallOptionsArg<TContract, TApiName, TProvided, TOptions> extends [options: infer TRequired] ? [payload: LambderContractInputOf<TContract, TApiName>, options: TRequired] : LambderCallOptionsArg<TContract, TApiName, TProvided, TOptions> extends [options?: infer TOptional] ? undefined extends LambderContractInputOf<TContract, TApiName> ? [payload?: LambderContractInputOf<TContract, TApiName>, options?: TOptional] : [payload: LambderContractInputOf<TContract, TApiName>, options?: TOptional] : never;
|
|
164
156
|
/**
|
|
165
157
|
* Provider values underneath, per-call values on top; undefined when neither
|
|
166
|
-
* side supplied any. Synchronous
|
|
167
|
-
*
|
|
168
|
-
* request in the same tick it was made.
|
|
158
|
+
* side supplied any. Synchronous so a call without a provider still issues
|
|
159
|
+
* its request in the same tick it was made.
|
|
169
160
|
*/
|
|
170
161
|
export declare const mergeGuardInputs: (provided: Record<string, unknown> | undefined, perCall: Record<string, unknown> | undefined) => Record<string, unknown> | undefined;
|
|
171
162
|
export {};
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The per-call options
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* it.
|
|
2
|
+
* The per-call options, the contract-driven typing of a call's arguments, and
|
|
3
|
+
* the runtime merge of guard inputs, shared by the browser caller
|
|
4
|
+
* (LambderCaller) and the server-side invoke caller (LambderInvokeCaller).
|
|
5
|
+
* Both speak the same envelope to the same kind of contract, so what an API
|
|
6
|
+
* demands of its caller (a guardInput-mode guard's value, say) is decided
|
|
7
|
+
* here once and the two cannot drift. Pure types and one dependency-free
|
|
8
|
+
* function, so the browser entry resolves it.
|
|
10
9
|
*/
|
|
11
10
|
/**
|
|
12
11
|
* Provider values underneath, per-call values on top; undefined when neither
|
|
13
|
-
* side supplied any. Synchronous
|
|
14
|
-
*
|
|
15
|
-
* request in the same tick it was made.
|
|
12
|
+
* side supplied any. Synchronous so a call without a provider still issues
|
|
13
|
+
* its request in the same tick it was made.
|
|
16
14
|
*/
|
|
17
15
|
export const mergeGuardInputs = (provided, perCall) => provided !== undefined || perCall !== undefined ? { ...provided, ...perCall } : undefined;
|