lambder 7.2.4 → 7.3.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 +88 -0
- package/README.md +4 -2
- package/dist/api/LambderApiIdempotency.d.ts +7 -0
- package/dist/api/LambderApiIdempotency.js +12 -0
- package/dist/api/LambderApiPipeline.d.ts +30 -1
- package/dist/api/LambderApiPipeline.js +14 -0
- package/dist/api/LambderApiPolicyEngine.d.ts +11 -0
- package/dist/api/LambderApiPolicyEngine.js +8 -0
- package/dist/api/LambderApiRateLimits.d.ts +7 -0
- package/dist/api/LambderApiRateLimits.js +12 -0
- package/dist/core/Lambder.d.ts +35 -1
- package/dist/core/Lambder.js +32 -1
- package/dist/core/LambderFiles.d.ts +7 -0
- package/dist/core/LambderFiles.js +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/invoke/LambderLambdaEvent.d.ts +23 -9
- package/dist/invoke/LambderLambdaEvent.js +41 -16
- package/dist/invoke/lambderHandlerTransport.d.ts +3 -0
- package/dist/invoke/lambderHandlerTransport.js +1 -1
- package/dist/mock.d.ts +2 -0
- package/dist/mock.js +3 -0
- package/dist/session/LambderSessionManager.d.ts +12 -1
- package/dist/session/LambderSessionManager.js +19 -3
- package/dist/shared/util/LambderTestingDoors.d.ts +29 -0
- package/dist/shared/util/LambderTestingDoors.js +29 -0
- package/dist/shared/wire/LambderApiContract.d.ts +50 -0
- package/dist/shared/wire/LambderOutcomeAssertions.d.ts +80 -0
- package/dist/shared/wire/LambderOutcomeAssertions.js +113 -0
- package/dist/testing/LambderTestApp.d.ts +178 -0
- package/dist/testing/LambderTestApp.js +206 -0
- package/dist/testing/LambderTestVisitor.d.ts +155 -0
- package/dist/testing/LambderTestVisitor.js +154 -0
- package/dist/testing.d.ts +26 -0
- package/dist/testing.js +23 -0
- package/package.json +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,94 @@ sit on its first published patch, and later patches list only what they changed.
|
|
|
9
9
|
Releases up to 3.2.6 carry git tags; the ones after it were published without
|
|
10
10
|
one, so versions are not cross-linked to tag comparisons here.
|
|
11
11
|
|
|
12
|
+
## [7.3.1] - 2026-09-21
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`lambder/testing`: a real app under test, in one call.** `lambderTestApp(lambder)`
|
|
17
|
+
takes the instance an app already exports and returns a `LambderTestApp`:
|
|
18
|
+
memory stores put under the instance, and simulated browsers in front of it,
|
|
19
|
+
with no HTTP and no AWS. Every piece existed before (the in-process handler
|
|
20
|
+
transport, the cookie jar, the memory stores), but each app had to assemble
|
|
21
|
+
them, and none could do the one thing that needs the framework's help: an
|
|
22
|
+
app is a module-level instance whose handlers and guards close over it, so a
|
|
23
|
+
test cannot be handed a copy over other stores, because the copy's closures
|
|
24
|
+
still reach the original and the production table under it. The stores are
|
|
25
|
+
therefore replaced **in place**, through a door keyed by a symbol no entry
|
|
26
|
+
point exports, so nothing on an instance's typed surface offers it to a
|
|
27
|
+
serving app. Nothing about the app is restructured, its guards, policies,
|
|
28
|
+
session model, hooks and error handlers all run as written, and from the
|
|
29
|
+
moment the test app exists the stores the app was created with are out of
|
|
30
|
+
the instance's reach.
|
|
31
|
+
- **Visitors.** `app.visitor()` is one simulated browser: its own cookie
|
|
32
|
+
jar, its own address (so a `per: "ip"` limit counts visitors apart, as it
|
|
33
|
+
does for two people in production), a typed `api` / `apiOutcome` over the
|
|
34
|
+
real pipeline, and `request(method, path)` for everything that is not an
|
|
35
|
+
API call (routes, redirects, session routes, public files), sharing the
|
|
36
|
+
same jar. `app.signIn(sessionKey, data)` returns a visitor already signed
|
|
37
|
+
in, minted by the app's own session model, so no login endpoint has to
|
|
38
|
+
exist. It throws, and says why, when the app's cookie domain does not
|
|
39
|
+
cover the visitor's host, instead of leaving every later call to answer
|
|
40
|
+
`sessionExpired`. `guardInputsProvider`, `headers`, `host`, `clientIp`,
|
|
41
|
+
`apiVersion` and `apiSignatures` are per visitor.
|
|
42
|
+
- **The mock app's verbs**, over the real handlers: `signIn`, `signOut`,
|
|
43
|
+
`expireSessionData`, `reset`, plus `event()` for what is not an HTTP
|
|
44
|
+
request (a schedule, SNS, SQS) and the stores themselves for assertions.
|
|
45
|
+
- **What the app threw.** A crash is answered by the app's global error
|
|
46
|
+
handler or by the framework's 500, and either answer deliberately says
|
|
47
|
+
nothing about the error, which leaves a test author with "status 500". The
|
|
48
|
+
test app watches what the instance throws without changing what it
|
|
49
|
+
answers: `visitor.apiOutcome` sets the thrown error as the failure's
|
|
50
|
+
`error.cause`, per call even with others in flight, and `app.crashes`
|
|
51
|
+
lists every one since the last reset.
|
|
52
|
+
- **Options** sit where `create()` takes them (`session.store`,
|
|
53
|
+
`rateLimits.limiter`, `idempotency.store`, `files`), each defaulting to a
|
|
54
|
+
fresh memory store, or for `files` to the app's own source.
|
|
55
|
+
- **`eventFormat: "v1" | "v2"`** (default `"v2"`): the gateway shape the
|
|
56
|
+
handler is called with, so an app behind a REST API can run its suite on
|
|
57
|
+
the event production delivers. `synthesizeLambdaHttpEvent` and
|
|
58
|
+
`lambderHandlerTransport` take the same option; a REST API event carries
|
|
59
|
+
its cookies in the Cookie header and every header in both delivery forms,
|
|
60
|
+
and the asserted client address stays the gateway's observation
|
|
61
|
+
(`requestContext.identity.sourceIp`) rather than a header, as on 2.0.
|
|
62
|
+
- The test app owns no clock, on purpose: faking `Date` with the test runner
|
|
63
|
+
moves the framework, the memory stores and the app's own handlers
|
|
64
|
+
together, which a clock of the test app's could not.
|
|
65
|
+
- **`assertApiSuccess` and `assertApiFailure`**, from `lambder/testing` and
|
|
66
|
+
`lambder/mock`. They narrow an outcome through an `asserts` signature, so
|
|
67
|
+
what the proved arm carries reads on the next line, and they throw a plain
|
|
68
|
+
Error naming what the outcome actually was, with the failure's own error as
|
|
69
|
+
its cause. `assertApiFailure(outcome, reason?, { code?, status? })` types
|
|
70
|
+
the reason against the union it was handed, so a misspelled one does not
|
|
71
|
+
compile, and narrows per arm, since `server` shares its arm with three other
|
|
72
|
+
reasons and `Extract` would have dropped it. Typed structurally over `ok`
|
|
73
|
+
and `reason`, so a `LambderInvokeOutcome` narrows through the same two
|
|
74
|
+
functions, and no test runner is imported.
|
|
75
|
+
- **[docs/testing.md](./docs/testing.md)**: testing a Lambder app in one page.
|
|
76
|
+
It was spread across ten pages as asides before.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- The session manager asks "may this crypto sit in front of this store" of
|
|
81
|
+
every store it is given, a swapped one included, rather than only in its
|
|
82
|
+
constructor.
|
|
83
|
+
- **The suite uses its own tools where the subject is what an app does.**
|
|
84
|
+
The routing, hook, plugin, action, redirect and thrown-response tests run
|
|
85
|
+
on the test app: a visitor's `request()` in place of a hand-built event, a
|
|
86
|
+
context and a body decoder, `app.event()` for the non-HTTP actions, a typed
|
|
87
|
+
`api()` for the plugin tests (which now also prove the contract flows
|
|
88
|
+
through `use()` to a caller), and `signIn()` in place of session records
|
|
89
|
+
forged with an SDK mock and hand-made hashes. Seven files lost their private
|
|
90
|
+
copies of the event and context builders. The hand-written `if(!outcome.ok)`
|
|
91
|
+
narrowing in the mock app's, the invoke caller's and the transports' tests
|
|
92
|
+
became the two assertions.
|
|
93
|
+
- The tests whose subject is the wire stay on hand-built events, because the
|
|
94
|
+
event or the raw answer is what they test: event parsing, response
|
|
95
|
+
finalization, compression, cookies and CORS headers, the policy and refusal
|
|
96
|
+
answers (status, envelope, Retry-After), error answers, the two gateway
|
|
97
|
+
formats, and the transports themselves. `tests/helpers.ts` says which is
|
|
98
|
+
which.
|
|
99
|
+
|
|
12
100
|
## [7.2.4] - 2026-09-19
|
|
13
101
|
|
|
14
102
|
### Changed
|
package/README.md
CHANGED
|
@@ -99,13 +99,14 @@ so if you need a specific one, install it and bundle it yourself.
|
|
|
99
99
|
|
|
100
100
|
## Package entry points
|
|
101
101
|
|
|
102
|
-
The package ships
|
|
102
|
+
The package ships four entry points; pick by where the code runs:
|
|
103
103
|
|
|
104
104
|
| Entry | Runs in | Carries |
|
|
105
105
|
| --- | --- | --- |
|
|
106
106
|
| `lambder` | Server (Lambda) | The full framework: pipeline, sessions, DDB stores, policies, plus the API core's building blocks and everything from `lambder/client` except the two request-compression helpers, `compressPayloadGzip` and `isRequestCompressionAvailable`, which stay on the client entry where a payload is compressed |
|
|
107
107
|
| `lambder/client` | Browser and isomorphic shared code | `LambderCaller`, `LambderApiRefusal`/`refuse`, the API contract and envelope types, `html`/`xml` tagged templates, `createLambderI18n` |
|
|
108
108
|
| `lambder/mock` | Browser and Node, in development and tests | `LambderMockApp`, the mock runtime: your typed contract served from mock handlers over the real API pipeline and memory stores |
|
|
109
|
+
| `lambder/testing` | Node, in tests | `lambderTestApp`: your real instance under test in this process, memory stores put under it in place, simulated browsers with typed callers in front of it, and the outcome assertions |
|
|
109
110
|
|
|
110
111
|
Frontends and shared isomorphic packages should import from `lambder/client`
|
|
111
112
|
only; the entry's module graph contains no AWS SDK, Node built-ins, or server
|
|
@@ -142,6 +143,7 @@ guide that matches what you are building. The full index lives in
|
|
|
142
143
|
| [Sessions](./docs/sessions.md) | Sessions over a store, cookie scope, secrets at rest, `dataRefresh`, the controller API |
|
|
143
144
|
| [API policies](./docs/api-policies.md) | Declarative rate limits, guards and idempotency, and mandatory authorization declarations |
|
|
144
145
|
| [Calling another lambda](./docs/invoke.md) | `LambderInvokeCaller`: invoking a Lambder app in another function, its contract, failures and compression |
|
|
146
|
+
| [Testing](./docs/testing.md) | `lambderTestApp`: the real instance under test with no HTTP and no AWS, visitors, sign-in without a login endpoint, outcome assertions, crashes, time |
|
|
145
147
|
| [The API core](./docs/api-core.md) | `LambderApiPipeline`: the one pipeline the server and the mock runtime run, the store interfaces, the transports |
|
|
146
148
|
| [Frontend client](./docs/client.md) | `LambderCaller`: typed calls, failure outcomes, timeouts, guard inputs, request compression, transports |
|
|
147
149
|
| [Frontend hosting](./docs/frontend-hosting.md) | File sources, `servePublicFiles`, `serveIndexHtml`, `res.templateFile` |
|
|
@@ -149,7 +151,7 @@ guide that matches what you are building. The full index lives in
|
|
|
149
151
|
| [Translations](./docs/i18n.md) | `createLambderI18n`: typed keys, extension, detection, on-demand languages, runtime dictionaries |
|
|
150
152
|
| [The mock runtime](./docs/mock.md) | `LambderMockApp`: the typed contract served from mock handlers over the real pipeline, in the browser and in tests |
|
|
151
153
|
| [DynamoDB tables](./docs/dynamodb-tables.md) | Table shapes, TTL and IAM for sessions, cache, rate limits and idempotency |
|
|
152
|
-
| [Exports reference](./docs/exports.md) | Every name the
|
|
154
|
+
| [Exports reference](./docs/exports.md) | Every name the four entry points export, grouped by purpose |
|
|
153
155
|
|
|
154
156
|
## Standalone modules
|
|
155
157
|
|
|
@@ -3,6 +3,7 @@ import type { LambderApiCallContext, LambderApiCallTrace } from "./LambderApiCal
|
|
|
3
3
|
import type { LambderApiAnswer } from "./LambderApiAnswer.js";
|
|
4
4
|
import type { LambderIdempotencyStore } from "../shared/contracts/LambderIdempotencyStore.js";
|
|
5
5
|
import type { LambderApiIdempotencyOption } from "../shared/wire/LambderApiOptionValues.js";
|
|
6
|
+
import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
|
|
6
7
|
export type LambderApiIdempotencyConfig = {
|
|
7
8
|
/** Your idempotency store instance; may share the rate limiter's table (distinct key prefix). */
|
|
8
9
|
store: LambderIdempotencyStore;
|
|
@@ -66,6 +67,12 @@ export declare class LambderApiIdempotencyEngine {
|
|
|
66
67
|
*/
|
|
67
68
|
private readonly scopeByCall;
|
|
68
69
|
configure(config: LambderApiIdempotencyConfig): void;
|
|
70
|
+
/**
|
|
71
|
+
* Puts the engine over another store, for `lambder/testing`; the replay
|
|
72
|
+
* TTLs, failOpen and callerIdentity stay as configured. False when
|
|
73
|
+
* idempotency was never configured.
|
|
74
|
+
*/
|
|
75
|
+
[LAMBDER_BACKEND_SWAP](store: LambderIdempotencyStore): boolean;
|
|
69
76
|
/** Startup validation of one API registration's idempotency option. */
|
|
70
77
|
assertRegistration(apiName: string, config: LambderApiIdempotencyOption): void;
|
|
71
78
|
/** True once the idempotency option was configured; registration asserts check it. */
|
|
@@ -2,6 +2,7 @@ import { getAnswerHeader } from "../shared/wire/LambderAnswerHeaders.js";
|
|
|
2
2
|
import { LambderApiRefusal, LAMBDER_REFUSAL_CODES } from "../shared/wire/LambderApiRefusal.js";
|
|
3
3
|
import { joinKeyFields } from "../shared/util/LambderKeyFields.js";
|
|
4
4
|
import { assertPositiveInteger } from "../shared/util/LambderOptionChecks.js";
|
|
5
|
+
import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
|
|
5
6
|
/**
|
|
6
7
|
* A crashed original must not block retries forever, so a pending claim
|
|
7
8
|
* expires on its own. The default is five minutes, which covers the great
|
|
@@ -102,6 +103,17 @@ export class LambderApiIdempotencyEngine {
|
|
|
102
103
|
this.failOpen = config.failOpen ?? true;
|
|
103
104
|
this.callerIdentity = config.callerIdentity;
|
|
104
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Puts the engine over another store, for `lambder/testing`; the replay
|
|
108
|
+
* TTLs, failOpen and callerIdentity stay as configured. False when
|
|
109
|
+
* idempotency was never configured.
|
|
110
|
+
*/
|
|
111
|
+
[LAMBDER_BACKEND_SWAP](store) {
|
|
112
|
+
if (!this.store)
|
|
113
|
+
return false;
|
|
114
|
+
this.store = store;
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
105
117
|
/** Startup validation of one API registration's idempotency option. */
|
|
106
118
|
assertRegistration(apiName, config) {
|
|
107
119
|
if (typeof config !== "object")
|
|
@@ -8,7 +8,10 @@ import { type LambderApiSignatureMap } from "../shared/wire/LambderApiSignature.
|
|
|
8
8
|
import type { LambderApiGuard } from "./LambderApiGuards.js";
|
|
9
9
|
import type { LambderApiRateLimitPolicyConfig, LambderApiRateLimitsConfig } from "./LambderApiRateLimits.js";
|
|
10
10
|
import type { LambderApiIdempotencyConfig } from "./LambderApiIdempotency.js";
|
|
11
|
-
import type { LambderSessionRecord } from "../shared/contracts/LambderSessionStore.js";
|
|
11
|
+
import type { LambderSessionRecord, LambderSessionStore } from "../shared/contracts/LambderSessionStore.js";
|
|
12
|
+
import type { LambderRateLimiter } from "../shared/contracts/LambderRateLimiter.js";
|
|
13
|
+
import type { LambderIdempotencyStore } from "../shared/contracts/LambderIdempotencyStore.js";
|
|
14
|
+
import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
|
|
12
15
|
import type LambderSessionManager from "../session/LambderSessionManager.js";
|
|
13
16
|
import LambderSessionController, { type LambderSessionCookieOptions, type LambderSessionRequestInfo } from "../session/LambderSessionController.js";
|
|
14
17
|
import type { MaybePromise } from "../shared/util/LambderTypeUtilities.js";
|
|
@@ -57,6 +60,26 @@ export type LambderApiPipelineOptions<TCtx extends LambderApiCallContext<TSessio
|
|
|
57
60
|
}>>;
|
|
58
61
|
idempotency?: LambderApiIdempotencyConfig;
|
|
59
62
|
};
|
|
63
|
+
/** The stores `lambder/testing` puts under a built pipeline. One the pipeline has no subsystem for is left aside. */
|
|
64
|
+
export type LambderPipelineBackends = {
|
|
65
|
+
sessionStore?: LambderSessionStore<any>;
|
|
66
|
+
rateLimiter?: LambderRateLimiter;
|
|
67
|
+
idempotencyStore?: LambderIdempotencyStore;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* What a backend swap found: whether the rate-limit and idempotency
|
|
71
|
+
* subsystems exist to take a store, and, when sessions are configured, the
|
|
72
|
+
* cookie names a caller over this pipeline has to be told (they are the
|
|
73
|
+
* app's own choice and nothing else hands them out).
|
|
74
|
+
*/
|
|
75
|
+
export type LambderPipelineBackendSwap = {
|
|
76
|
+
sessions: {
|
|
77
|
+
tokenCookieKey: string;
|
|
78
|
+
csrfCookieKey: string;
|
|
79
|
+
} | null;
|
|
80
|
+
rateLimits: boolean;
|
|
81
|
+
idempotency: boolean;
|
|
82
|
+
};
|
|
60
83
|
/** What one run produced, beside the answer: what an adapter may want to report. */
|
|
61
84
|
export type LambderApiRunResult = LambderApiCallTrace & {
|
|
62
85
|
answer: LambderApiAnswer;
|
|
@@ -105,6 +128,12 @@ export declare class LambderApiPipeline<TCtx extends LambderApiCallContext<TSess
|
|
|
105
128
|
* (its cookies and posted CSRF token) or a route's (cookies and no CSRF).
|
|
106
129
|
*/
|
|
107
130
|
sessionController(ctx: TCtx, request: LambderSessionRequestInfo): LambderSessionController<TSessionData>;
|
|
131
|
+
/**
|
|
132
|
+
* The backend swap: each store given goes under the subsystem that holds
|
|
133
|
+
* one, and everything the app configured around it (the session model,
|
|
134
|
+
* the named policies, the replay TTLs) stays in force.
|
|
135
|
+
*/
|
|
136
|
+
[LAMBDER_BACKEND_SWAP](backends: LambderPipelineBackends): LambderPipelineBackendSwap;
|
|
108
137
|
/** The session request info of an API request: its cookies, and the CSRF token it posted. */
|
|
109
138
|
static sessionInfoOf(request: LambderApiRequest): LambderSessionRequestInfo;
|
|
110
139
|
/** Registration-time checks of one definition's declarative options; the same messages on the server and in the mock. */
|
|
@@ -7,6 +7,7 @@ import { DEFAULT_MAX_RESTORED_PAYLOAD_BYTES } from "../shared/wire/LambderReques
|
|
|
7
7
|
import { assertPositiveInteger } from "../shared/util/LambderOptionChecks.js";
|
|
8
8
|
import { compareDottedVersions, isDottedVersion } from "../shared/wire/LambderVersionOrder.js";
|
|
9
9
|
import { LambderApiPolicyEngine } from "./LambderApiPolicyEngine.js";
|
|
10
|
+
import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
|
|
10
11
|
import LambderSessionController, { assertSessionCookiePrefixes, } from "../session/LambderSessionController.js";
|
|
11
12
|
import { DEFAULT_SESSION_CSRF_COOKIE_KEY, DEFAULT_SESSION_TOKEN_COOKIE_KEY } from "../shared/wire/LambderSessionCookieNames.js";
|
|
12
13
|
/**
|
|
@@ -109,6 +110,19 @@ export class LambderApiPipeline {
|
|
|
109
110
|
request,
|
|
110
111
|
});
|
|
111
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* The backend swap: each store given goes under the subsystem that holds
|
|
115
|
+
* one, and everything the app configured around it (the session model,
|
|
116
|
+
* the named policies, the replay TTLs) stays in force.
|
|
117
|
+
*/
|
|
118
|
+
[LAMBDER_BACKEND_SWAP](backends) {
|
|
119
|
+
if (this.sessions && backends.sessionStore)
|
|
120
|
+
this.sessions.manager[LAMBDER_BACKEND_SWAP](backends.sessionStore);
|
|
121
|
+
return {
|
|
122
|
+
sessions: this.sessions ? { tokenCookieKey: this.sessions.tokenCookieKey, csrfCookieKey: this.sessions.csrfCookieKey } : null,
|
|
123
|
+
...this.policies[LAMBDER_BACKEND_SWAP](backends),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
112
126
|
/** The session request info of an API request: its cookies, and the CSRF token it posted. */
|
|
113
127
|
static sessionInfoOf(request) {
|
|
114
128
|
return { host: request.host, cookies: request.cookies, csrfToken: request.token };
|
|
@@ -5,6 +5,9 @@ import type { LambderApiDefinition } from "./LambderApiDefinition.js";
|
|
|
5
5
|
import { type LambderApiGuard } from "./LambderApiGuards.js";
|
|
6
6
|
import { type LambderApiRateLimitPolicyConfig, type LambderApiRateLimitsConfig } from "./LambderApiRateLimits.js";
|
|
7
7
|
import { type LambderApiIdempotencyConfig } from "./LambderApiIdempotency.js";
|
|
8
|
+
import type { LambderRateLimiter } from "../shared/contracts/LambderRateLimiter.js";
|
|
9
|
+
import type { LambderIdempotencyStore } from "../shared/contracts/LambderIdempotencyStore.js";
|
|
10
|
+
import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
|
|
8
11
|
/**
|
|
9
12
|
* Runtime side of the declarative API options: composes the three policy
|
|
10
13
|
* subsystems (rate limits in ./LambderApiRateLimits.ts, guards in
|
|
@@ -23,6 +26,14 @@ export declare class LambderApiPolicyEngine {
|
|
|
23
26
|
configureRateLimits(config: LambderApiRateLimitsConfig<Record<string, LambderApiRateLimitPolicyConfig>>): void;
|
|
24
27
|
configureGuards(guards: Record<string, LambderApiGuard<any, any, any>>): void;
|
|
25
28
|
configureIdempotency(config: LambderApiIdempotencyConfig): void;
|
|
29
|
+
/** The backend swap, handed on to the two subsystems that hold a store. Each answers whether it had a place for one. */
|
|
30
|
+
[LAMBDER_BACKEND_SWAP](backends: {
|
|
31
|
+
rateLimiter?: LambderRateLimiter;
|
|
32
|
+
idempotencyStore?: LambderIdempotencyStore;
|
|
33
|
+
}): {
|
|
34
|
+
rateLimits: boolean;
|
|
35
|
+
idempotency: boolean;
|
|
36
|
+
};
|
|
26
37
|
/** Startup validation of one API's declarative options. */
|
|
27
38
|
assertRegistration(definition: LambderApiDefinition): void;
|
|
28
39
|
/** The rate-limit policies that can be checked before the session is read: see LambderApiRateLimitsEngine.run. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LambderApiGuardsEngine } from "./LambderApiGuards.js";
|
|
2
2
|
import { LambderApiRateLimitsEngine } from "./LambderApiRateLimits.js";
|
|
3
3
|
import { LambderApiIdempotencyEngine } from "./LambderApiIdempotency.js";
|
|
4
|
+
import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
|
|
4
5
|
/** An API that asks for idempotency: declared, and not the explicit `false` opt-out. */
|
|
5
6
|
const usesIdempotency = (definition) => definition.idempotency !== undefined && definition.idempotency !== false;
|
|
6
7
|
/**
|
|
@@ -29,6 +30,13 @@ export class LambderApiPolicyEngine {
|
|
|
29
30
|
configureIdempotency(config) {
|
|
30
31
|
this.idempotency.configure(config);
|
|
31
32
|
}
|
|
33
|
+
/** The backend swap, handed on to the two subsystems that hold a store. Each answers whether it had a place for one. */
|
|
34
|
+
[LAMBDER_BACKEND_SWAP](backends) {
|
|
35
|
+
return {
|
|
36
|
+
rateLimits: backends.rateLimiter ? this.rateLimits[LAMBDER_BACKEND_SWAP](backends.rateLimiter) : false,
|
|
37
|
+
idempotency: backends.idempotencyStore ? this.idempotency[LAMBDER_BACKEND_SWAP](backends.idempotencyStore) : false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
32
40
|
/** Startup validation of one API's declarative options. */
|
|
33
41
|
assertRegistration(definition) {
|
|
34
42
|
const { name, mode } = definition;
|
|
@@ -6,6 +6,7 @@ import type { LambderApiCallContext } from "./LambderApiCallContext.js";
|
|
|
6
6
|
import { type LambderRateLimiter, type LambderRateLimitPolicy } from "../shared/contracts/LambderRateLimiter.js";
|
|
7
7
|
import { LambderApiRefusal, type LambderAppRefusalMessage } from "../shared/wire/LambderApiRefusal.js";
|
|
8
8
|
import type { LambderNonEmptyOptionMap } from "../shared/util/LambderTypeUtilities.js";
|
|
9
|
+
import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
|
|
9
10
|
/** Refusal a rate-limited request answers unless the policy or the API's override names its own. */
|
|
10
11
|
export declare const DEFAULT_RATE_LIMIT_REFUSAL: {
|
|
11
12
|
type: "warning";
|
|
@@ -184,6 +185,12 @@ export declare class LambderApiRateLimitsEngine {
|
|
|
184
185
|
/** True once rateLimits were configured. */
|
|
185
186
|
get isConfigured(): boolean;
|
|
186
187
|
configure(config: LambderApiRateLimitsConfig<Record<string, LambderApiRateLimitPolicyConfig>>): void;
|
|
188
|
+
/**
|
|
189
|
+
* Puts the engine over another limiter, for `lambder/testing`; the named
|
|
190
|
+
* policies and failOpen stay as configured. False when rateLimits were
|
|
191
|
+
* never configured: there is nothing for a limiter to sit under.
|
|
192
|
+
*/
|
|
193
|
+
[LAMBDER_BACKEND_SWAP](limiter: LambderRateLimiter): boolean;
|
|
187
194
|
/** Startup validation of one API registration's rateLimit option. */
|
|
188
195
|
assertRegistration(apiName: string, mode: LambderApiMode, rateLimitOption?: LambderRateLimitOptionValue): void;
|
|
189
196
|
/**
|
|
@@ -4,6 +4,7 @@ import { RATE_LIMIT_WINDOWS, } from "../shared/contracts/LambderRateLimiter.js";
|
|
|
4
4
|
import { LambderApiRefusal, LAMBDER_REFUSAL_CODES } from "../shared/wire/LambderApiRefusal.js";
|
|
5
5
|
import { parsePreflightSlice } from "./LambderApiValidationRefusal.js";
|
|
6
6
|
import { assertNonNegativeInteger } from "../shared/util/LambderOptionChecks.js";
|
|
7
|
+
import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
|
|
7
8
|
const RATE_LIMIT_WINDOW_KEYS = RATE_LIMIT_WINDOWS.map((window) => window.key);
|
|
8
9
|
/** Refusal a rate-limited request answers unless the policy or the API's override names its own. */
|
|
9
10
|
export const DEFAULT_RATE_LIMIT_REFUSAL = { type: "warning", code: LAMBDER_REFUSAL_CODES.rateLimited, content: "Too many requests. Please try again later." };
|
|
@@ -139,6 +140,17 @@ export class LambderApiRateLimitsEngine {
|
|
|
139
140
|
this.failOpen = config.failOpen ?? true;
|
|
140
141
|
this.policies = new Map(Object.entries(config.policies));
|
|
141
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Puts the engine over another limiter, for `lambder/testing`; the named
|
|
145
|
+
* policies and failOpen stay as configured. False when rateLimits were
|
|
146
|
+
* never configured: there is nothing for a limiter to sit under.
|
|
147
|
+
*/
|
|
148
|
+
[LAMBDER_BACKEND_SWAP](limiter) {
|
|
149
|
+
if (!this.limiter)
|
|
150
|
+
return false;
|
|
151
|
+
this.limiter = limiter;
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
142
154
|
/** Startup validation of one API registration's rateLimit option. */
|
|
143
155
|
assertRegistration(apiName, mode, rateLimitOption) {
|
|
144
156
|
const entries = toRateLimitEntries(rateLimitOption);
|
package/dist/core/Lambder.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ import type LambderSessionController from "../session/LambderSessionController.j
|
|
|
9
9
|
import { type LambderPublicFilesOptions } from "./LambderPublicFiles.js";
|
|
10
10
|
import { type LambderIndexHtmlOptions } from "./LambderIndexHtml.js";
|
|
11
11
|
import { LambderFiles } from "./LambderFiles.js";
|
|
12
|
+
import { type LambderPipelineBackends, type LambderPipelineBackendSwap } from "../api/LambderApiPipeline.js";
|
|
13
|
+
import type { LambderFileSource } from "../shared/contracts/LambderFileSource.js";
|
|
14
|
+
import { LAMBDER_BACKEND_SWAP, LAMBDER_CRASH_WATCH } from "../shared/util/LambderTestingDoors.js";
|
|
12
15
|
import { type LambderApiSignatureEntry } from "../api/LambderApiSignature.js";
|
|
13
16
|
import { type LambderApiSignatureMap } from "../shared/wire/LambderApiSignature.js";
|
|
14
17
|
import type { LambderApiIdempotencyOption } from "../shared/wire/LambderApiOptionValues.js";
|
|
@@ -25,6 +28,14 @@ import { type LambderRouteHandler, type LambderInputValidationHandler, type Lamb
|
|
|
25
28
|
* because its parameter is the class, and an options module that names the
|
|
26
29
|
* class cannot be read without it.
|
|
27
30
|
*/
|
|
31
|
+
/** Everything `lambder/testing` may put under a built instance: the pipeline's stores, and the source its files are read from. */
|
|
32
|
+
export type LambderInstanceBackends = LambderPipelineBackends & {
|
|
33
|
+
fileSource?: LambderFileSource;
|
|
34
|
+
};
|
|
35
|
+
/** What the instance had a place for; see LambderPipelineBackendSwap. `files` is false on an instance created without the files option. */
|
|
36
|
+
export type LambderInstanceBackendSwap = LambderPipelineBackendSwap & {
|
|
37
|
+
files: boolean;
|
|
38
|
+
};
|
|
28
39
|
export type LambderCreatedHook = (lambderInstance: Lambder<any, any, any, any, any, any, any, any>) => void | Promise<void>;
|
|
29
40
|
/**
|
|
30
41
|
* Main Lambder class for building type-safe serverless APIs. Create
|
|
@@ -60,10 +71,17 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
60
71
|
* Type property for extracting the API contract
|
|
61
72
|
* Use this to export your API types to the frontend
|
|
62
73
|
*
|
|
74
|
+
* Export it as an interface extending LambderFlattenContract, not as a
|
|
75
|
+
* type alias. Chaining builds the contract as an intersection one member
|
|
76
|
+
* deep per endpoint, and an interface collapses that into one declared
|
|
77
|
+
* set of members, which every generic read of the contract (a mock
|
|
78
|
+
* registry, a needs map, the typed caller) is then far cheaper against.
|
|
79
|
+
* See LambderFlattenContract for the measurements.
|
|
80
|
+
*
|
|
63
81
|
* @example
|
|
64
82
|
* ```typescript
|
|
65
83
|
* const lambder = new Lambder().addApi(...).addApi(...);
|
|
66
|
-
* export
|
|
84
|
+
* export interface ApiContractType extends LambderFlattenContract<typeof lambder.ApiContract> {}
|
|
67
85
|
* ```
|
|
68
86
|
*/
|
|
69
87
|
readonly ApiContract: _TContract;
|
|
@@ -88,6 +106,8 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
88
106
|
private corsConfig;
|
|
89
107
|
private finalizeOptions;
|
|
90
108
|
private requireSessionApiGuards;
|
|
109
|
+
/** Told what a request threw, beside whatever answers it; null outside a test. See LAMBDER_CRASH_WATCH. */
|
|
110
|
+
private crashWatcher;
|
|
91
111
|
private readonly trustedClientIpHeaders;
|
|
92
112
|
private requirePublicApiGuards;
|
|
93
113
|
constructor(options?: LambderCreateOptions<TSessionData>);
|
|
@@ -171,6 +191,20 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
171
191
|
getSessionController(ctx: LambderRenderContext | LambderSessionRenderContext<any, TSessionData>): LambderSessionController<TSessionData>;
|
|
172
192
|
/** The session manager, for code that works on sessions outside a request (maintenance, tests). */
|
|
173
193
|
getSessionManager(): LambderSessionManager<TSessionData>;
|
|
194
|
+
/**
|
|
195
|
+
* The backend swap `lambder/testing` performs: the stores given go under
|
|
196
|
+
* this instance in place, so every handler and guard that closed over it
|
|
197
|
+
* reaches them, and the production ones are out of reach from then on.
|
|
198
|
+
* Keyed by a symbol no entry point exports, so it is not part of what an
|
|
199
|
+
* app can call; see LAMBDER_BACKEND_SWAP.
|
|
200
|
+
*/
|
|
201
|
+
[LAMBDER_BACKEND_SWAP](backends: LambderInstanceBackends): LambderInstanceBackendSwap;
|
|
202
|
+
/**
|
|
203
|
+
* The crash watch `lambder/testing` sets: told every error a request
|
|
204
|
+
* throws past the framework's own handling, before the global error
|
|
205
|
+
* handler or the last-resort 500 answers it. The answer is unchanged.
|
|
206
|
+
*/
|
|
207
|
+
[LAMBDER_CRASH_WATCH](watcher: (error: Error) => void): void;
|
|
174
208
|
/**
|
|
175
209
|
* Every registered endpoint's signature, keyed by its hashed name: the
|
|
176
210
|
* LambderApiSignatureMap both sides ship with. A generator imports the
|
package/dist/core/Lambder.js
CHANGED
|
@@ -10,6 +10,7 @@ import { LambderIndexHtmlHandler } from "./LambderIndexHtml.js";
|
|
|
10
10
|
import { LambderFiles } from "./LambderFiles.js";
|
|
11
11
|
import { isLambderApiRefusal } from "../shared/wire/LambderApiRefusal.js";
|
|
12
12
|
import { LambderApiPipeline } from "../api/LambderApiPipeline.js";
|
|
13
|
+
import { LAMBDER_BACKEND_SWAP, LAMBDER_CRASH_WATCH } from "../shared/util/LambderTestingDoors.js";
|
|
13
14
|
import { apiSignatureOf } from "../api/LambderApiSignature.js";
|
|
14
15
|
import { apiNameKeyOf } from "../shared/wire/LambderApiSignature.js";
|
|
15
16
|
import { apiNotFoundAnswer, crashAnswer, refusalAnswer, } from "../api/LambderApiEnvelope.js";
|
|
@@ -55,10 +56,17 @@ export default class Lambder {
|
|
|
55
56
|
* Type property for extracting the API contract
|
|
56
57
|
* Use this to export your API types to the frontend
|
|
57
58
|
*
|
|
59
|
+
* Export it as an interface extending LambderFlattenContract, not as a
|
|
60
|
+
* type alias. Chaining builds the contract as an intersection one member
|
|
61
|
+
* deep per endpoint, and an interface collapses that into one declared
|
|
62
|
+
* set of members, which every generic read of the contract (a mock
|
|
63
|
+
* registry, a needs map, the typed caller) is then far cheaper against.
|
|
64
|
+
* See LambderFlattenContract for the measurements.
|
|
65
|
+
*
|
|
58
66
|
* @example
|
|
59
67
|
* ```typescript
|
|
60
68
|
* const lambder = new Lambder().addApi(...).addApi(...);
|
|
61
|
-
* export
|
|
69
|
+
* export interface ApiContractType extends LambderFlattenContract<typeof lambder.ApiContract> {}
|
|
62
70
|
* ```
|
|
63
71
|
*/
|
|
64
72
|
ApiContract;
|
|
@@ -83,6 +91,8 @@ export default class Lambder {
|
|
|
83
91
|
corsConfig = null;
|
|
84
92
|
finalizeOptions;
|
|
85
93
|
requireSessionApiGuards;
|
|
94
|
+
/** Told what a request threw, beside whatever answers it; null outside a test. See LAMBDER_CRASH_WATCH. */
|
|
95
|
+
crashWatcher = null;
|
|
86
96
|
trustedClientIpHeaders;
|
|
87
97
|
requirePublicApiGuards;
|
|
88
98
|
constructor(options = {}) {
|
|
@@ -287,6 +297,26 @@ export default class Lambder {
|
|
|
287
297
|
getSessionManager() {
|
|
288
298
|
return this.pipeline.sessionManager;
|
|
289
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* The backend swap `lambder/testing` performs: the stores given go under
|
|
302
|
+
* this instance in place, so every handler and guard that closed over it
|
|
303
|
+
* reaches them, and the production ones are out of reach from then on.
|
|
304
|
+
* Keyed by a symbol no entry point exports, so it is not part of what an
|
|
305
|
+
* app can call; see LAMBDER_BACKEND_SWAP.
|
|
306
|
+
*/
|
|
307
|
+
[LAMBDER_BACKEND_SWAP](backends) {
|
|
308
|
+
if (this.files && backends.fileSource)
|
|
309
|
+
this.files[LAMBDER_BACKEND_SWAP](backends.fileSource);
|
|
310
|
+
return { ...this.pipeline[LAMBDER_BACKEND_SWAP](backends), files: this.files !== null };
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* The crash watch `lambder/testing` sets: told every error a request
|
|
314
|
+
* throws past the framework's own handling, before the global error
|
|
315
|
+
* handler or the last-resort 500 answers it. The answer is unchanged.
|
|
316
|
+
*/
|
|
317
|
+
[LAMBDER_CRASH_WATCH](watcher) {
|
|
318
|
+
this.crashWatcher = watcher;
|
|
319
|
+
}
|
|
290
320
|
/**
|
|
291
321
|
* Every registered endpoint's signature, keyed by its hashed name: the
|
|
292
322
|
* LambderApiSignatureMap both sides ship with. A generator imports the
|
|
@@ -545,6 +575,7 @@ export default class Lambder {
|
|
|
545
575
|
// client could parse. coerceToError is the shared version of that
|
|
546
576
|
// care, the one every site in the framework now uses.
|
|
547
577
|
const wrappedError = coerceToError(err, "an unstringifiable thrown value");
|
|
578
|
+
this.crashWatcher?.(wrappedError);
|
|
548
579
|
// ctx may be null (createContext failed): derive the format from the raw event.
|
|
549
580
|
const eventFormat = ctx?.eventFormat ?? (isV2HttpEvent(event) ? "v2" : "v1");
|
|
550
581
|
try {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LambderTemplatingEngine } from "./LambderTemplatingEngine.js";
|
|
2
2
|
import type { LambderFileSource } from "../shared/contracts/LambderFileSource.js";
|
|
3
|
+
import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
|
|
3
4
|
/** In-memory cache of files for warm invocations. Default: { maxBytes: 32MB, maxFileBytes: 2MB }. false disables it. */
|
|
4
5
|
export type LambderFileMemoryCacheOption = false | {
|
|
5
6
|
maxBytes?: number;
|
|
@@ -30,6 +31,12 @@ export declare class LambderFiles {
|
|
|
30
31
|
private maxFileBytes;
|
|
31
32
|
private templates;
|
|
32
33
|
constructor(option: LambderFilesOption);
|
|
34
|
+
/**
|
|
35
|
+
* Puts the reader over another source, for `lambder/testing`. In place,
|
|
36
|
+
* because servePublicFiles holds this reader rather than the instance's
|
|
37
|
+
* field; both caches go with the source they were filled from.
|
|
38
|
+
*/
|
|
39
|
+
[LAMBDER_BACKEND_SWAP](source: LambderFileSource): void;
|
|
33
40
|
/**
|
|
34
41
|
* The file at a request or handler path (leading slash optional), mime
|
|
35
42
|
* type resolved; null when the path is invalid or the source has none.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import mimeTypeResolver from "mime-types";
|
|
2
2
|
import { LambderTemplatingEngine } from "./LambderTemplatingEngine.js";
|
|
3
|
+
import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
|
|
3
4
|
const DEFAULT_MEMORY_CACHE_MAX_BYTES = 32 * 1024 * 1024;
|
|
4
5
|
const DEFAULT_MEMORY_CACHE_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
5
6
|
/**
|
|
@@ -45,6 +46,17 @@ export class LambderFiles {
|
|
|
45
46
|
this.maxBytes = memoryCache === false ? 0 : (memoryCache?.maxBytes ?? DEFAULT_MEMORY_CACHE_MAX_BYTES);
|
|
46
47
|
this.maxFileBytes = memoryCache === false ? 0 : (memoryCache?.maxFileBytes ?? DEFAULT_MEMORY_CACHE_MAX_FILE_BYTES);
|
|
47
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Puts the reader over another source, for `lambder/testing`. In place,
|
|
51
|
+
* because servePublicFiles holds this reader rather than the instance's
|
|
52
|
+
* field; both caches go with the source they were filled from.
|
|
53
|
+
*/
|
|
54
|
+
[LAMBDER_BACKEND_SWAP](source) {
|
|
55
|
+
this.source = source;
|
|
56
|
+
this.cache?.clear();
|
|
57
|
+
this.cacheBytes = 0;
|
|
58
|
+
this.templates.clear();
|
|
59
|
+
}
|
|
48
60
|
/**
|
|
49
61
|
* The file at a request or handler path (leading slash optional), mime
|
|
50
62
|
* type resolved; null when the path is invalid or the source has none.
|
package/dist/index.d.ts
CHANGED
|
@@ -104,7 +104,7 @@ export type { LambderApiIdempotencyConfig } from "./api/LambderApiIdempotency.js
|
|
|
104
104
|
export type { LambderGuardsOptionValue, LambderRateLimitOverride, LambderRateLimitOptionValue, LambderApiIdempotencyOption, } from "./shared/wire/LambderApiOptionValues.js";
|
|
105
105
|
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
106
106
|
export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nDictionaryLoader, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./shared/LambderI18n.js";
|
|
107
|
-
export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiResponseConfig, LambderApiNullAnswerConfig, LambderContractEntry, LambderMergeContract, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractRateLimitNames, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
|
|
107
|
+
export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiResponseConfig, LambderApiNullAnswerConfig, LambderContractEntry, LambderMergeContract, LambderFlattenContract, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractRateLimitNames, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
|
|
108
108
|
export type { LambderRenderContext, LambderSessionRenderContext, LambderHttpEvent, LambderHttpEventFormat } from "./core/LambderContext.js";
|
|
109
109
|
export type { LambderApiAnswerOutcome, LambderApiSuccessOutcome, LambderApiCallFailure, LambderApiValidationFailure, LambderApiEnvelopeFailure, LambderApiHttpAnswer, } from "./shared/wire/LambderApiOutcome.js";
|
|
110
110
|
export { resolveApiOutcome } from "./shared/wire/LambderApiOutcome.js";
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* its body envelope, and a carried session as the cookie pair it travels as.
|
|
9
9
|
* Server-only: Buffer and the codec's zlib restore.
|
|
10
10
|
*/
|
|
11
|
-
import type { APIGatewayProxyEventV2, Context } from "aws-lambda";
|
|
11
|
+
import type { APIGatewayProxyEvent, APIGatewayProxyEventV2, Context } from "aws-lambda";
|
|
12
|
+
import type { LambderHttpEventFormat } from "../core/LambderContext.js";
|
|
12
13
|
import type { LambderCompressedBrotliPayload, LambderCompressedGzipPayload } from "../shared/wire/LambderRequestPayload.js";
|
|
13
14
|
/** Marks a synthesized request as an invoke, for guards and hooks that want to tell. Not an authorization. */
|
|
14
15
|
export declare const LAMBDER_INVOKE_HEADER = "x-lambder-invoke";
|
|
@@ -34,19 +35,32 @@ export type LambderSynthesizedRequest = {
|
|
|
34
35
|
body?: string | Buffer;
|
|
35
36
|
};
|
|
36
37
|
/**
|
|
37
|
-
* The
|
|
38
|
+
* The event API Gateway would deliver for this request: payload format 2.0
|
|
39
|
+
* (an HTTP API, a Function URL) unless `eventFormat: "v1"` asks for the REST
|
|
40
|
+
* API's. An invoke is always 2.0; the other format is for an in-process call
|
|
41
|
+
* that wants the handler to meet the shape its own deployment delivers.
|
|
38
42
|
* `invoke: true` adds the invoke marker headers a server-to-server call
|
|
39
43
|
* carries; a browser-shaped request (the handler transport) leaves them off.
|
|
40
44
|
*
|
|
41
|
-
* The client address is `clientIp` and reaches the callee as
|
|
42
|
-
* requestContext.http.sourceIp
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
45
|
+
* The client address is `clientIp` and reaches the callee as the gateway's
|
|
46
|
+
* observed source address only (requestContext.http.sourceIp, or
|
|
47
|
+
* requestContext.identity.sourceIp on a REST API event). Writing it as
|
|
48
|
+
* x-forwarded-for as well would put the same fact on a channel a callee may
|
|
49
|
+
* be configured to trust (trustedClientIpHeaders), and the header is the one
|
|
50
|
+
* the caller's own `headers` could otherwise have set.
|
|
46
51
|
*/
|
|
47
|
-
export declare
|
|
52
|
+
export declare function synthesizeLambdaHttpEvent(request: LambderSynthesizedRequest, options: {
|
|
48
53
|
invoke: boolean;
|
|
49
|
-
|
|
54
|
+
eventFormat?: "v2";
|
|
55
|
+
}): APIGatewayProxyEventV2;
|
|
56
|
+
export declare function synthesizeLambdaHttpEvent(request: LambderSynthesizedRequest, options: {
|
|
57
|
+
invoke: boolean;
|
|
58
|
+
eventFormat: "v1";
|
|
59
|
+
}): APIGatewayProxyEvent;
|
|
60
|
+
export declare function synthesizeLambdaHttpEvent(request: LambderSynthesizedRequest, options: {
|
|
61
|
+
invoke: boolean;
|
|
62
|
+
eventFormat?: LambderHttpEventFormat;
|
|
63
|
+
}): APIGatewayProxyEventV2 | APIGatewayProxyEvent;
|
|
50
64
|
/**
|
|
51
65
|
* The body envelope LambderCaller sends, minus the fields only a browser has
|
|
52
66
|
* a value for, as JSON. A plain payload arrives already serialized (the
|