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
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, decompressPayloadGzip } from '../shared/LambderRequestPayload.js';
|
|
2
|
-
export default class LambderMSW {
|
|
3
|
-
apiPath;
|
|
4
|
-
apiVersion;
|
|
5
|
-
http;
|
|
6
|
-
HttpResponse;
|
|
7
|
-
constructor({ apiPath, apiVersion, msw, }) {
|
|
8
|
-
this.apiPath = apiPath;
|
|
9
|
-
this.apiVersion = apiVersion;
|
|
10
|
-
if (!msw?.http || !msw?.HttpResponse) {
|
|
11
|
-
throw new Error('LambderMSW requires the msw module: new LambderMSW({ apiPath, msw: await import("msw") }). Install it with: npm install msw --save-dev');
|
|
12
|
-
}
|
|
13
|
-
this.http = msw.http;
|
|
14
|
-
this.HttpResponse = msw.HttpResponse;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Mock an API endpoint with MSW
|
|
18
|
-
* @param apiName - The name of the API to mock
|
|
19
|
-
* @param handler - Function that returns the mock payload
|
|
20
|
-
* @param options - Additional response options (session expired, version expired, etc.)
|
|
21
|
-
*
|
|
22
|
-
* Note: TypeScript will check that the return type is assignable to the output type,
|
|
23
|
-
* but due to structural typing, extra properties are allowed. Enable strict checks
|
|
24
|
-
* in your tsconfig.json with "noUncheckedIndexedAccess" and "exactOptionalPropertyTypes"
|
|
25
|
-
* for better type safety.
|
|
26
|
-
*/
|
|
27
|
-
mockApi(apiName, handler, options) {
|
|
28
|
-
return this.http.post(this.apiPath, async ({ request }) => {
|
|
29
|
-
let body;
|
|
30
|
-
try {
|
|
31
|
-
const clonedRequest = request.clone();
|
|
32
|
-
body = await clonedRequest.json();
|
|
33
|
-
}
|
|
34
|
-
catch (parseErr) {
|
|
35
|
-
// If JSON parsing fails, return undefined to let other handlers try
|
|
36
|
-
console.warn("LambderMSW: Failed to parse request body as JSON");
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
// Check if body is valid and has apiName
|
|
40
|
-
if (!body || typeof body.apiName !== 'string') {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (body.apiName !== apiName) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
// A caller with requestCompression on sends the payload gzipped;
|
|
47
|
-
// mock handlers still receive the payload itself, and the wire
|
|
48
|
-
// fields are consumed the way the server consumes them.
|
|
49
|
-
if (typeof body[COMPRESSED_PAYLOAD_GZ_FIELD] === 'string') {
|
|
50
|
-
try {
|
|
51
|
-
body.payload = await decompressPayloadGzip(body[COMPRESSED_PAYLOAD_GZ_FIELD]);
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
console.warn("LambderMSW: Failed to decompress the request payload");
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
delete body[COMPRESSED_PAYLOAD_GZ_FIELD];
|
|
58
|
-
delete body[COMPRESSED_PAYLOAD_BYTES_FIELD];
|
|
59
|
-
}
|
|
60
|
-
try {
|
|
61
|
-
// Add artificial delay if specified
|
|
62
|
-
if (options?.delay) {
|
|
63
|
-
await new Promise(resolve => setTimeout(resolve, options.delay));
|
|
64
|
-
}
|
|
65
|
-
// Call the handler with the payload from the request
|
|
66
|
-
const payload = await handler(body.payload);
|
|
67
|
-
const response = {
|
|
68
|
-
apiVersion: this.apiVersion,
|
|
69
|
-
payload,
|
|
70
|
-
...(options?.versionExpired ? { versionExpired: options.versionExpired } : {}),
|
|
71
|
-
...(options?.sessionExpired ? { sessionExpired: options.sessionExpired } : {}),
|
|
72
|
-
...(options?.notAuthorized ? { notAuthorized: options.notAuthorized } : {}),
|
|
73
|
-
...(options?.message ? { message: options.message } : {}),
|
|
74
|
-
...(options?.errorMessage ? { errorMessage: options.errorMessage } : {}),
|
|
75
|
-
...(options?.logList?.length ? { logList: options.logList } : {}),
|
|
76
|
-
};
|
|
77
|
-
return this.HttpResponse.json(response);
|
|
78
|
-
}
|
|
79
|
-
catch (err) {
|
|
80
|
-
// Only handle errors that occur during handler execution for matched APIs
|
|
81
|
-
console.error("Error in LambderMSW handler for", apiName, ":", err);
|
|
82
|
-
const errorResponse = {
|
|
83
|
-
apiVersion: this.apiVersion,
|
|
84
|
-
payload: null,
|
|
85
|
-
errorMessage: err.message || "Unknown error",
|
|
86
|
-
};
|
|
87
|
-
return this.HttpResponse.json(errorResponse, { status: 500 });
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Mock an API endpoint that returns a session expired error
|
|
93
|
-
*/
|
|
94
|
-
mockSessionExpired(apiName) {
|
|
95
|
-
return this.mockApi(apiName, async () => null, { sessionExpired: true });
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Mock an API endpoint that returns a version expired error
|
|
99
|
-
*/
|
|
100
|
-
mockVersionExpired(apiName) {
|
|
101
|
-
return this.mockApi(apiName, async () => null, { versionExpired: true });
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Mock an API endpoint that returns a not authorized error
|
|
105
|
-
*/
|
|
106
|
-
mockNotAuthorized(apiName) {
|
|
107
|
-
return this.mockApi(apiName, async () => null, { notAuthorized: true });
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Mock an API endpoint that returns an error message
|
|
111
|
-
*/
|
|
112
|
-
mockError(apiName, errorMessage) {
|
|
113
|
-
return this.mockApi(apiName, async () => null, { errorMessage });
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Mock an API endpoint with a custom message
|
|
117
|
-
*/
|
|
118
|
-
mockWithMessage(apiName, handler, message) {
|
|
119
|
-
return this.mockApi(apiName, handler, { message });
|
|
120
|
-
}
|
|
121
|
-
}
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import type { z } from "zod";
|
|
2
|
-
import type { LambderRenderContext, LambderSessionRenderContext } from "../core/LambderContext.js";
|
|
3
|
-
import type LambderResolver from "../core/LambderResolver.js";
|
|
4
|
-
import type { LambderResponse } from "../core/LambderResponse.js";
|
|
5
|
-
/**
|
|
6
|
-
* A named guard, run before the API's own input validation. Three input
|
|
7
|
-
* modes:
|
|
8
|
-
*
|
|
9
|
-
* - `apiInput`: the guard checks fields of the API's OWN payload. The slice
|
|
10
|
-
* is validated against the raw payload before `handler` runs and handed to
|
|
11
|
-
* it typed. The API's input schema stays the owner of those fields:
|
|
12
|
-
* declaring the guard on an API whose schema does not carry them is a
|
|
13
|
-
* compile error.
|
|
14
|
-
* - `guardInput`: the guard has its own value the client sends SEPARATELY,
|
|
15
|
-
* outside the API payload, via the caller's options.guardInputs[name].
|
|
16
|
-
* The requirement lands on the API's contract (`guardInputs`), so the
|
|
17
|
-
* typed caller refuses to compile a call that does not send it. The API
|
|
18
|
-
* payload and handler never see the value.
|
|
19
|
-
* - neither: the guard reads only the context.
|
|
20
|
-
*
|
|
21
|
-
* Orthogonally, a guard may also:
|
|
22
|
-
*
|
|
23
|
-
* - declare `session: true`: the guard needs ctx.session, so it is only
|
|
24
|
-
* declarable on addSessionApi (compile error and startup assert on public
|
|
25
|
-
* APIs) and its handler receives the session-typed context.
|
|
26
|
-
* - take a PARAMETER: annotate a 4th handler argument
|
|
27
|
-
* (`(ctx, payload, res, param: YourType) => ...`) and APIs pass the value
|
|
28
|
-
* in their declaration: `guards: { yourGuard: paramValue }`. The value is
|
|
29
|
-
* trusted registration-time code (never client data), typed per guard.
|
|
30
|
-
* - RETURN a value: whatever the handler returns (awaited) is attached to
|
|
31
|
-
* the API handler's context as `ctx.guardData[guardName]`, fully typed.
|
|
32
|
-
* Guards that return nothing never appear in guardData.
|
|
33
|
-
*
|
|
34
|
-
* A validation failure answers like the API's own input validation (the
|
|
35
|
-
* app's setApiInputValidationErrorHandler when set, else the standard 422),
|
|
36
|
-
* and the handler refuses by throwing (typically refuse()/LambderApiError).
|
|
37
|
-
* Build with lambderGuard() so the handler's payload/ctx/param types line up.
|
|
38
|
-
*/
|
|
39
|
-
export type LambderApiGuard<TInput extends z.ZodType = z.ZodType, TParam = any, TOutput = any> = {
|
|
40
|
-
apiInput: TInput;
|
|
41
|
-
guardInput?: undefined;
|
|
42
|
-
session?: boolean;
|
|
43
|
-
handler: (ctx: any, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
44
|
-
} | {
|
|
45
|
-
guardInput: TInput;
|
|
46
|
-
apiInput?: undefined;
|
|
47
|
-
session?: boolean;
|
|
48
|
-
handler: (ctx: any, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
49
|
-
} | {
|
|
50
|
-
apiInput?: undefined;
|
|
51
|
-
guardInput?: undefined;
|
|
52
|
-
session?: boolean;
|
|
53
|
-
handler: (ctx: any, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
54
|
-
};
|
|
55
|
-
type GuardCtx = LambderRenderContext;
|
|
56
|
-
type GuardSessionCtx = LambderSessionRenderContext<any, any>;
|
|
57
|
-
/**
|
|
58
|
-
* Builder that ties the handler's payload, context, param, and output types
|
|
59
|
-
* together inside one literal. Returns the exact shape so type extraction
|
|
60
|
-
* (mode, session, param, output) works downstream. The param type is
|
|
61
|
-
* inferred from the handler's 4th argument annotation; the output from its
|
|
62
|
-
* return type.
|
|
63
|
-
*/
|
|
64
|
-
export declare function lambderGuard<TInput extends z.ZodType, TParam = undefined, TOutput = void>(guard: {
|
|
65
|
-
apiInput: TInput;
|
|
66
|
-
session: true;
|
|
67
|
-
handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
68
|
-
}): {
|
|
69
|
-
apiInput: TInput;
|
|
70
|
-
guardInput?: undefined;
|
|
71
|
-
session: true;
|
|
72
|
-
handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
73
|
-
};
|
|
74
|
-
export declare function lambderGuard<TInput extends z.ZodType, TParam = undefined, TOutput = void>(guard: {
|
|
75
|
-
apiInput: TInput;
|
|
76
|
-
handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
77
|
-
}): {
|
|
78
|
-
apiInput: TInput;
|
|
79
|
-
guardInput?: undefined;
|
|
80
|
-
session?: undefined;
|
|
81
|
-
handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
82
|
-
};
|
|
83
|
-
export declare function lambderGuard<TInput extends z.ZodType, TParam = undefined, TOutput = void>(guard: {
|
|
84
|
-
guardInput: TInput;
|
|
85
|
-
session: true;
|
|
86
|
-
handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
87
|
-
}): {
|
|
88
|
-
guardInput: TInput;
|
|
89
|
-
apiInput?: undefined;
|
|
90
|
-
session: true;
|
|
91
|
-
handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
92
|
-
};
|
|
93
|
-
export declare function lambderGuard<TInput extends z.ZodType, TParam = undefined, TOutput = void>(guard: {
|
|
94
|
-
guardInput: TInput;
|
|
95
|
-
handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
96
|
-
}): {
|
|
97
|
-
guardInput: TInput;
|
|
98
|
-
apiInput?: undefined;
|
|
99
|
-
session?: undefined;
|
|
100
|
-
handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
101
|
-
};
|
|
102
|
-
export declare function lambderGuard<TParam = undefined, TOutput = void>(guard: {
|
|
103
|
-
session: true;
|
|
104
|
-
handler: (ctx: GuardSessionCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
105
|
-
}): {
|
|
106
|
-
apiInput?: undefined;
|
|
107
|
-
guardInput?: undefined;
|
|
108
|
-
session: true;
|
|
109
|
-
handler: (ctx: GuardSessionCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
110
|
-
};
|
|
111
|
-
export declare function lambderGuard<TParam = undefined, TOutput = void>(guard: {
|
|
112
|
-
handler: (ctx: GuardCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
113
|
-
}): {
|
|
114
|
-
apiInput?: undefined;
|
|
115
|
-
guardInput?: undefined;
|
|
116
|
-
session?: undefined;
|
|
117
|
-
handler: (ctx: GuardCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
118
|
-
};
|
|
119
|
-
/** The param type a guard's handler declares as its 4th argument; undefined for paramless guards. */
|
|
120
|
-
type LambderGuardParamOf<G> = G extends {
|
|
121
|
-
handler: (...args: infer A) => any;
|
|
122
|
-
} ? (A extends [any, any, any, infer P, ...any[]] ? P : undefined) : undefined;
|
|
123
|
-
/** What a guard's handler returns (awaited); void for check-only guards. */
|
|
124
|
-
type LambderGuardOutputOf<G> = G extends {
|
|
125
|
-
handler: (...args: any[]) => infer R;
|
|
126
|
-
} ? Awaited<R> : never;
|
|
127
|
-
/** Per-guard metadata carried on the Lambder instance: input mode, session requirement, param type, output type. */
|
|
128
|
-
export type LambderGuardMeta<G> = (G extends {
|
|
129
|
-
apiInput: infer S extends z.ZodType;
|
|
130
|
-
} ? {
|
|
131
|
-
apiInput: z.output<S>;
|
|
132
|
-
} : G extends {
|
|
133
|
-
guardInput: infer S extends z.ZodType;
|
|
134
|
-
} ? {
|
|
135
|
-
guardInput: z.output<S>;
|
|
136
|
-
} : {}) & (G extends {
|
|
137
|
-
session: true;
|
|
138
|
-
} ? {
|
|
139
|
-
session: true;
|
|
140
|
-
} : {}) & {
|
|
141
|
-
param: LambderGuardParamOf<G>;
|
|
142
|
-
output: LambderGuardOutputOf<G>;
|
|
143
|
-
};
|
|
144
|
-
export type LambderGuardMetaMap<TGuards> = {
|
|
145
|
-
[K in keyof TGuards]: LambderGuardMeta<TGuards[K]>;
|
|
146
|
-
};
|
|
147
|
-
/** Guard names referenced by a guards option, whichever of its three forms is used. */
|
|
148
|
-
type NamesIn<TOpt> = TOpt extends string ? TOpt : TOpt extends readonly (infer N extends string)[] ? N : TOpt extends object ? keyof TOpt & string : never;
|
|
149
|
-
type LambderGuardNameIfPayloadOk<TGuards, K extends keyof TGuards, TPayload> = TGuards[K] extends {
|
|
150
|
-
apiInput: infer R;
|
|
151
|
-
} ? (TPayload extends R ? K : never) : K;
|
|
152
|
-
/**
|
|
153
|
-
* Guard names an API may declare: apiInput-mode guards only when the API's
|
|
154
|
-
* payload carries their fields, session guards only on session APIs.
|
|
155
|
-
*/
|
|
156
|
-
export type LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession extends boolean = true> = {
|
|
157
|
-
[K in keyof TGuards]: TGuards[K] extends {
|
|
158
|
-
session: true;
|
|
159
|
-
} ? (TIncludeSession extends true ? LambderGuardNameIfPayloadOk<TGuards, K, TPayload> : never) : LambderGuardNameIfPayloadOk<TGuards, K, TPayload>;
|
|
160
|
-
}[keyof TGuards] & string;
|
|
161
|
-
/** The allowed guard names whose handler takes no param (usable in the string/array forms). */
|
|
162
|
-
export type LambderParamlessGuardNames<TGuards, TPayload, TIncludeSession extends boolean> = {
|
|
163
|
-
[K in LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession> & keyof TGuards]: TGuards[K] extends {
|
|
164
|
-
param: undefined;
|
|
165
|
-
} ? K & string : never;
|
|
166
|
-
}[LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession> & keyof TGuards];
|
|
167
|
-
/** The map form's full shape: every declarable guard name, each carrying its own param type. */
|
|
168
|
-
type LambderGuardsMap<TGuards, TPayload, TIncludeSession extends boolean> = {
|
|
169
|
-
readonly [K in LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession> & keyof TGuards]?: TGuards[K] extends {
|
|
170
|
-
param: undefined;
|
|
171
|
-
} ? true : TGuards[K] extends {
|
|
172
|
-
param: infer P;
|
|
173
|
-
} ? P : true;
|
|
174
|
-
};
|
|
175
|
-
/**
|
|
176
|
-
* The map form with AT LEAST ONE entry: the union, over every declarable
|
|
177
|
-
* name, of "this one required and the rest optional".
|
|
178
|
-
*
|
|
179
|
-
* An all-optional map is inhabited by `{}`, which would let `guards: {}`
|
|
180
|
-
* satisfy requireSessionApiGuards / requirePublicApiGuards at the type level
|
|
181
|
-
* while declaring no guard at all: the option is present, so the required-field
|
|
182
|
-
* check passes, and it normalizes to zero entries, so nothing runs. Requiring
|
|
183
|
-
* the chosen key also rejects `{ theGuard: undefined }`, which an optional
|
|
184
|
-
* property accepts and which would otherwise reach the guard's handler with an
|
|
185
|
-
* undefined param.
|
|
186
|
-
*/
|
|
187
|
-
type LambderNonEmptyGuardsMap<TGuards, TPayload, TIncludeSession extends boolean, TMap = LambderGuardsMap<TGuards, TPayload, TIncludeSession>> = {
|
|
188
|
-
[K in keyof TMap]-?: Required<Pick<TMap, K>> & Omit<TMap, K>;
|
|
189
|
-
}[keyof TMap];
|
|
190
|
-
/**
|
|
191
|
-
* The per-API `guards` option: one paramless guard name, a non-empty ordered
|
|
192
|
-
* list of paramless names, or a non-empty object map that can carry each
|
|
193
|
-
* guard's param (`true` enables a paramless guard). Map entries run in
|
|
194
|
-
* insertion order.
|
|
195
|
-
*
|
|
196
|
-
* Every form is non-empty by construction, so declaring the option is always
|
|
197
|
-
* declaring a guard. See LambderNonEmptyGuardsMap.
|
|
198
|
-
*/
|
|
199
|
-
export type LambderGuardsOption<TGuards, TPayload, TIncludeSession extends boolean> = LambderParamlessGuardNames<TGuards, TPayload, TIncludeSession> | readonly [
|
|
200
|
-
LambderParamlessGuardNames<TGuards, TPayload, TIncludeSession>,
|
|
201
|
-
...LambderParamlessGuardNames<TGuards, TPayload, TIncludeSession>[]
|
|
202
|
-
] | LambderNonEmptyGuardsMap<TGuards, TPayload, TIncludeSession>;
|
|
203
|
-
/**
|
|
204
|
-
* The typed ctx.guardData an API's handler sees: declared guards that return
|
|
205
|
-
* a value, keyed by name. Check-only (void) guards never appear.
|
|
206
|
-
*/
|
|
207
|
-
export type LambderGuardDataOf<TGuards, TOpt> = {
|
|
208
|
-
[K in NamesIn<TOpt> & keyof TGuards as [
|
|
209
|
-
TGuards[K] extends {
|
|
210
|
-
output: infer O;
|
|
211
|
-
} ? O : never
|
|
212
|
-
] extends [void] ? never : K & string]: TGuards[K] extends {
|
|
213
|
-
output: infer O;
|
|
214
|
-
} ? O : never;
|
|
215
|
-
};
|
|
216
|
-
type GuardInputsEntries<TGuards, TOpt> = {
|
|
217
|
-
[K in Extract<NamesIn<TOpt>, keyof TGuards> as TGuards[K] extends {
|
|
218
|
-
guardInput: any;
|
|
219
|
-
} ? K : never]: TGuards[K] extends {
|
|
220
|
-
guardInput: infer V;
|
|
221
|
-
} ? V : never;
|
|
222
|
-
};
|
|
223
|
-
/** The guardInputs map an API's contract requires clients to send; never when no declared guard uses guardInput mode. */
|
|
224
|
-
export type LambderGuardInputsOf<TGuards, TOpt> = keyof GuardInputsEntries<TGuards, TOpt> extends never ? never : GuardInputsEntries<TGuards, TOpt>;
|
|
225
|
-
/** The guards option's runtime shape: a name, ordered names, or a name-to-param map. */
|
|
226
|
-
export type LambderGuardsOptionValue = string | readonly string[] | Readonly<Record<string, unknown>>;
|
|
227
|
-
/**
|
|
228
|
-
* How a rejected input answers. Lambder binds this to its own decision
|
|
229
|
-
* (setApiInputValidationErrorHandler when set, else the standard 422 body),
|
|
230
|
-
* so the API's schema and every preflight slice refuse with one shape.
|
|
231
|
-
*/
|
|
232
|
-
export type LambderInputValidationRefusal = (ctx: LambderRenderContext, resolver: LambderResolver, zodError: z.ZodError) => Promise<LambderResponse>;
|
|
233
|
-
/**
|
|
234
|
-
* Validate a preflight input slice (an apiInput slice of the raw payload, or
|
|
235
|
-
* a guardInput value from the raw guardInputs map). Runs before the API's
|
|
236
|
-
* own validation; a failure throws the response `onInvalid` decides, the
|
|
237
|
-
* same one regular input validation answers. Shared with the rate-limit
|
|
238
|
-
* engine's apiInput-keyed policies.
|
|
239
|
-
*/
|
|
240
|
-
export declare const parsePreflightSlice: (input: z.ZodType, value: unknown, ctx: LambderRenderContext, resolver: LambderResolver, onInvalid: LambderInputValidationRefusal) => Promise<unknown>;
|
|
241
|
-
/**
|
|
242
|
-
* Runtime side of the guards subsystem: holds the defined guards, asserts
|
|
243
|
-
* API registrations against them at startup, and executes an API's declared
|
|
244
|
-
* guards during preflight. Composed into LambderApiPolicyEngine.
|
|
245
|
-
*/
|
|
246
|
-
export declare class LambderApiGuardsEngine {
|
|
247
|
-
private readonly onInvalidInput;
|
|
248
|
-
private guards;
|
|
249
|
-
constructor(onInvalidInput: LambderInputValidationRefusal);
|
|
250
|
-
addGuards(guards: Record<string, LambderApiGuard<any, any, any>>): void;
|
|
251
|
-
/** Startup validation of one API registration's guards option. */
|
|
252
|
-
assertRegistration(apiName: string, mode: "public" | "session", guardsOption?: LambderGuardsOptionValue): void;
|
|
253
|
-
/** Run the API's guards in declared order. Refusals throw; outputs land on ctx.guardData. */
|
|
254
|
-
run(ctx: LambderRenderContext, resolver: LambderResolver, guardsOption?: LambderGuardsOptionValue): Promise<void>;
|
|
255
|
-
}
|
|
256
|
-
export {};
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
export function lambderGuard(guard) { return guard; }
|
|
2
|
-
/** Normalize the three guards-option forms into ordered { name, param } entries. */
|
|
3
|
-
const toGuardEntries = (value) => {
|
|
4
|
-
if (value === undefined)
|
|
5
|
-
return [];
|
|
6
|
-
if (typeof value === "string")
|
|
7
|
-
return [{ name: value, param: undefined }];
|
|
8
|
-
if (Array.isArray(value))
|
|
9
|
-
return value.map((name) => ({ name: String(name), param: undefined }));
|
|
10
|
-
// Object form: insertion order, params passed verbatim (paramless guards
|
|
11
|
-
// are declared with `true` and their handlers take no param argument).
|
|
12
|
-
return Object.entries(value).map(([name, param]) => ({ name, param }));
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* Validate a preflight input slice (an apiInput slice of the raw payload, or
|
|
16
|
-
* a guardInput value from the raw guardInputs map). Runs before the API's
|
|
17
|
-
* own validation; a failure throws the response `onInvalid` decides, the
|
|
18
|
-
* same one regular input validation answers. Shared with the rate-limit
|
|
19
|
-
* engine's apiInput-keyed policies.
|
|
20
|
-
*/
|
|
21
|
-
export const parsePreflightSlice = async (input, value, ctx, resolver, onInvalid) => {
|
|
22
|
-
const parsed = input.safeParse(value);
|
|
23
|
-
if (!parsed.success)
|
|
24
|
-
throw await onInvalid(ctx, resolver, parsed.error);
|
|
25
|
-
return parsed.data;
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Runtime side of the guards subsystem: holds the defined guards, asserts
|
|
29
|
-
* API registrations against them at startup, and executes an API's declared
|
|
30
|
-
* guards during preflight. Composed into LambderApiPolicyEngine.
|
|
31
|
-
*/
|
|
32
|
-
export class LambderApiGuardsEngine {
|
|
33
|
-
onInvalidInput;
|
|
34
|
-
guards = {};
|
|
35
|
-
constructor(onInvalidInput) {
|
|
36
|
-
this.onInvalidInput = onInvalidInput;
|
|
37
|
-
}
|
|
38
|
-
addGuards(guards) {
|
|
39
|
-
for (const [name, guardDef] of Object.entries(guards)) {
|
|
40
|
-
if (this.guards[name])
|
|
41
|
-
throw new Error(`Lambder: guard "${name}" is already defined.`);
|
|
42
|
-
if (typeof guardDef?.handler !== "function")
|
|
43
|
-
throw new Error(`Lambder: guard "${name}" has no handler function.`);
|
|
44
|
-
if (guardDef.apiInput && guardDef.guardInput)
|
|
45
|
-
throw new Error(`Lambder: guard "${name}" declares both apiInput and guardInput; pick one.`);
|
|
46
|
-
this.guards[name] = guardDef;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/** Startup validation of one API registration's guards option. */
|
|
50
|
-
assertRegistration(apiName, mode, guardsOption) {
|
|
51
|
-
const entries = toGuardEntries(guardsOption);
|
|
52
|
-
// The runtime half of LambderNonEmptyGuardsMap. `guards: {}` and
|
|
53
|
-
// `guards: []` are present-but-empty: they satisfy the require*ApiGuards
|
|
54
|
-
// field check while running nothing, which is the one shape that turns a
|
|
55
|
-
// mandatory authorization declaration back into an optional one. The type
|
|
56
|
-
// rejects both; a plain-JS caller, a cast, or a spread that happened to
|
|
57
|
-
// produce an empty object lands here instead.
|
|
58
|
-
if (guardsOption !== undefined && entries.length === 0) {
|
|
59
|
-
throw new Error(`Lambder: API "${apiName}" declares an empty guards option, which authorizes nothing. ` +
|
|
60
|
-
`Name the guard that authorizes it, or omit the option entirely.`);
|
|
61
|
-
}
|
|
62
|
-
for (const { name } of entries) {
|
|
63
|
-
const guardDef = this.guards[name];
|
|
64
|
-
if (!guardDef) {
|
|
65
|
-
throw new Error(`Lambder: API "${apiName}" references unknown guard "${name}". Declare it in the guards option at creation.`);
|
|
66
|
-
}
|
|
67
|
-
if (guardDef.session && mode !== "session") {
|
|
68
|
-
throw new Error(`Lambder: API "${apiName}" uses guard "${name}" (session: true), which requires addSessionApi.`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/** Run the API's guards in declared order. Refusals throw; outputs land on ctx.guardData. */
|
|
73
|
-
async run(ctx, resolver, guardsOption) {
|
|
74
|
-
for (const { name, param } of toGuardEntries(guardsOption)) {
|
|
75
|
-
const guardDef = this.guards[name];
|
|
76
|
-
if (!guardDef)
|
|
77
|
-
throw new Error(`Lambder: guard "${name}" is not configured.`);
|
|
78
|
-
const post = ctx.post;
|
|
79
|
-
let payload;
|
|
80
|
-
if (guardDef.apiInput) {
|
|
81
|
-
payload = await parsePreflightSlice(guardDef.apiInput, post?.payload, ctx, resolver, this.onInvalidInput);
|
|
82
|
-
}
|
|
83
|
-
else if (guardDef.guardInput) {
|
|
84
|
-
payload = await parsePreflightSlice(guardDef.guardInput, post?.guardInputs?.[name], ctx, resolver, this.onInvalidInput);
|
|
85
|
-
}
|
|
86
|
-
// A guard's return value becomes the handler's typed
|
|
87
|
-
// ctx.guardData[name]; check-only guards return undefined.
|
|
88
|
-
const output = await guardDef.handler(ctx, payload, resolver, param);
|
|
89
|
-
if (output !== undefined) {
|
|
90
|
-
ctx.guardData[name] = output;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { LambderRenderContext } from "../core/LambderContext.js";
|
|
2
|
-
import type { LambderDdbIdempotency } from "../stores/LambderDdbIdempotency.js";
|
|
3
|
-
import { LambderResponse } from "../core/LambderResponse.js";
|
|
4
|
-
export type LambderApiIdempotencyConfig = {
|
|
5
|
-
/** Your idempotency store instance; may share the rate limiter's table (distinct key prefix). */
|
|
6
|
-
store: LambderDdbIdempotency;
|
|
7
|
-
/** Seconds a stored response replays for. Default: 86400 (24h). Per-API override: idempotency: { ttlSeconds }. */
|
|
8
|
-
defaultTtlSeconds?: number;
|
|
9
|
-
/** Skip idempotency (execute normally) when DynamoDB errors, instead of failing the request. Default: true. */
|
|
10
|
-
failOpen?: boolean;
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Runtime side of the idempotency subsystem: claims a per-operation scope
|
|
14
|
-
* around handler execution, replays stored responses, and settles claims.
|
|
15
|
-
* Composed into LambderApiPolicyEngine.
|
|
16
|
-
*/
|
|
17
|
-
export declare class LambderApiIdempotencyEngine {
|
|
18
|
-
private store;
|
|
19
|
-
private defaultTtlSeconds;
|
|
20
|
-
private failOpen;
|
|
21
|
-
configure(config: LambderApiIdempotencyConfig): void;
|
|
22
|
-
/** True once the idempotency option was configured; registration asserts check it. */
|
|
23
|
-
get isConfigured(): boolean;
|
|
24
|
-
/**
|
|
25
|
-
* The request's idempotencyKey: null when absent, the key when valid, a
|
|
26
|
-
* 400 refusal when malformed. The minimum length matters for security:
|
|
27
|
-
* see IDEMPOTENCY_MIN_KEY_LENGTH.
|
|
28
|
-
*/
|
|
29
|
-
private readKey;
|
|
30
|
-
/**
|
|
31
|
-
* The record's scope. Session APIs scope per session, so even a leaked
|
|
32
|
-
* key cannot cross users. Public APIs scope by the key alone: the key is
|
|
33
|
-
* required to be long (and documented to be random), and identity proxies
|
|
34
|
-
* like the client IP are deliberately NOT part of the scope, because the
|
|
35
|
-
* retry idempotency exists for (a timeout followed by a network change)
|
|
36
|
-
* frequently arrives from a different IP.
|
|
37
|
-
*/
|
|
38
|
-
private scopeOf;
|
|
39
|
-
/**
|
|
40
|
-
* Replay fast path, run BEFORE rate limits and guards: a completed record
|
|
41
|
-
* answers with its stored response so a legitimate retry neither burns
|
|
42
|
-
* rate-limit quota nor re-runs guards (the original already passed them,
|
|
43
|
-
* and no handler executes). Misses fall through to the normal pipeline;
|
|
44
|
-
* store errors follow the failOpen setting.
|
|
45
|
-
*/
|
|
46
|
-
findReplay(apiName: string, ctx: LambderRenderContext): Promise<LambderResponse | null>;
|
|
47
|
-
/**
|
|
48
|
-
* Idempotency wrapper around validation-passed handler execution. Without
|
|
49
|
-
* a client idempotencyKey the handler just runs; with one, the scope
|
|
50
|
-
* (identity + api + key) is claimed atomically: duplicates of an
|
|
51
|
-
* in-flight original refuse with 409, replays of a completed one return
|
|
52
|
-
* the stored response verbatim, and a crashed original releases its claim
|
|
53
|
-
* so a retry actually retries.
|
|
54
|
-
*/
|
|
55
|
-
withIdempotency(apiName: string, ctx: LambderRenderContext, config: boolean | {
|
|
56
|
-
ttlSeconds?: number;
|
|
57
|
-
}, exec: () => Promise<LambderResponse>): Promise<LambderResponse>;
|
|
58
|
-
}
|