lambder 6.0.1 → 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 +26 -24
- 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
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { buildTransportEnvelope, LambderTransportFailure } from "../shared/transport/LambderApiTransport.js";
|
|
2
|
+
/**
|
|
3
|
+
* fetch, with one failure worth a better sentence than the platform gives it.
|
|
4
|
+
* A relative apiPath is resolved against the page; outside a page there is no
|
|
5
|
+
* page to resolve it against, so fetch rejects with a URL parse error that
|
|
6
|
+
* arrives at the caller looking like the network is down. The condition is
|
|
7
|
+
* read from the actual failure rather than guessed beforehand, so a runtime
|
|
8
|
+
* that resolves relative URLs some other way is left alone.
|
|
9
|
+
*/
|
|
10
|
+
const fetchOrExplain = async (url, init) => {
|
|
11
|
+
try {
|
|
12
|
+
return await fetch(url, init);
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
// Narrow on purpose: only the URL failing to parse, which is what
|
|
16
|
+
// this is. A transport that rejected for any other reason, a stubbed
|
|
17
|
+
// one included, keeps its own meaning.
|
|
18
|
+
const failedToParseUrl = err instanceof TypeError && /failed to parse url|invalid url/i.test(String(err.message));
|
|
19
|
+
if (failedToParseUrl && url.startsWith("/") && typeof globalThis.location?.href !== "string") {
|
|
20
|
+
throw new LambderTransportFailure("protocol", `lambderFetchTransport could not resolve the relative apiPath "${url}": there is no page to resolve it against outside a browser. `
|
|
21
|
+
+ "Give the caller an absolute apiPath (https://api.example.com/api), or a transport that does not need one.", { cause: err });
|
|
22
|
+
}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The production transport: one POST of the envelope to the API path over
|
|
28
|
+
* fetch, with the cookie and CORS behaviour a browser call needs. The
|
|
29
|
+
* caller's default, built from its isCorsEnabled option.
|
|
30
|
+
*/
|
|
31
|
+
export const lambderFetchTransport = (options = {}) => async (request) => {
|
|
32
|
+
const cors = options.cors ?? false;
|
|
33
|
+
const response = await fetchOrExplain(request.apiPath, {
|
|
34
|
+
method: 'POST', cache: 'no-cache',
|
|
35
|
+
// Cross-origin API hosts need CORS mode and included credentials.
|
|
36
|
+
mode: cors ? 'cors' : 'same-origin',
|
|
37
|
+
credentials: cors ? 'include' : 'same-origin',
|
|
38
|
+
redirect: 'follow', referrerPolicy: 'origin',
|
|
39
|
+
headers: {
|
|
40
|
+
// The caller's own headers go on first, so the ones this transport
|
|
41
|
+
// owns cannot be displaced by them, the way the synthesized invoke
|
|
42
|
+
// event does it. With the spread last, a per-call `Cookie` header
|
|
43
|
+
// (to carry one extra cookie, say) replaced the whole Cookie
|
|
44
|
+
// header a jar had just built, and the session went missing with
|
|
45
|
+
// nothing in the failure pointing at the cause.
|
|
46
|
+
...(request.headers ?? {}),
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
// The cookies the request is meant to carry, which is how a cookie
|
|
49
|
+
// jar works over fetch outside a browser: undici sends this
|
|
50
|
+
// header, and a page cannot (Cookie is a forbidden header name, so
|
|
51
|
+
// a browser drops it silently and uses its own cookie store, which
|
|
52
|
+
// is the right answer there). Without it the jar collected every
|
|
53
|
+
// Set-Cookie and sent none of them back, so a Node script against
|
|
54
|
+
// a deployed app got its CSRF token filled in and sessionExpired
|
|
55
|
+
// on every session call.
|
|
56
|
+
...(request.cookies?.length ? { Cookie: request.cookies.join('; ') } : {}),
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify(buildTransportEnvelope(request)),
|
|
59
|
+
...(request.signal ? { signal: request.signal } : {}),
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
status: response.status,
|
|
63
|
+
statusText: response.statusText,
|
|
64
|
+
header: (name) => response.headers?.get?.(name) ?? null,
|
|
65
|
+
json: () => response.json(),
|
|
66
|
+
text: () => response.text(),
|
|
67
|
+
// Set-Cookie is unreadable from a page's script; where the runtime
|
|
68
|
+
// exposes it (Node's fetch), a cookie jar can still consume it.
|
|
69
|
+
...(typeof response.headers?.getSetCookie === "function" ? { setCookies: response.headers.getSetCookie() } : {}),
|
|
70
|
+
};
|
|
71
|
+
};
|
package/dist/client.d.ts
CHANGED
|
@@ -7,16 +7,26 @@
|
|
|
7
7
|
* the root entry (`"lambder"`) is the server surface.
|
|
8
8
|
*/
|
|
9
9
|
export { default as LambderCaller } from "./client/LambderCaller.js";
|
|
10
|
-
export
|
|
11
|
-
export {
|
|
12
|
-
export
|
|
13
|
-
export
|
|
14
|
-
export {
|
|
15
|
-
export
|
|
16
|
-
export {
|
|
17
|
-
export
|
|
18
|
-
export {
|
|
19
|
-
export type {
|
|
10
|
+
export { DEFAULT_SESSION_TOKEN_COOKIE_KEY, DEFAULT_SESSION_CSRF_COOKIE_KEY } from "./shared/wire/LambderSessionCookieNames.js";
|
|
11
|
+
export { lambderFetchTransport } from "./client/lambderFetchTransport.js";
|
|
12
|
+
export { buildTransportEnvelope, LambderTransportFailure, isLambderTransportFailure } from "./shared/transport/LambderApiTransport.js";
|
|
13
|
+
export { lambderCookieJarTransport } from "./shared/transport/lambderCookieJarTransport.js";
|
|
14
|
+
export type { LambderApiTransport, LambderApiTransportRequest, LambderTransportFailureReason } from "./shared/transport/LambderApiTransport.js";
|
|
15
|
+
export { LambderCookieJar, parseSetCookie } from "./shared/transport/LambderCookieJar.js";
|
|
16
|
+
export type { LambderStoredCookie } from "./shared/transport/LambderCookieJar.js";
|
|
17
|
+
export { resolveApiOutcome } from "./shared/wire/LambderApiOutcome.js";
|
|
18
|
+
export type { LambderApiAnswerOutcome, LambderApiSuccessOutcome, LambderApiCallFailure, LambderApiValidationFailure, LambderApiEnvelopeFailure, LambderApiHttpAnswer, } from "./shared/wire/LambderApiOutcome.js";
|
|
19
|
+
export type { LambderApiOutcome, LambderApiFailureReason, LambderValidationError, LambderCallOptions, LambderCallerOptions, LambderGuardInputsProvider, LambderProvidedGuardInputs, LambderIdempotencyKeyScope, LambderLogListHandler, } from "./client/LambderCaller.js";
|
|
20
|
+
export { LambderApiRefusal, isLambderApiRefusal, refuse, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
21
|
+
export type { LambderApiRefusalOptions, LambderRefusalMessage, LambderAppRefusalMessage, LambderRefusalCode, LambderRefuseOptions } from "./shared/wire/LambderApiRefusal.js";
|
|
22
|
+
export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiResponseConfig, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
|
|
23
|
+
export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
|
|
24
|
+
export type { LambderCrashDetail, LambderCrashCause } from "./shared/wire/LambderCrashDetail.js";
|
|
25
|
+
export { compressPayloadGzip, isRequestCompressionAvailable, COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, DEFAULT_REQUEST_COMPRESSION_SETTINGS, } from "./shared/wire/LambderRequestPayload.js";
|
|
26
|
+
export type { LambderCompressedGzipPayload, LambderCompressedBrotliPayload, LambderRequestCompressionOption, LambderRequestCompressionSettings, } from "./shared/wire/LambderRequestPayload.js";
|
|
27
|
+
export { resolveCompressionOption } from "./shared/wire/LambderCompressionOption.js";
|
|
28
|
+
export type { LambderCompressionOption, LambderCompressionSettingsBase } from "./shared/wire/LambderCompressionOption.js";
|
|
20
29
|
export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml, type LambderHtmlValue } from "./shared/LambderHtml.js";
|
|
21
30
|
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
22
31
|
export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./shared/LambderI18n.js";
|
|
32
|
+
export type { LambderHttpStatusCode } from "./shared/wire/LambderHttpStatus.js";
|
package/dist/client.js
CHANGED
|
@@ -6,17 +6,23 @@
|
|
|
6
6
|
* import from this entry so their bundles can never pick up server code;
|
|
7
7
|
* the root entry (`"lambder"`) is the server surface.
|
|
8
8
|
*/
|
|
9
|
-
// The typed API caller.
|
|
9
|
+
// The typed API caller, and how it reaches the server.
|
|
10
10
|
export { default as LambderCaller } from "./client/LambderCaller.js";
|
|
11
|
+
export { DEFAULT_SESSION_TOKEN_COOKIE_KEY, DEFAULT_SESSION_CSRF_COOKIE_KEY } from "./shared/wire/LambderSessionCookieNames.js";
|
|
12
|
+
export { lambderFetchTransport } from "./client/lambderFetchTransport.js";
|
|
13
|
+
export { buildTransportEnvelope, LambderTransportFailure, isLambderTransportFailure } from "./shared/transport/LambderApiTransport.js";
|
|
14
|
+
export { lambderCookieJarTransport } from "./shared/transport/lambderCookieJarTransport.js";
|
|
15
|
+
export { LambderCookieJar, parseSetCookie } from "./shared/transport/LambderCookieJar.js";
|
|
16
|
+
export { resolveApiOutcome } from "./shared/wire/LambderApiOutcome.js";
|
|
11
17
|
// Typed API refusals (isomorphic: shared code may throw them from anywhere;
|
|
12
18
|
// in the browser they are plain Errors).
|
|
13
|
-
export {
|
|
19
|
+
export { LambderApiRefusal, isLambderApiRefusal, refuse, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
14
20
|
// A crash described for a caller allowed to see it (the envelope's `crash` field; pure, no Node built-ins).
|
|
15
|
-
export { describeCrash, errorFromCrashDetail } from "./shared/LambderCrashDetail.js";
|
|
21
|
+
export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
|
|
16
22
|
// Request payload compression (browser-safe: gzip via CompressionStream, no Node built-ins).
|
|
17
|
-
export { compressPayloadGzip,
|
|
23
|
+
export { compressPayloadGzip, isRequestCompressionAvailable, COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, DEFAULT_REQUEST_COMPRESSION_SETTINGS, } from "./shared/wire/LambderRequestPayload.js";
|
|
18
24
|
// The compression option vocabulary every Lambder surface shares (pure: no zlib).
|
|
19
|
-
export { resolveCompressionOption } from "./shared/LambderCompressionOption.js";
|
|
25
|
+
export { resolveCompressionOption } from "./shared/wire/LambderCompressionOption.js";
|
|
20
26
|
// Type-safe templating (tagged templates with auto-escaping)
|
|
21
27
|
export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml } from "./shared/LambderHtml.js";
|
|
22
28
|
// Typed translations (standalone, isomorphic)
|
package/dist/core/Lambder.d.ts
CHANGED
|
@@ -2,195 +2,28 @@ import type { z } from "zod";
|
|
|
2
2
|
import type { Context } from "aws-lambda";
|
|
3
3
|
import LambderResolver from "./LambderResolver.js";
|
|
4
4
|
import LambderResponseBuilder from "./LambderResponseBuilder.js";
|
|
5
|
-
import { LambderResponse, type LambderHttpResponse
|
|
6
|
-
import { type
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import { type LambderCompressionOption } from "../shared/LambderCompressionOption.js";
|
|
10
|
-
import LambderSessionController, { type LambderSessionCookieOptions } from "../session/LambderSessionController.js";
|
|
5
|
+
import { LambderResponse, type LambderHttpResponse } from "./LambderResponse.js";
|
|
6
|
+
import { type LambderRouteConditionFn, type LambderRouteMatcher, type LambderPathParamsOf, type LambderRoutePath } from "./LambderRouting.js";
|
|
7
|
+
import LambderSessionManager from "../session/LambderSessionManager.js";
|
|
8
|
+
import type LambderSessionController from "../session/LambderSessionController.js";
|
|
11
9
|
import { type LambderPublicFilesOptions } from "./LambderPublicFiles.js";
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
import type {
|
|
15
|
-
import type {
|
|
16
|
-
import type {
|
|
10
|
+
import { type LambderIndexHtmlOptions } from "./LambderIndexHtml.js";
|
|
11
|
+
import { LambderFiles } from "./LambderFiles.js";
|
|
12
|
+
import type { LambderApiIdempotencyOption } from "../shared/wire/LambderApiOptionValues.js";
|
|
13
|
+
import type { LambderApiGuard, LambderGuardMetaMap, LambderGuardsOption, LambderGuardDataOf, LambderGuardInputsOf } from "../api/LambderApiGuards.js";
|
|
14
|
+
import type { LambderApiRateLimitPolicyConfig, LambderRateLimitOption } from "../api/LambderApiRateLimits.js";
|
|
15
|
+
import type { LambderApiIdempotencyConfig } from "../api/LambderApiIdempotency.js";
|
|
16
|
+
import type { LambderContractEntry, LambderMergeContract } from "../shared/wire/LambderApiContract.js";
|
|
17
17
|
import { type LambderHttpEvent, type LambderRenderContext, type LambderSessionRenderContext } from "./LambderContext.js";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
type MaybePromise<T> = T | Promise<T>;
|
|
21
|
-
type Path = `/${string}`;
|
|
22
|
-
type ActionFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => MaybePromise<LambderResponse>;
|
|
23
|
-
type SessionActionFunction<SessionData = any> = (ctx: LambderSessionRenderContext<any, SessionData>, resolver: LambderResolver) => MaybePromise<LambderResponse>;
|
|
24
|
-
type HookCreatedFunction = (lambderInstance: Lambder<any, any, any, any, any, any, any>) => void | Promise<void>;
|
|
25
|
-
/** Return the (possibly replaced) ctx to continue, a LambderResponse to short-circuit, or an Error to fail. */
|
|
26
|
-
type HookBeforeRenderFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => MaybePromise<LambderRenderContext | LambderResponse | Error>;
|
|
27
|
-
type HookAfterRenderFunction = (ctx: LambderRenderContext, resolver: LambderResolver, response: LambderResponse) => MaybePromise<LambderResponse | Error>;
|
|
28
|
-
type HookFallbackFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => void | Promise<void>;
|
|
29
|
-
type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext | null, response: LambderResponseBuilder, logListToApiResponse?: any[]) => MaybePromise<LambderResponse>;
|
|
30
|
-
type FallbackHandlerFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => MaybePromise<LambderResponse>;
|
|
31
|
-
type ApiInputValidationErrorHandlerFunction = (ctx: LambderRenderContext, resolver: LambderResolver, zodError: z.ZodError) => MaybePromise<LambderResponse>;
|
|
32
|
-
export type LambderIndexHtmlOptions = {
|
|
33
|
-
/** Methods that reach the index handler. Default: ["GET", "HEAD"]. */
|
|
34
|
-
methods?: string[];
|
|
35
|
-
/**
|
|
36
|
-
* Skip paths whose last segment contains a dot, treating them as missing
|
|
37
|
-
* assets rather than app routes. Default: false — real files have already
|
|
38
|
-
* been served by servePublicFiles at this point, and plenty of app routes
|
|
39
|
-
* carry dots (JWTs, coordinates, domain names, version numbers). Turn it
|
|
40
|
-
* on to get 404s instead of a 200 shell for missing-asset requests.
|
|
41
|
-
*/
|
|
42
|
-
skipFilePaths?: boolean;
|
|
43
|
-
/** 301-redirect trailing-slash paths to the canonical no-slash URL. Default: false. */
|
|
44
|
-
redirectTrailingSlash?: boolean;
|
|
45
|
-
/** Shell served by the default handler. Default: "index.html". */
|
|
46
|
-
indexFile?: string | ((ctx: LambderRenderContext) => string);
|
|
47
|
-
/** Compression override, like servePublicFiles: "auto" (default), true/false, or (ctx) => boolean | "auto". */
|
|
48
|
-
compress?: boolean | "auto" | ((ctx: LambderRenderContext) => boolean | "auto");
|
|
49
|
-
};
|
|
18
|
+
import type { MaybePromise } from "../shared/util/LambderTypeUtilities.js";
|
|
19
|
+
import { type LambderRouteHandler, type LambderInputValidationHandler, type LambderFallbackHandler, type LambderGlobalErrorHandler, type LambderAfterRenderHook, type LambderBeforeRenderHook, type LambderFallbackHook, type LambderActionTools, type LambderCreateOptions, type LambderGivenOption, type LambderHandler, type LambderNestedOptionChecks, type LambderNoExtraKeys, type LambderRequirableGuardsField, type LambderSessionEnabledInstance, type LambderSessionRouteHandler } from "./LambderCreateOptions.js";
|
|
50
20
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
21
|
+
* The "created" hook: run once the instance exists, with the instance. It is
|
|
22
|
+
* declared here rather than beside the other hooks in LambderCreateOptions
|
|
23
|
+
* because its parameter is the class, and an options module that names the
|
|
24
|
+
* class cannot be read without it.
|
|
54
25
|
*/
|
|
55
|
-
export type
|
|
56
|
-
ctx: LambderRenderContext;
|
|
57
|
-
res: LambderResolver;
|
|
58
|
-
lambdaContext: Context;
|
|
59
|
-
} | {
|
|
60
|
-
ctx: null;
|
|
61
|
-
res: null;
|
|
62
|
-
lambdaContext: Context;
|
|
63
|
-
};
|
|
64
|
-
/** Overloaded handler type returned by getHandler(): HTTP events get a typed response, others dispatch to actions. */
|
|
65
|
-
export type LambderHandler = {
|
|
66
|
-
(event: LambderHttpEvent, context: Context): Promise<LambderHttpResponse>;
|
|
67
|
-
(event: unknown, context: Context): Promise<unknown>;
|
|
68
|
-
};
|
|
69
|
-
/** DynamoDB session configuration (the `session` option of create/new). */
|
|
70
|
-
export type LambderSessionOptions<TSessionData = any> = {
|
|
71
|
-
tableName: string;
|
|
72
|
-
tableRegion: string;
|
|
73
|
-
sessionSalt: string;
|
|
74
|
-
enableSlidingExpiration?: boolean;
|
|
75
|
-
/** Min seconds between sliding-expiration writes. Default: max(60, 5% of TTL). */
|
|
76
|
-
slidingWriteIntervalSeconds?: number;
|
|
77
|
-
/** Session cookie attributes, e.g. { domain: ".example.com" } for cross-subdomain sessions. `domain` may be a (hostname) => string function for multi-domain deployments. */
|
|
78
|
-
cookie?: LambderSessionCookieOptions;
|
|
79
|
-
/** Session cookie names. Defaults: LMDRSESSIONTKID / LMDRSESSIONCSTK. */
|
|
80
|
-
tokenCookieKey?: string;
|
|
81
|
-
csrfCookieKey?: string;
|
|
82
|
-
partitionKey?: string;
|
|
83
|
-
sortKey?: string;
|
|
84
|
-
/**
|
|
85
|
-
* Opt-in freshness for session.data derived from external state (roles,
|
|
86
|
-
* permissions, feature flags...). Every session read renews data past
|
|
87
|
-
* its ttlSeconds via your refresh callback, persisting in place on the
|
|
88
|
-
* same record: same tokens, same cookies. Return null from refresh to
|
|
89
|
-
* end the session. See LambderSessionDataRefreshConfig for the exact
|
|
90
|
-
* semantics.
|
|
91
|
-
*/
|
|
92
|
-
dataRefresh?: LambderSessionDataRefreshConfig<TSessionData>;
|
|
93
|
-
/**
|
|
94
|
-
* Brotli compression of session.data at rest. `true` (the default)
|
|
95
|
-
* compresses every record, the same as `{ minBytes: 0 }`; `false` turns
|
|
96
|
-
* it off; `{ minBytes }` compresses only records whose JSON is at least
|
|
97
|
-
* that many bytes. Records written under either setting read back, so
|
|
98
|
-
* it can be switched on or off on a live table.
|
|
99
|
-
*/
|
|
100
|
-
compression?: LambderCompressionOption;
|
|
101
|
-
};
|
|
102
|
-
/**
|
|
103
|
-
* Everything an instance is configured with, in ONE declaration: base
|
|
104
|
-
* serving options plus the type-affecting policy layer (rate limits,
|
|
105
|
-
* guards, idempotency) and session/CORS config. There are no enable/define
|
|
106
|
-
* chain methods; the instance is born fully configured and fully typed
|
|
107
|
-
* (via initLambder), so no ordering rules exist and no partially-configured
|
|
108
|
-
* instance type ever needs a name.
|
|
109
|
-
*/
|
|
110
|
-
export type LambderCreateOptions<TSessionData = any> = {
|
|
111
|
-
/**
|
|
112
|
-
* Where the app's files come from, for servePublicFiles, serveIndexHtml,
|
|
113
|
-
* res.file and res.templateFile: a LambderLocalFileSource over a folder
|
|
114
|
-
* (the build output bundled with the deployment), a LambderS3FileSource
|
|
115
|
-
* (S3, R2), or any LambderFileSource; or `{ source, memoryCache }` to
|
|
116
|
-
* tune or disable the in-memory file cache. Required by those features.
|
|
117
|
-
*/
|
|
118
|
-
files?: LambderFilesOption;
|
|
119
|
-
apiPath?: string;
|
|
120
|
-
apiVersion?: string;
|
|
121
|
-
/**
|
|
122
|
-
* Automatic compression for compressible responses. `true` (the default)
|
|
123
|
-
* is `{ minBytes: 860, encodings: ["br", "gzip"], quality: 5 }`; `false`
|
|
124
|
-
* disables it. `encodings` is a preference order, so `["gzip"]` opts out
|
|
125
|
-
* of Brotli for a client or CDN that mishandles it, and `quality` is the
|
|
126
|
-
* Brotli quality, the same field the at-rest stores take.
|
|
127
|
-
*/
|
|
128
|
-
compression?: LambderResponseCompressionOption;
|
|
129
|
-
/** Automatic ETag + If-None-Match 304 on GET/HEAD 200 responses. Default: true. */
|
|
130
|
-
etag?: boolean;
|
|
131
|
-
/** Guard threshold for Lambda's ~6MB response cap. Default: 5,500,000. */
|
|
132
|
-
maxResponseBytes?: number;
|
|
133
|
-
/**
|
|
134
|
-
* Ceiling on what a gzipped request payload may restore to (Lambda's
|
|
135
|
-
* ~6MB invoke cap already bounds the compressed bytes). Default:
|
|
136
|
-
* 20,000,000. Requests over it are refused rather than decompressed.
|
|
137
|
-
* The restored JSON is parsed in full before any policy or session
|
|
138
|
-
* check, so size it to the function's memory.
|
|
139
|
-
*/
|
|
140
|
-
maxRequestPayloadBytes?: number;
|
|
141
|
-
/** CORS: true allows any origin; or pass a LambderCorsConfig. Default: off. */
|
|
142
|
-
cors?: boolean | LambderCorsConfig;
|
|
143
|
-
/** DynamoDB-backed sessions; required for addSessionApi/addSessionRoute. */
|
|
144
|
-
session?: LambderSessionOptions<TSessionData>;
|
|
145
|
-
/** Declarative per-API rate limiting: your limiter plus named policies APIs reference (typed) via the `rateLimit` option. */
|
|
146
|
-
rateLimits?: LambderApiRateLimitsConfig<Record<string, LambderApiRateLimitPolicyConfig>>;
|
|
147
|
-
/** Named guards APIs reference (typed) via the `guards` option; build each with lambderGuard(). */
|
|
148
|
-
guards?: Record<string, LambderApiGuard<any, any, any>>;
|
|
149
|
-
/**
|
|
150
|
-
* Make an authorization declaration part of registering a session API:
|
|
151
|
-
* every addSessionApi must declare `guards`, at the type level (a
|
|
152
|
-
* missing `guards` is a compile error) and at registration (a plain-JS
|
|
153
|
-
* caller throws). An API that legitimately needs none, because the
|
|
154
|
-
* session itself is the whole authorization (the signed-in user's own
|
|
155
|
-
* account), declares a named no-op session guard, so every opt-out is
|
|
156
|
-
* explicit and one grep lists them all. Needs a guards map to pick
|
|
157
|
-
* from. Default: false.
|
|
158
|
-
*/
|
|
159
|
-
requireSessionApiGuards?: boolean;
|
|
160
|
-
/**
|
|
161
|
-
* The same for public APIs: every addApi must declare `guards`, at the
|
|
162
|
-
* type level and at registration.
|
|
163
|
-
*
|
|
164
|
-
* Public APIs are open by default and that is the right default, so this
|
|
165
|
-
* is off unless an app decides otherwise. What it buys an app that turns
|
|
166
|
-
* it on is that a public endpoint's openness becomes a written decision
|
|
167
|
-
* rather than an omission: the ones anybody may call declare a named no-op
|
|
168
|
-
* guard carrying the reason, and the ones that authorize their caller some
|
|
169
|
-
* other way (a signature, a device secret, a one-shot token) name where
|
|
170
|
-
* that happens. One grep over the guard names then lists every public
|
|
171
|
-
* door and why it is open, which is the review question a growing public
|
|
172
|
-
* surface makes expensive to answer any other way. Needs a guards map to
|
|
173
|
-
* pick from. Default: false.
|
|
174
|
-
*/
|
|
175
|
-
requirePublicApiGuards?: boolean;
|
|
176
|
-
/** Declarative idempotency: your store plus replay defaults; APIs opt in via `idempotency: true | { ttlSeconds }`. */
|
|
177
|
-
idempotency?: LambderApiIdempotencyConfig;
|
|
178
|
-
};
|
|
179
|
-
/**
|
|
180
|
-
* The `guards` field of an API's options: optional by default, required once
|
|
181
|
-
* create() received the require*ApiGuards flag for that kind of API, so that
|
|
182
|
-
* an authorization declaration cannot be forgotten at the type level.
|
|
183
|
-
*
|
|
184
|
-
* One type for both kinds: the requirement is the same shape either way, and
|
|
185
|
-
* only which flag switches it on differs.
|
|
186
|
-
*/
|
|
187
|
-
type LambderRequirableGuardsField<TRequired extends boolean, TGuardsOpt> = TRequired extends true ? {
|
|
188
|
-
/** Named guards, run in declared order before input validation: a name, a non-empty list of names, or a non-empty { name: param } map for parameterized guards. Required on this instance: an API that needs no authorization declares a named no-op guard, so every opt-out is explicit and one grep lists them all. Their input requirements merge into this API's contract input; their return values land typed on ctx.guardData. */
|
|
189
|
-
guards: TGuardsOpt;
|
|
190
|
-
} : {
|
|
191
|
-
/** Named guards, run in declared order before input validation: a name, a non-empty list of names, or a non-empty { name: param } map for parameterized guards. Their input requirements merge into this API's contract input; their return values land typed on ctx.guardData. */
|
|
192
|
-
guards?: TGuardsOpt;
|
|
193
|
-
};
|
|
26
|
+
export type LambderCreatedHook = (lambderInstance: Lambder<any, any, any, any, any, any, any, any>) => void | Promise<void>;
|
|
194
27
|
/**
|
|
195
28
|
* Main Lambder class for building type-safe serverless APIs. Create
|
|
196
29
|
* instances with initLambder<SessionData>().create({...}) (see below): the
|
|
@@ -204,6 +37,7 @@ type LambderRequirableGuardsField<TRequired extends boolean, TGuardsOpt> = TRequ
|
|
|
204
37
|
* @typeParam _TIdempotencyEnabled - @internal True when create() received idempotency (do not pass manually)
|
|
205
38
|
* @typeParam _TSessionGuardsRequired - @internal True when create() received requireSessionApiGuards (do not pass manually)
|
|
206
39
|
* @typeParam _TPublicGuardsRequired - @internal True when create() received requirePublicApiGuards (do not pass manually)
|
|
40
|
+
* @typeParam _TSessionsEnabled - @internal True when create() received the session option (do not pass manually). It defaults to TRUE, unlike its siblings: a plugin module annotates its parameter as the bare Lambder<SessionData>, and that annotation has to keep registering session APIs. create() is where the option is actually known, so create() is where the false comes from; `new Lambder(...)` keeps only the registration-time throw.
|
|
207
41
|
*
|
|
208
42
|
* @example
|
|
209
43
|
* ```typescript
|
|
@@ -214,7 +48,7 @@ type LambderRequirableGuardsField<TRequired extends boolean, TGuardsOpt> = TRequ
|
|
|
214
48
|
* .addApi('createUser', { input: z.object({...}), output: z.object({...}) }, handler);
|
|
215
49
|
* ```
|
|
216
50
|
*/
|
|
217
|
-
export default class Lambder<TSessionData = any, _TContract extends Record<string, any> = {}, _TRateLimitPolicies extends Record<string, LambderApiRateLimitPolicyConfig> = {}, _TGuards extends Record<string, any> = {}, _TIdempotencyEnabled extends boolean = false, _TSessionGuardsRequired extends boolean = false, _TPublicGuardsRequired extends boolean = false> {
|
|
51
|
+
export default class Lambder<TSessionData = any, _TContract extends Record<string, any> = {}, _TRateLimitPolicies extends Record<string, LambderApiRateLimitPolicyConfig> = {}, _TGuards extends Record<string, any> = {}, _TIdempotencyEnabled extends boolean = false, _TSessionGuardsRequired extends boolean = false, _TPublicGuardsRequired extends boolean = false, _TSessionsEnabled extends boolean = true> {
|
|
218
52
|
apiPath: string;
|
|
219
53
|
apiVersion: null | string;
|
|
220
54
|
/** The instance's file reader (source + caches), or null without the files option. */
|
|
@@ -231,7 +65,8 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
231
65
|
*/
|
|
232
66
|
readonly ApiContract: _TContract;
|
|
233
67
|
private actionList;
|
|
234
|
-
|
|
68
|
+
/** The API core: the pipeline every API call runs through, shared in shape with the mock runtime. */
|
|
69
|
+
private readonly pipeline;
|
|
235
70
|
private registeredApiNames;
|
|
236
71
|
private hookList;
|
|
237
72
|
private createdHooks;
|
|
@@ -242,32 +77,29 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
242
77
|
private apiInputValidationErrorHandler;
|
|
243
78
|
private sessionExpiredRouteHandler;
|
|
244
79
|
private publicFilesHandler;
|
|
245
|
-
private
|
|
80
|
+
private indexHtmlHandler;
|
|
246
81
|
private eventActionList;
|
|
247
82
|
private corsConfig;
|
|
248
83
|
private finalizeOptions;
|
|
249
|
-
private maxRequestPayloadBytes;
|
|
250
84
|
private requireSessionApiGuards;
|
|
85
|
+
private readonly trustedClientIpHeaders;
|
|
251
86
|
private requirePublicApiGuards;
|
|
252
|
-
private lambderSessionManager?;
|
|
253
|
-
private sessionCookieOptions;
|
|
254
|
-
private sessionTokenCookieKey;
|
|
255
|
-
private sessionCsrfCookieKey;
|
|
256
87
|
constructor(options?: LambderCreateOptions<TSessionData>);
|
|
257
|
-
setRouteFallbackHandler(routeFallbackHandler:
|
|
258
|
-
setApiFallbackHandler(apiFallbackHandler:
|
|
259
|
-
setApiInputValidationErrorHandler(apiInputValidationErrorHandler:
|
|
260
|
-
setGlobalErrorHandler(globalErrorHandler:
|
|
88
|
+
setRouteFallbackHandler(routeFallbackHandler: LambderFallbackHandler): this;
|
|
89
|
+
setApiFallbackHandler(apiFallbackHandler: LambderFallbackHandler): this;
|
|
90
|
+
setApiInputValidationErrorHandler(apiInputValidationErrorHandler: LambderInputValidationHandler): this;
|
|
91
|
+
setGlobalErrorHandler(globalErrorHandler: LambderGlobalErrorHandler): this;
|
|
261
92
|
/** Response for session routes when the session is missing/expired (non-API). Default: 401. */
|
|
262
|
-
setSessionExpiredRouteHandler(handler:
|
|
93
|
+
setSessionExpiredRouteHandler(handler: LambderFallbackHandler): this;
|
|
263
94
|
/**
|
|
264
95
|
* Terminal public-file layer. Runs only when no route matched, so it can
|
|
265
96
|
* never shadow routes registered after it. Serves files from the `files`
|
|
266
|
-
* source configured at creation,
|
|
97
|
+
* source configured at creation, under the reader's path rule, mime-typed,
|
|
267
98
|
* memory-cached, with the immutable-cache heuristic for content-hashed
|
|
268
|
-
* assets
|
|
269
|
-
*
|
|
270
|
-
*
|
|
99
|
+
* assets. Only configured methods reach it, default GET/HEAD, as for
|
|
100
|
+
* serveIndexHtml; a gated-out method and a path the source has no file
|
|
101
|
+
* for both fall through to setRouteFallbackHandler, where the app decides
|
|
102
|
+
* what remains (e.g. render an app shell with res.templateFile).
|
|
271
103
|
*/
|
|
272
104
|
servePublicFiles(options?: LambderPublicFilesOptions): this;
|
|
273
105
|
/**
|
|
@@ -279,64 +111,33 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
279
111
|
* Without a handler, index.html from the files source is served via
|
|
280
112
|
* res.templateFile (markers optional) with no-cache.
|
|
281
113
|
*/
|
|
282
|
-
serveIndexHtml(handler?:
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
* setApiInputValidationErrorHandler when set, otherwise the standard 422
|
|
289
|
-
* body. The API's own schema and every preflight slice (guard inputs,
|
|
290
|
-
* rate-limit keys) answer through here, so one failure has one shape.
|
|
291
|
-
*/
|
|
292
|
-
private inputValidationRefusal;
|
|
293
|
-
/** Registration-time checks shared by addApi/addSessionApi. */
|
|
294
|
-
private assertApiRegistration;
|
|
295
|
-
addRoute<TPath extends Path>(condition: TPath, actionFn: (ctx: LambderRenderContext<any, PathParamsOf<TPath>>, resolver: LambderResolver) => MaybePromise<LambderResponse>): this;
|
|
296
|
-
addRoute(condition: RegExp | ConditionFunction | LambderRouteMatcher, actionFn: ActionFunction): this;
|
|
297
|
-
addSessionRoute<TPath extends Path>(condition: TPath, actionFn: (ctx: LambderSessionRenderContext<any, TSessionData, PathParamsOf<TPath>>, resolver: LambderResolver) => MaybePromise<LambderResponse>): this;
|
|
298
|
-
addSessionRoute(condition: RegExp | ConditionFunction | LambderRouteMatcher, actionFn: SessionActionFunction<TSessionData>): this;
|
|
299
|
-
use<_TNewContract extends Record<string, any>>(plugin: (lambder: Lambder<TSessionData, _TContract, any, any, any, any, any>) => Lambder<TSessionData, _TNewContract, any, any, any, any, any>): Lambder<TSessionData, _TNewContract extends _TContract ? _TNewContract : (_TContract & _TNewContract), _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired>;
|
|
300
|
-
addApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.infer<TInput>, false> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.infer<TInput>, false> = never>(name: TName, schema: {
|
|
114
|
+
serveIndexHtml(handler?: LambderFallbackHandler, options?: LambderIndexHtmlOptions): this;
|
|
115
|
+
addRoute<TPath extends LambderRoutePath>(condition: TPath, actionFn: (ctx: LambderRenderContext<any, LambderPathParamsOf<TPath>>, resolver: LambderResolver) => MaybePromise<LambderResponse>): this;
|
|
116
|
+
addRoute(condition: RegExp | LambderRouteConditionFn | LambderRouteMatcher, actionFn: LambderRouteHandler): this;
|
|
117
|
+
addSessionRoute<TPath extends LambderRoutePath>(condition: TPath, actionFn: ((ctx: LambderSessionRenderContext<any, TSessionData, LambderPathParamsOf<TPath>>, resolver: LambderResolver) => MaybePromise<LambderResponse>) & LambderSessionEnabledInstance<_TSessionsEnabled>): this;
|
|
118
|
+
addSessionRoute(condition: RegExp | LambderRouteConditionFn | LambderRouteMatcher, actionFn: LambderSessionRouteHandler<TSessionData> & LambderSessionEnabledInstance<_TSessionsEnabled>): this;
|
|
119
|
+
addApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.infer<TInput>, false> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.infer<TInput>, false> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never>(name: TName, schema: {
|
|
301
120
|
input: TInput;
|
|
302
121
|
output: TOutput;
|
|
303
122
|
} & {
|
|
304
123
|
/** Named rate limits, checked in declared order before guards and validation: a name, a list of names, or a { name: true | override } map (windows overridable on perApi budgets, errorMessage on any). The first exceeded one refuses (429 envelope + Retry-After); attempts count on every counter checked before it. */
|
|
305
124
|
rateLimit?: TRateOpt;
|
|
306
125
|
/** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
|
|
307
|
-
idempotency?: _TIdempotencyEnabled extends true ?
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
} & LambderRequirableGuardsField<_TPublicGuardsRequired, TGuardsOpt>, handler: (ctx: LambderRenderContext<z.infer<TInput>, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>>, resolver: LambderResolver<z.infer<TOutput>>) => MaybePromise<LambderResponse>): Lambder<TSessionData, MergeContract<_TContract, TName, z.infer<TInput>, z.infer<TOutput>, LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired>;
|
|
311
|
-
addSessionApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.infer<TInput>, true> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.infer<TInput>, true> = never>(name: TName, schema: {
|
|
126
|
+
idempotency?: _TIdempotencyEnabled extends true ? TIdempotencyOpt : never;
|
|
127
|
+
} & LambderRequirableGuardsField<_TPublicGuardsRequired, TGuardsOpt>, handler: (ctx: LambderRenderContext<z.infer<TInput>, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>>, resolver: LambderResolver<z.infer<TOutput>>) => MaybePromise<LambderResponse>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.infer<TInput>, z.infer<TOutput>, "public", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
|
|
128
|
+
addSessionApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.infer<TInput>, true> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.infer<TInput>, true> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never>(name: TName, schema: {
|
|
312
129
|
input: TInput;
|
|
313
130
|
output: TOutput;
|
|
314
131
|
} & {
|
|
315
132
|
/** Named rate limits, checked in declared order before guards and validation: a name, a list of names, or a { name: true | override } map (windows overridable on perApi budgets, errorMessage on any). The first exceeded one refuses (429 envelope + Retry-After); attempts count on every counter checked before it. */
|
|
316
133
|
rateLimit?: TRateOpt;
|
|
317
134
|
/** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
|
|
318
|
-
idempotency?: _TIdempotencyEnabled extends true ?
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
* protocol's { sessionExpired: true } response (handled by LambderCaller),
|
|
325
|
-
* routes get the sessionExpiredRouteHandler response (default 401).
|
|
326
|
-
*/
|
|
327
|
-
private requireSession;
|
|
328
|
-
addHook(hookEvent: 'created', hookFn: HookCreatedFunction, priority?: number): this;
|
|
329
|
-
addHook(hookEvent: 'beforeRender', hookFn: HookBeforeRenderFunction, priority?: number): this;
|
|
330
|
-
addHook(hookEvent: 'afterRender', hookFn: HookAfterRenderFunction, priority?: number): this;
|
|
331
|
-
addHook(hookEvent: 'fallback', hookFn: HookFallbackFunction, priority?: number): this;
|
|
332
|
-
getSessionController(ctx: LambderRenderContext | LambderSessionRenderContext<any, TSessionData>): LambderSessionController<TSessionData>;
|
|
333
|
-
getResponseBuilder(ctx?: LambderRenderContext): LambderResponseBuilder<any>;
|
|
334
|
-
private getResolver;
|
|
335
|
-
/** Map a thrown LambderApiError onto the structured API envelope. */
|
|
336
|
-
private apiErrorResponse;
|
|
337
|
-
getHandler(): LambderHandler;
|
|
338
|
-
/** True when the Lambda event is an API Gateway HTTP event (REST API v1 or HTTP API / Function URL v2). */
|
|
339
|
-
static isHttpEvent(event: unknown): event is LambderHttpEvent;
|
|
135
|
+
idempotency?: _TIdempotencyEnabled extends true ? TIdempotencyOpt : never;
|
|
136
|
+
} & LambderRequirableGuardsField<_TSessionGuardsRequired, TGuardsOpt> & LambderSessionEnabledInstance<_TSessionsEnabled>, handler: (ctx: LambderSessionRenderContext<z.infer<TInput>, TSessionData, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>>, resolver: LambderResolver<z.infer<TOutput>>) => MaybePromise<LambderResponse>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.infer<TInput>, z.infer<TOutput>, "session", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
|
|
137
|
+
addHook(hookEvent: 'created', hookFn: LambderCreatedHook, priority?: number): this;
|
|
138
|
+
addHook(hookEvent: 'beforeRender', hookFn: LambderBeforeRenderHook, priority?: number): this;
|
|
139
|
+
addHook(hookEvent: 'afterRender', hookFn: LambderAfterRenderHook, priority?: number): this;
|
|
140
|
+
addHook(hookEvent: 'fallback', hookFn: LambderFallbackHook, priority?: number): this;
|
|
340
141
|
/**
|
|
341
142
|
* Register an action that filters on the raw Lambda event and, for HTTP
|
|
342
143
|
* invocations, the context (ctx is null otherwise).
|
|
@@ -355,13 +156,76 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
355
156
|
*/
|
|
356
157
|
addAction<TEvent>(filter: (event: unknown, ctx: LambderRenderContext | null) => event is TEvent, actionFn: (event: TEvent, tools: LambderActionTools) => MaybePromise<unknown>): this;
|
|
357
158
|
addAction(filter: (event: unknown, ctx: LambderRenderContext | null) => boolean, actionFn: (event: unknown, tools: LambderActionTools) => MaybePromise<unknown>): this;
|
|
358
|
-
|
|
359
|
-
|
|
159
|
+
use<_TNewContract extends Record<string, any>>(plugin: (lambder: Lambder<TSessionData, _TContract, any, any, any, any, any, any>) => Lambder<TSessionData, _TNewContract, any, any, any, any, any, any>): Lambder<TSessionData, _TNewContract extends _TContract ? _TNewContract : (_TContract & _TNewContract), _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
|
|
160
|
+
/**
|
|
161
|
+
* Sessions for this request: what handlers create, rotate, refresh and
|
|
162
|
+
* end sessions with. An API call presents its posted CSRF token; a route
|
|
163
|
+
* presents cookies alone.
|
|
164
|
+
*/
|
|
165
|
+
getSessionController(ctx: LambderRenderContext | LambderSessionRenderContext<any, TSessionData>): LambderSessionController<TSessionData>;
|
|
166
|
+
/** The session manager, for code that works on sessions outside a request (maintenance, tests). */
|
|
167
|
+
getSessionManager(): LambderSessionManager<TSessionData>;
|
|
168
|
+
getResponseBuilder(ctx?: LambderRenderContext): LambderResponseBuilder<any>;
|
|
169
|
+
private getResolver;
|
|
170
|
+
getHandler(): LambderHandler;
|
|
171
|
+
/** True when the Lambda event is an API Gateway HTTP event (REST API v1 or HTTP API / Function URL v2). */
|
|
172
|
+
static isHttpEvent(event: unknown): event is LambderHttpEvent;
|
|
360
173
|
private ensureInitialized;
|
|
361
174
|
private applyCors;
|
|
175
|
+
/**
|
|
176
|
+
* The beforeRender hooks, in priority order: the replaced context to
|
|
177
|
+
* continue with, or the response one of them answered with.
|
|
178
|
+
*
|
|
179
|
+
* Its own method because BOTH request paths run it. Left inline after the
|
|
180
|
+
* match, it ran for routes and APIs and for nothing else, so a
|
|
181
|
+
* servePublicFiles or serveIndexHtml answer, which is every asset and
|
|
182
|
+
* every app-shell page, skipped the one hook that can inspect a request,
|
|
183
|
+
* replace its context or short-circuit it: a security header written in a
|
|
184
|
+
* hook reached the API answers and not the HTML it was written for, and a
|
|
185
|
+
* maintenance-mode hook served the whole frontend anyway.
|
|
186
|
+
*/
|
|
187
|
+
private runBeforeRenderHooks;
|
|
362
188
|
private handleNoMatchedAction;
|
|
189
|
+
/**
|
|
190
|
+
* True for the OPTIONS request the CORS layer answers by itself. Asked
|
|
191
|
+
* twice: once to build the 204, once at the end of render() to decide
|
|
192
|
+
* which form of the headers goes on. Asking once and letting the tail
|
|
193
|
+
* apply the ordinary headers on top of the 204's would put both forms on
|
|
194
|
+
* a preflight, answering `Vary: Origin, Origin` and an
|
|
195
|
+
* Access-Control-Expose-Headers that means nothing before a request.
|
|
196
|
+
*/
|
|
197
|
+
private isCorsPreflight;
|
|
363
198
|
private resolveRequest;
|
|
364
199
|
render(event: LambderHttpEvent, lambdaContext: Context): Promise<LambderHttpResponse>;
|
|
200
|
+
/**
|
|
201
|
+
* Fetch the session for a session route or short-circuit it with the
|
|
202
|
+
* sessionExpiredRouteHandler response (default 401). Session APIs never
|
|
203
|
+
* come through here: the pipeline answers them with the protocol's
|
|
204
|
+
* { sessionExpired: true } envelope itself.
|
|
205
|
+
*/
|
|
206
|
+
private requireSession;
|
|
207
|
+
/** Dispatch a non-HTTP Lambda event to the registered actions. */
|
|
208
|
+
renderEvent(event: unknown, lambdaContext: Context): Promise<unknown>;
|
|
209
|
+
/** Registration-time checks shared by addApi/addSessionApi. */
|
|
210
|
+
private assertApiRegistration;
|
|
211
|
+
/**
|
|
212
|
+
* The answer for a rejected input: the app's
|
|
213
|
+
* setApiInputValidationErrorHandler when set, otherwise the standard 422
|
|
214
|
+
* body. The API's own schema and every preflight slice (guard inputs,
|
|
215
|
+
* rate-limit keys) answer through here, so one failure has one shape.
|
|
216
|
+
*/
|
|
217
|
+
private inputValidationRefusal;
|
|
218
|
+
/**
|
|
219
|
+
* One API call through the core: the pipeline runs the protocol steps and
|
|
220
|
+
* calls back for the handler, whose LambderResponse (returned, or thrown
|
|
221
|
+
* via res.die.*) becomes the answer the pipeline stores and hands back.
|
|
222
|
+
* The context is the pipeline's context, so a session it fetched is on
|
|
223
|
+
* ctx.session and the validated payload is on ctx.apiPayload when the
|
|
224
|
+
* handler runs.
|
|
225
|
+
*/
|
|
226
|
+
private runApi;
|
|
227
|
+
/** A thrown LambderApiRefusal (from a hook, say) as the structured API envelope: the core's one mapping. */
|
|
228
|
+
private apiErrorResponse;
|
|
365
229
|
}
|
|
366
230
|
/**
|
|
367
231
|
* The canonical way to create an instance: fix the session data type first,
|
|
@@ -375,7 +239,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
375
239
|
* // app.ts (imports no api modules, so modules can import the type back)
|
|
376
240
|
* export const lambderApp = initLambder<SessionData>().create({
|
|
377
241
|
* apiPath: "/api",
|
|
378
|
-
* session: { tableName: "app-session",
|
|
242
|
+
* session: { store: new LambderDdbSessionStore({ tableName: "app-session", region: "us-east-1" }), sessionSalt: "..." },
|
|
379
243
|
* rateLimits: { limiter, policies },
|
|
380
244
|
* guards,
|
|
381
245
|
* idempotency: { store },
|
|
@@ -399,7 +263,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
399
263
|
* for untyped or session-data-free instances.
|
|
400
264
|
*/
|
|
401
265
|
export declare const initLambder: <TSessionData = any>() => {
|
|
402
|
-
create<const TOptions extends LambderCreateOptions<TSessionData>>(options: TOptions): Lambder<TSessionData, {}, TOptions["rateLimits"] extends {
|
|
266
|
+
create<const TOptions extends LambderCreateOptions<TSessionData>>(options: LambderNoExtraKeys<TOptions, LambderCreateOptions<TSessionData>> & LambderNestedOptionChecks<TSessionData, TOptions>): Lambder<TSessionData, {}, TOptions["rateLimits"] extends {
|
|
403
267
|
policies: infer TPolicies extends Record<string, LambderApiRateLimitPolicyConfig>;
|
|
404
|
-
} ? TPolicies : {}, TOptions["guards"] extends Record<string, LambderApiGuard<any, any, any>> ? LambderGuardMetaMap<TOptions["guards"]> : {}, TOptions["idempotency"] extends LambderApiIdempotencyConfig ? true : false, TOptions
|
|
268
|
+
} ? TPolicies : {}, TOptions["guards"] extends Record<string, LambderApiGuard<any, any, any>> ? LambderGuardMetaMap<TOptions["guards"]> : {}, TOptions["idempotency"] extends LambderApiIdempotencyConfig ? true : false, [LambderGivenOption<TOptions, "requireSessionApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "requirePublicApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "session">] extends [undefined] ? false : true>;
|
|
405
269
|
};
|