lambder 3.8.1 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +139 -50
- package/dist/{LambderCaller.d.ts → client/LambderCaller.d.ts} +30 -12
- package/dist/{LambderCaller.js → client/LambderCaller.js} +21 -12
- package/dist/{LambderMSW.d.ts → client/LambderMSW.d.ts} +1 -1
- package/dist/client.d.ts +16 -0
- package/dist/client.js +17 -0
- package/dist/{Lambder.d.ts → core/Lambder.d.ts} +107 -75
- package/dist/{Lambder.js → core/Lambder.js} +96 -76
- package/dist/{LambderContext.d.ts → core/LambderContext.d.ts} +9 -3
- package/dist/{LambderContext.js → core/LambderContext.js} +1 -0
- package/dist/{LambderPublicFiles.js → core/LambderPublicFiles.js} +1 -1
- package/dist/{LambderResponse.js → core/LambderResponse.js} +1 -1
- package/dist/{LambderResponseBuilder.d.ts → core/LambderResponseBuilder.d.ts} +3 -15
- package/dist/{LambderResponseBuilder.js → core/LambderResponseBuilder.js} +2 -2
- package/dist/{LambderTemplatingEngine.d.ts → core/LambderTemplatingEngine.d.ts} +1 -1
- package/dist/{LambderTemplatingEngine.js → core/LambderTemplatingEngine.js} +2 -2
- package/dist/index.d.ts +37 -35
- package/dist/index.js +22 -20
- package/dist/policies/LambderApiGuards.d.ts +221 -0
- package/dist/policies/LambderApiGuards.js +79 -0
- package/dist/policies/LambderApiIdempotency.d.ts +58 -0
- package/dist/policies/LambderApiIdempotency.js +215 -0
- package/dist/policies/LambderApiPolicies.d.ts +40 -0
- package/dist/policies/LambderApiPolicies.js +47 -0
- package/dist/policies/LambderApiRateLimits.d.ts +90 -0
- package/dist/policies/LambderApiRateLimits.js +77 -0
- package/dist/{LambderSessionController.d.ts → session/LambderSessionController.d.ts} +3 -2
- package/dist/{LambderSessionController.js → session/LambderSessionController.js} +17 -12
- package/dist/{LambderSessionManager.d.ts → session/LambderSessionManager.d.ts} +38 -4
- package/dist/{LambderSessionManager.js → session/LambderSessionManager.js} +50 -17
- package/dist/{LambderApiContract.d.ts → shared/LambderApiContract.d.ts} +14 -0
- package/dist/{LambderApiError.d.ts → shared/LambderApiError.d.ts} +1 -1
- package/dist/{LambderDdbCache.js → stores/LambderDdbCache.js} +7 -38
- package/dist/stores/LambderDdbCompression.d.ts +3 -0
- package/dist/stores/LambderDdbCompression.js +39 -0
- package/dist/{LambderDdbIdempotency.d.ts → stores/LambderDdbIdempotency.d.ts} +41 -12
- package/dist/{LambderDdbIdempotency.js → stores/LambderDdbIdempotency.js} +100 -11
- package/dist/{LambderDdbRateLimiter.js → stores/LambderDdbRateLimiter.js} +5 -1
- package/dist/testing.d.ts +9 -0
- package/dist/testing.js +8 -0
- package/package.json +19 -1
- package/dist/LambderApiPolicies.d.ts +0 -212
- package/dist/LambderApiPolicies.js +0 -228
- /package/dist/{LambderMSW.js → client/LambderMSW.js} +0 -0
- /package/dist/{LambderCors.d.ts → core/LambderCors.d.ts} +0 -0
- /package/dist/{LambderCors.js → core/LambderCors.js} +0 -0
- /package/dist/{LambderPublicFiles.d.ts → core/LambderPublicFiles.d.ts} +0 -0
- /package/dist/{LambderResolver.d.ts → core/LambderResolver.d.ts} +0 -0
- /package/dist/{LambderResolver.js → core/LambderResolver.js} +0 -0
- /package/dist/{LambderResponse.d.ts → core/LambderResponse.d.ts} +0 -0
- /package/dist/{LambderRouting.d.ts → core/LambderRouting.d.ts} +0 -0
- /package/dist/{LambderRouting.js → core/LambderRouting.js} +0 -0
- /package/dist/{LambderApiContract.js → shared/LambderApiContract.js} +0 -0
- /package/dist/{LambderApiError.js → shared/LambderApiError.js} +0 -0
- /package/dist/{LambderHtml.d.ts → shared/LambderHtml.d.ts} +0 -0
- /package/dist/{LambderHtml.js → shared/LambderHtml.js} +0 -0
- /package/dist/{LambderI18n.d.ts → shared/LambderI18n.d.ts} +0 -0
- /package/dist/{LambderI18n.js → shared/LambderI18n.js} +0 -0
- /package/dist/{node-polyfills.d.ts → shared/node-polyfills.d.ts} +0 -0
- /package/dist/{node-polyfills.js → shared/node-polyfills.js} +0 -0
- /package/dist/{LambderDdbCache.d.ts → stores/LambderDdbCache.d.ts} +0 -0
- /package/dist/{LambderDdbRateLimiter.d.ts → stores/LambderDdbRateLimiter.d.ts} +0 -0
|
@@ -5,11 +5,13 @@ import LambderResponseBuilder from "./LambderResponseBuilder.js";
|
|
|
5
5
|
import { LambderResponse, type LambderHttpResponse } from "./LambderResponse.js";
|
|
6
6
|
import { type ConditionFunction, type LambderRouteMatcher, type PathParamsOf } from "./LambderRouting.js";
|
|
7
7
|
import { type LambderCorsConfig } from "./LambderCors.js";
|
|
8
|
-
import { type LambderSessionDataRefreshConfig } from "
|
|
9
|
-
import LambderSessionController, { type LambderSessionCookieOptions } from "
|
|
8
|
+
import { type LambderSessionDataRefreshConfig } from "../session/LambderSessionManager.js";
|
|
9
|
+
import LambderSessionController, { type LambderSessionCookieOptions } from "../session/LambderSessionController.js";
|
|
10
10
|
import { type LambderPublicFilesOptions } from "./LambderPublicFiles.js";
|
|
11
|
-
import
|
|
12
|
-
import type {
|
|
11
|
+
import type { LambderApiGuard, LambderGuardMetaMap, LambderGuardsOption, LambderGuardDataOf, LambderGuardInputsOf } from "../policies/LambderApiGuards.js";
|
|
12
|
+
import type { LambderApiRateLimitPolicyConfig, LambderApiRateLimitsConfig, LambderAllowedPolicyNames } from "../policies/LambderApiRateLimits.js";
|
|
13
|
+
import type { LambderApiIdempotencyConfig } from "../policies/LambderApiIdempotency.js";
|
|
14
|
+
import type { MergeContract } from "../shared/LambderApiContract.js";
|
|
13
15
|
import { type LambderHttpEvent, type LambderRenderContext, type LambderSessionRenderContext } from "./LambderContext.js";
|
|
14
16
|
export type { PathParamsOf, RouteCondition, ConditionFunction, LambderRouteMatcher } from "./LambderRouting.js";
|
|
15
17
|
export type { LambderCorsConfig } from "./LambderCors.js";
|
|
@@ -62,7 +64,40 @@ export type LambderHandler = {
|
|
|
62
64
|
(event: LambderHttpEvent, context: Context): Promise<LambderHttpResponse>;
|
|
63
65
|
(event: unknown, context: Context): Promise<unknown>;
|
|
64
66
|
};
|
|
65
|
-
|
|
67
|
+
/** DynamoDB session configuration (the `session` option of create/new). */
|
|
68
|
+
export type LambderSessionOptions<TSessionData = any> = {
|
|
69
|
+
tableName: string;
|
|
70
|
+
tableRegion: string;
|
|
71
|
+
sessionSalt: string;
|
|
72
|
+
enableSlidingExpiration?: boolean;
|
|
73
|
+
/** Min seconds between sliding-expiration writes. Default: max(60, 5% of TTL). */
|
|
74
|
+
slidingWriteIntervalSeconds?: number;
|
|
75
|
+
/** Session cookie attributes, e.g. { domain: ".example.com" } for cross-subdomain sessions. `domain` may be a (hostname) => string function for multi-domain deployments. */
|
|
76
|
+
cookie?: LambderSessionCookieOptions;
|
|
77
|
+
/** Session cookie names. Defaults: LMDRSESSIONTKID / LMDRSESSIONCSTK. */
|
|
78
|
+
tokenCookieKey?: string;
|
|
79
|
+
csrfCookieKey?: string;
|
|
80
|
+
partitionKey?: string;
|
|
81
|
+
sortKey?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Opt-in freshness for session.data derived from external state (roles,
|
|
84
|
+
* permissions, feature flags...). Every session read renews data past
|
|
85
|
+
* its ttlSeconds via your refresh callback, persisting in place on the
|
|
86
|
+
* same record: same tokens, same cookies. Return null from refresh to
|
|
87
|
+
* end the session. See LambderSessionDataRefreshConfig for the exact
|
|
88
|
+
* semantics.
|
|
89
|
+
*/
|
|
90
|
+
dataRefresh?: LambderSessionDataRefreshConfig<TSessionData>;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Everything an instance is configured with, in ONE declaration: base
|
|
94
|
+
* serving options plus the type-affecting policy layer (rate limits,
|
|
95
|
+
* guards, idempotency) and session/CORS config. There are no enable/define
|
|
96
|
+
* chain methods; the instance is born fully configured and fully typed
|
|
97
|
+
* (via initLambder), so no ordering rules exist and no partially-configured
|
|
98
|
+
* instance type ever needs a name.
|
|
99
|
+
*/
|
|
100
|
+
export type LambderCreateOptions<TSessionData = any> = {
|
|
66
101
|
publicPath?: string;
|
|
67
102
|
apiPath?: string;
|
|
68
103
|
apiVersion?: string;
|
|
@@ -74,21 +109,34 @@ export type LambderConstructorOptions = {
|
|
|
74
109
|
etag?: boolean;
|
|
75
110
|
/** Guard threshold for Lambda's ~6MB response cap. Default: 5,500,000. */
|
|
76
111
|
maxResponseBytes?: number;
|
|
112
|
+
/** CORS: true allows any origin; or pass a LambderCorsConfig. Default: off. */
|
|
113
|
+
cors?: boolean | LambderCorsConfig;
|
|
114
|
+
/** DynamoDB-backed sessions; required for addSessionApi/addSessionRoute. */
|
|
115
|
+
session?: LambderSessionOptions<TSessionData>;
|
|
116
|
+
/** Declarative per-API rate limiting: your limiter plus named policies APIs reference (typed) via the `rateLimit` option. */
|
|
117
|
+
rateLimits?: LambderApiRateLimitsConfig<Record<string, LambderApiRateLimitPolicyConfig>>;
|
|
118
|
+
/** Named guards APIs reference (typed) via the `guards` option; build each with lambderGuard(). */
|
|
119
|
+
guards?: Record<string, LambderApiGuard<any, any, any>>;
|
|
120
|
+
/** Declarative idempotency: your store plus replay defaults; APIs opt in via `idempotency: true | { ttlSeconds }`. */
|
|
121
|
+
idempotency?: LambderApiIdempotencyConfig;
|
|
77
122
|
};
|
|
78
123
|
/**
|
|
79
|
-
* Main Lambder class for building type-safe serverless APIs
|
|
124
|
+
* Main Lambder class for building type-safe serverless APIs. Create
|
|
125
|
+
* instances with initLambder<SessionData>().create({...}) (see below): the
|
|
126
|
+
* whole configuration, including the typed policy layer, is given at
|
|
127
|
+
* construction, and only registration (routes, apis, hooks, use) chains.
|
|
80
128
|
*
|
|
81
129
|
* @typeParam TSessionData - Type of session data stored in DynamoDB
|
|
82
130
|
* @typeParam _TContract - @internal Accumulates API contract during chaining (do not pass manually)
|
|
83
|
-
* @typeParam _TRateLimitPolicies - @internal
|
|
84
|
-
* @typeParam _TGuards - @internal Guard
|
|
85
|
-
* @typeParam _TIdempotencyEnabled - @internal
|
|
131
|
+
* @typeParam _TRateLimitPolicies - @internal Inferred from create()'s rateLimits.policies (do not pass manually)
|
|
132
|
+
* @typeParam _TGuards - @internal Guard metadata map inferred from create()'s guards (do not pass manually)
|
|
133
|
+
* @typeParam _TIdempotencyEnabled - @internal True when create() received idempotency (do not pass manually)
|
|
86
134
|
*
|
|
87
135
|
* @example
|
|
88
136
|
* ```typescript
|
|
89
137
|
* interface SessionData { userId: string; role: string; }
|
|
90
138
|
*
|
|
91
|
-
* const lambder =
|
|
139
|
+
* const lambder = initLambder<SessionData>().create({ apiPath: '/api' })
|
|
92
140
|
* .addApi('getUser', { input: z.object({...}), output: z.object({...}) }, handler)
|
|
93
141
|
* .addApi('createUser', { input: z.object({...}), output: z.object({...}) }, handler);
|
|
94
142
|
* ```
|
|
@@ -128,30 +176,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
128
176
|
private sessionCookieOptions;
|
|
129
177
|
private sessionTokenCookieKey;
|
|
130
178
|
private sessionCsrfCookieKey;
|
|
131
|
-
constructor(options?:
|
|
132
|
-
enableCors(config: boolean | LambderCorsConfig): this;
|
|
133
|
-
enableDdbSession({ tableName, tableRegion, sessionSalt, enableSlidingExpiration, slidingWriteIntervalSeconds, cookie, partitionKey, sortKey, dataRefresh, }: {
|
|
134
|
-
tableName: string;
|
|
135
|
-
tableRegion: string;
|
|
136
|
-
sessionSalt: string;
|
|
137
|
-
enableSlidingExpiration?: boolean;
|
|
138
|
-
/** Min seconds between sliding-expiration writes. Default: max(60, 5% of TTL). */
|
|
139
|
-
slidingWriteIntervalSeconds?: number;
|
|
140
|
-
/** Session cookie attributes, e.g. { domain: ".example.com" } for cross-subdomain sessions. `domain` may be a (hostname) => string function for multi-domain deployments. */
|
|
141
|
-
cookie?: LambderSessionCookieOptions;
|
|
142
|
-
partitionKey?: string;
|
|
143
|
-
sortKey?: string;
|
|
144
|
-
/**
|
|
145
|
-
* Opt-in freshness for session.data derived from external state
|
|
146
|
-
* (roles, permissions, feature flags...). Every session read
|
|
147
|
-
* renews data past its ttlSeconds via your refresh callback,
|
|
148
|
-
* persisting in place on the same record: same tokens, same
|
|
149
|
-
* cookies. Return null from refresh to end the session. See
|
|
150
|
-
* LambderSessionDataRefreshConfig for the exact semantics.
|
|
151
|
-
*/
|
|
152
|
-
dataRefresh?: LambderSessionDataRefreshConfig<TSessionData>;
|
|
153
|
-
}): this;
|
|
154
|
-
setSessionCookieKey(sessionTokenCookieKey: string, sessionCsrfCookieKey: string): this;
|
|
179
|
+
constructor(options?: LambderCreateOptions<TSessionData>);
|
|
155
180
|
setRouteFallbackHandler(routeFallbackHandler: FallbackHandlerFunction): this;
|
|
156
181
|
setApiFallbackHandler(apiFallbackHandler: FallbackHandlerFunction): this;
|
|
157
182
|
setApiInputValidationErrorHandler(apiInputValidationErrorHandler: ApiInputValidationErrorHandlerFunction): this;
|
|
@@ -179,39 +204,6 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
179
204
|
serveIndexHtml(handler?: FallbackHandlerFunction, options?: LambderIndexHtmlOptions): this;
|
|
180
205
|
/** Apply the serveIndexHtml gates; null means fall through. */
|
|
181
206
|
private tryServeIndexHtml;
|
|
182
|
-
/**
|
|
183
|
-
* Wire declarative per-API rate limiting: your LambderDdbRateLimiter
|
|
184
|
-
* instance plus named policies, each declaring its windows and what one
|
|
185
|
-
* counter tracks (`per`: "ip", "session", or a custom key function).
|
|
186
|
-
* APIs then reference policies by name via the `rateLimit` option; the
|
|
187
|
-
* returned type narrows so only declared names are accepted, and
|
|
188
|
-
* policies keyed per "session" are only referable from addSessionApi.
|
|
189
|
-
* Callable once; call it before the API registrations that use it.
|
|
190
|
-
*/
|
|
191
|
-
enableApiRateLimits<const TPolicies extends Record<string, LambderApiRateLimitPolicyConfig>>(config: LambderApiRateLimitsConfig<TPolicies>): Lambder<TSessionData, _TContract, TPolicies, _TGuards, _TIdempotencyEnabled>;
|
|
192
|
-
/**
|
|
193
|
-
* Wire declarative idempotency: your LambderDdbIdempotency instance plus
|
|
194
|
-
* replay defaults. APIs opt in via `idempotency: true | { ttlSeconds }`;
|
|
195
|
-
* the option is a type error until this is called. Requests carrying a
|
|
196
|
-
* client `idempotencyKey` (sent by LambderCaller) claim an
|
|
197
|
-
* identity+api+key scope atomically: concurrent duplicates refuse with
|
|
198
|
-
* 409, replays of a completed request return the stored response, and a
|
|
199
|
-
* crashed original releases its claim. Callable once.
|
|
200
|
-
*/
|
|
201
|
-
enableApiIdempotency(config: LambderApiIdempotencyConfig): Lambder<TSessionData, _TContract, _TRateLimitPolicies, _TGuards, true>;
|
|
202
|
-
/**
|
|
203
|
-
* Define named guards that APIs reference (typed) via the `guards`
|
|
204
|
-
* option. Each guard is built with lambderGuard() in one of two modes:
|
|
205
|
-
* apiInput (checks a slice of the API's own payload; declarable only on
|
|
206
|
-
* APIs whose input schema carries those fields, so the payload type
|
|
207
|
-
* passes both the API input and the guard's apiInput) or guardInput (the
|
|
208
|
-
* client sends the guard's value separately via options.guardInputs, and
|
|
209
|
-
* the contract forces it at the call site). Guards run before input
|
|
210
|
-
* validation, in the order the API declares them; a handler refuses by
|
|
211
|
-
* throwing (typically refuse()). Callable multiple times so domain
|
|
212
|
-
* modules can contribute their own; names must not collide.
|
|
213
|
-
*/
|
|
214
|
-
defineApiGuards<TGuards extends Record<string, LambderApiGuard<any>>>(guards: TGuards): Lambder<TSessionData, _TContract, _TRateLimitPolicies, _TGuards & LambderGuardMetaMap<TGuards>, _TIdempotencyEnabled>;
|
|
215
207
|
private getOrCreatePolicyEngine;
|
|
216
208
|
/** Registration-time checks shared by addApi/addSessionApi. */
|
|
217
209
|
private assertApiRegistration;
|
|
@@ -220,32 +212,32 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
220
212
|
addSessionRoute<TPath extends Path>(condition: TPath, actionFn: (ctx: LambderSessionRenderContext<any, TSessionData, PathParamsOf<TPath>>, resolver: LambderResolver) => MaybePromise<LambderResponse>): this;
|
|
221
213
|
addSessionRoute(condition: RegExp | ConditionFunction | LambderRouteMatcher, actionFn: SessionActionFunction<TSessionData>): this;
|
|
222
214
|
use<_TNewContract extends Record<string, any>>(plugin: (lambder: Lambder<TSessionData, _TContract, any, any, any>) => Lambder<TSessionData, _TNewContract, any, any, any>): Lambder<TSessionData, _TNewContract extends _TContract ? _TNewContract : (_TContract & _TNewContract), _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled>;
|
|
223
|
-
addApi<TName extends string, TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny, const TRateOpt extends LambderAllowedPolicyNames<_TRateLimitPolicies, z.infer<TInput>, false> | readonly LambderAllowedPolicyNames<_TRateLimitPolicies, z.infer<TInput>, false>[] = never, const TGuardsOpt extends
|
|
215
|
+
addApi<TName extends string, TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny, const TRateOpt extends LambderAllowedPolicyNames<_TRateLimitPolicies, z.infer<TInput>, false> | readonly LambderAllowedPolicyNames<_TRateLimitPolicies, z.infer<TInput>, false>[] = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.infer<TInput>, false> = never>(name: TName, schema: {
|
|
224
216
|
input: TInput;
|
|
225
217
|
output: TOutput;
|
|
226
218
|
} & {
|
|
227
219
|
/** Named rate limits, checked in declared order before guards and validation; the first exceeded one refuses (429 envelope). */
|
|
228
220
|
rateLimit?: TRateOpt;
|
|
229
|
-
/** Named guards, run in declared order before input validation
|
|
221
|
+
/** Named guards, run in declared order before input validation: a name, a list of names, or a { name: param } map for parameterized guards. Their input requirements merge into this API's contract input; their return values land typed on ctx.guardData. */
|
|
230
222
|
guards?: TGuardsOpt;
|
|
231
|
-
/** Replay-protect this API per client idempotencyKey. Requires
|
|
223
|
+
/** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
|
|
232
224
|
idempotency?: _TIdempotencyEnabled extends true ? (boolean | {
|
|
233
225
|
ttlSeconds?: number;
|
|
234
226
|
}) : never;
|
|
235
|
-
}, handler: (ctx: LambderRenderContext<z.infer<TInput>>, resolver: LambderResolver<z.infer<TOutput>>) => MaybePromise<LambderResponse>): Lambder<TSessionData, MergeContract<_TContract, TName, z.infer<TInput>, z.infer<TOutput>, LambderGuardInputsOf<_TGuards, TGuardsOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled>;
|
|
236
|
-
addSessionApi<TName extends string, TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny, const TRateOpt extends LambderAllowedPolicyNames<_TRateLimitPolicies, z.infer<TInput>, true> | readonly LambderAllowedPolicyNames<_TRateLimitPolicies, z.infer<TInput>, true>[] = never, const TGuardsOpt extends
|
|
227
|
+
}, 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>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled>;
|
|
228
|
+
addSessionApi<TName extends string, TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny, const TRateOpt extends LambderAllowedPolicyNames<_TRateLimitPolicies, z.infer<TInput>, true> | readonly LambderAllowedPolicyNames<_TRateLimitPolicies, z.infer<TInput>, true>[] = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.infer<TInput>, true> = never>(name: TName, schema: {
|
|
237
229
|
input: TInput;
|
|
238
230
|
output: TOutput;
|
|
239
231
|
} & {
|
|
240
232
|
/** Named rate limits, checked in declared order before guards and validation; the first exceeded one refuses (429 envelope). */
|
|
241
233
|
rateLimit?: TRateOpt;
|
|
242
|
-
/** Named guards, run in declared order before input validation
|
|
234
|
+
/** Named guards, run in declared order before input validation: a name, a list of names, or a { name: param } map for parameterized guards. Their input requirements merge into this API's contract input; their return values land typed on ctx.guardData. */
|
|
243
235
|
guards?: TGuardsOpt;
|
|
244
|
-
/** Replay-protect this API per client idempotencyKey. Requires
|
|
236
|
+
/** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
|
|
245
237
|
idempotency?: _TIdempotencyEnabled extends true ? (boolean | {
|
|
246
238
|
ttlSeconds?: number;
|
|
247
239
|
}) : never;
|
|
248
|
-
}, handler: (ctx: LambderSessionRenderContext<z.infer<TInput>, TSessionData>, resolver: LambderResolver<z.infer<TOutput>>) => MaybePromise<LambderResponse>): Lambder<TSessionData, MergeContract<_TContract, TName, z.infer<TInput>, z.infer<TOutput>, LambderGuardInputsOf<_TGuards, TGuardsOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled>;
|
|
240
|
+
}, handler: (ctx: LambderSessionRenderContext<z.infer<TInput>, TSessionData, 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>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled>;
|
|
249
241
|
/**
|
|
250
242
|
* Fetch the session or short-circuit the request: API calls get the
|
|
251
243
|
* protocol's { sessionExpired: true } response (handled by LambderCaller),
|
|
@@ -290,3 +282,43 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
290
282
|
private resolveRequest;
|
|
291
283
|
render(event: LambderHttpEvent, lambdaContext: Context): Promise<LambderHttpResponse>;
|
|
292
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* The canonical way to create an instance: fix the session data type first,
|
|
287
|
+
* then create with the full configuration in one declaration; the policy,
|
|
288
|
+
* guard, and idempotency types are INFERRED from the options, so the
|
|
289
|
+
* instance is born fully typed and `typeof lambderApp` is the annotation
|
|
290
|
+
* type for api modules. No enable/define chain exists, so there are no
|
|
291
|
+
* ordering rules and nothing can be half-configured.
|
|
292
|
+
*
|
|
293
|
+
* ```typescript
|
|
294
|
+
* // app.ts (imports no api modules, so modules can import the type back)
|
|
295
|
+
* export const lambderApp = initLambder<SessionData>().create({
|
|
296
|
+
* apiPath: "/api",
|
|
297
|
+
* session: { tableName: "app-session", tableRegion: "us-east-1", sessionSalt: "..." },
|
|
298
|
+
* rateLimits: { limiter, policies },
|
|
299
|
+
* guards,
|
|
300
|
+
* idempotency: { store },
|
|
301
|
+
* });
|
|
302
|
+
* export type AppLambder = typeof lambderApp;
|
|
303
|
+
*
|
|
304
|
+
* // orders.ts
|
|
305
|
+
* export const orderApi = (lambder: AppLambder) => lambder.addSessionApi(...);
|
|
306
|
+
*
|
|
307
|
+
* // index.ts: registration only
|
|
308
|
+
* const lambder = lambderApp.addHook(...).use(orderApi)...;
|
|
309
|
+
* export const handler = lambder.getHandler();
|
|
310
|
+
* ```
|
|
311
|
+
*
|
|
312
|
+
* Why curried (`initLambder<S>().create(...)` rather than
|
|
313
|
+
* `new Lambder<S>(...)`): TypeScript type arguments are all-or-nothing per
|
|
314
|
+
* call, so explicitly passing the session data type to the constructor
|
|
315
|
+
* would silently WIDEN the inferred policy and guard types to their {}
|
|
316
|
+
* defaults. Fixing the session type in the first call lets the second call
|
|
317
|
+
* infer everything else from the options. `new Lambder(options)` remains
|
|
318
|
+
* for untyped or session-data-free instances.
|
|
319
|
+
*/
|
|
320
|
+
export declare const initLambder: <TSessionData = any>() => {
|
|
321
|
+
create<const TOptions extends LambderCreateOptions<TSessionData>>(options: TOptions): Lambder<TSessionData, {}, TOptions["rateLimits"] extends {
|
|
322
|
+
policies: infer TPolicies extends Record<string, LambderApiRateLimitPolicyConfig>;
|
|
323
|
+
} ? TPolicies : {}, TOptions["guards"] extends Record<string, LambderApiGuard<any, any, any>> ? LambderGuardMetaMap<TOptions["guards"]> : {}, TOptions["idempotency"] extends LambderApiIdempotencyConfig ? true : false>;
|
|
324
|
+
};
|
|
@@ -3,26 +3,29 @@ import LambderResponseBuilder from "./LambderResponseBuilder.js";
|
|
|
3
3
|
import { LambderResponse, finalizeResponse, DEFAULT_FINALIZE_OPTIONS, } from "./LambderResponse.js";
|
|
4
4
|
import { compileRouteMatcher } from "./LambderRouting.js";
|
|
5
5
|
import { applyCorsHeaders } from "./LambderCors.js";
|
|
6
|
-
import LambderSessionManager from "
|
|
7
|
-
import LambderSessionController from "
|
|
6
|
+
import LambderSessionManager from "../session/LambderSessionManager.js";
|
|
7
|
+
import LambderSessionController from "../session/LambderSessionController.js";
|
|
8
8
|
import { LambderPublicFilesHandler } from "./LambderPublicFiles.js";
|
|
9
|
-
import { isLambderApiError } from "
|
|
10
|
-
import { LambderApiPolicyEngine
|
|
9
|
+
import { isLambderApiError } from "../shared/LambderApiError.js";
|
|
10
|
+
import { LambderApiPolicyEngine } from "../policies/LambderApiPolicies.js";
|
|
11
11
|
import { createContext, isV2HttpEvent } from "./LambderContext.js";
|
|
12
12
|
/**
|
|
13
|
-
* Main Lambder class for building type-safe serverless APIs
|
|
13
|
+
* Main Lambder class for building type-safe serverless APIs. Create
|
|
14
|
+
* instances with initLambder<SessionData>().create({...}) (see below): the
|
|
15
|
+
* whole configuration, including the typed policy layer, is given at
|
|
16
|
+
* construction, and only registration (routes, apis, hooks, use) chains.
|
|
14
17
|
*
|
|
15
18
|
* @typeParam TSessionData - Type of session data stored in DynamoDB
|
|
16
19
|
* @typeParam _TContract - @internal Accumulates API contract during chaining (do not pass manually)
|
|
17
|
-
* @typeParam _TRateLimitPolicies - @internal
|
|
18
|
-
* @typeParam _TGuards - @internal Guard
|
|
19
|
-
* @typeParam _TIdempotencyEnabled - @internal
|
|
20
|
+
* @typeParam _TRateLimitPolicies - @internal Inferred from create()'s rateLimits.policies (do not pass manually)
|
|
21
|
+
* @typeParam _TGuards - @internal Guard metadata map inferred from create()'s guards (do not pass manually)
|
|
22
|
+
* @typeParam _TIdempotencyEnabled - @internal True when create() received idempotency (do not pass manually)
|
|
20
23
|
*
|
|
21
24
|
* @example
|
|
22
25
|
* ```typescript
|
|
23
26
|
* interface SessionData { userId: string; role: string; }
|
|
24
27
|
*
|
|
25
|
-
* const lambder =
|
|
28
|
+
* const lambder = initLambder<SessionData>().create({ apiPath: '/api' })
|
|
26
29
|
* .addApi('getUser', { input: z.object({...}), output: z.object({...}) }, handler)
|
|
27
30
|
* .addApi('createUser', { input: z.object({...}), output: z.object({...}) }, handler);
|
|
28
31
|
* ```
|
|
@@ -73,26 +76,33 @@ export default class Lambder {
|
|
|
73
76
|
etag: options.etag ?? DEFAULT_FINALIZE_OPTIONS.etag,
|
|
74
77
|
maxResponseBytes: options.maxResponseBytes ?? DEFAULT_FINALIZE_OPTIONS.maxResponseBytes,
|
|
75
78
|
};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
79
|
+
if (options.cors !== undefined && options.cors !== false) {
|
|
80
|
+
this.corsConfig = options.cors === true ? {} : options.cors;
|
|
81
|
+
}
|
|
82
|
+
if (options.session) {
|
|
83
|
+
const session = options.session;
|
|
84
|
+
this.lambderSessionManager = new LambderSessionManager({
|
|
85
|
+
tableName: session.tableName,
|
|
86
|
+
tableRegion: session.tableRegion,
|
|
87
|
+
partitionKey: session.partitionKey ?? "pk",
|
|
88
|
+
sortKey: session.sortKey ?? "sk",
|
|
89
|
+
sessionSalt: session.sessionSalt,
|
|
90
|
+
enableSlidingExpiration: session.enableSlidingExpiration,
|
|
91
|
+
slidingWriteIntervalSeconds: session.slidingWriteIntervalSeconds,
|
|
92
|
+
dataRefresh: session.dataRefresh,
|
|
93
|
+
});
|
|
94
|
+
this.sessionCookieOptions = session.cookie ?? {};
|
|
95
|
+
if (session.tokenCookieKey)
|
|
96
|
+
this.sessionTokenCookieKey = session.tokenCookieKey;
|
|
97
|
+
if (session.csrfCookieKey)
|
|
98
|
+
this.sessionCsrfCookieKey = session.csrfCookieKey;
|
|
99
|
+
}
|
|
100
|
+
if (options.rateLimits)
|
|
101
|
+
this.getOrCreatePolicyEngine().setRateLimits(options.rateLimits);
|
|
102
|
+
if (options.guards)
|
|
103
|
+
this.getOrCreatePolicyEngine().addGuards(options.guards);
|
|
104
|
+
if (options.idempotency)
|
|
105
|
+
this.getOrCreatePolicyEngine().setIdempotency(options.idempotency);
|
|
96
106
|
}
|
|
97
107
|
setRouteFallbackHandler(routeFallbackHandler) {
|
|
98
108
|
this.routeFallbackHandler = routeFallbackHandler;
|
|
@@ -162,51 +172,6 @@ export default class Lambder {
|
|
|
162
172
|
}
|
|
163
173
|
return response;
|
|
164
174
|
}
|
|
165
|
-
// ---------------------------------------------------------------------
|
|
166
|
-
// Declarative API policies (rate limits, guards, idempotency)
|
|
167
|
-
// ---------------------------------------------------------------------
|
|
168
|
-
/**
|
|
169
|
-
* Wire declarative per-API rate limiting: your LambderDdbRateLimiter
|
|
170
|
-
* instance plus named policies, each declaring its windows and what one
|
|
171
|
-
* counter tracks (`per`: "ip", "session", or a custom key function).
|
|
172
|
-
* APIs then reference policies by name via the `rateLimit` option; the
|
|
173
|
-
* returned type narrows so only declared names are accepted, and
|
|
174
|
-
* policies keyed per "session" are only referable from addSessionApi.
|
|
175
|
-
* Callable once; call it before the API registrations that use it.
|
|
176
|
-
*/
|
|
177
|
-
enableApiRateLimits(config) {
|
|
178
|
-
this.getOrCreatePolicyEngine().setRateLimits(config);
|
|
179
|
-
return this;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Wire declarative idempotency: your LambderDdbIdempotency instance plus
|
|
183
|
-
* replay defaults. APIs opt in via `idempotency: true | { ttlSeconds }`;
|
|
184
|
-
* the option is a type error until this is called. Requests carrying a
|
|
185
|
-
* client `idempotencyKey` (sent by LambderCaller) claim an
|
|
186
|
-
* identity+api+key scope atomically: concurrent duplicates refuse with
|
|
187
|
-
* 409, replays of a completed request return the stored response, and a
|
|
188
|
-
* crashed original releases its claim. Callable once.
|
|
189
|
-
*/
|
|
190
|
-
enableApiIdempotency(config) {
|
|
191
|
-
this.getOrCreatePolicyEngine().setIdempotency(config);
|
|
192
|
-
return this;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Define named guards that APIs reference (typed) via the `guards`
|
|
196
|
-
* option. Each guard is built with lambderGuard() in one of two modes:
|
|
197
|
-
* apiInput (checks a slice of the API's own payload; declarable only on
|
|
198
|
-
* APIs whose input schema carries those fields, so the payload type
|
|
199
|
-
* passes both the API input and the guard's apiInput) or guardInput (the
|
|
200
|
-
* client sends the guard's value separately via options.guardInputs, and
|
|
201
|
-
* the contract forces it at the call site). Guards run before input
|
|
202
|
-
* validation, in the order the API declares them; a handler refuses by
|
|
203
|
-
* throwing (typically refuse()). Callable multiple times so domain
|
|
204
|
-
* modules can contribute their own; names must not collide.
|
|
205
|
-
*/
|
|
206
|
-
defineApiGuards(guards) {
|
|
207
|
-
this.getOrCreatePolicyEngine().addGuards(guards);
|
|
208
|
-
return this;
|
|
209
|
-
}
|
|
210
175
|
getOrCreatePolicyEngine() {
|
|
211
176
|
if (!this.apiPolicyEngine)
|
|
212
177
|
this.apiPolicyEngine = new LambderApiPolicyEngine();
|
|
@@ -222,7 +187,7 @@ export default class Lambder {
|
|
|
222
187
|
if (!usesPolicies)
|
|
223
188
|
return;
|
|
224
189
|
if (!this.apiPolicyEngine) {
|
|
225
|
-
throw new Error(`Lambder: API "${name}" declares rateLimit/guards/idempotency, but none of
|
|
190
|
+
throw new Error(`Lambder: API "${name}" declares rateLimit/guards/idempotency, but none of rateLimits/guards/idempotency was configured at creation.`);
|
|
226
191
|
}
|
|
227
192
|
this.apiPolicyEngine.assertRegistration(name, mode, options);
|
|
228
193
|
}
|
|
@@ -257,6 +222,14 @@ export default class Lambder {
|
|
|
257
222
|
this.actionList.push({
|
|
258
223
|
match: (ctx) => ctx.apiName === name ? {} : false,
|
|
259
224
|
actionFn: async (ctx, resolver) => {
|
|
225
|
+
// Replay fast path first: a completed idempotent request must
|
|
226
|
+
// answer its stored response without burning rate-limit quota
|
|
227
|
+
// or re-running guards (no handler executes either way).
|
|
228
|
+
if (this.apiPolicyEngine && schema.idempotency) {
|
|
229
|
+
const replay = await this.apiPolicyEngine.findReplay(name, ctx);
|
|
230
|
+
if (replay)
|
|
231
|
+
return replay;
|
|
232
|
+
}
|
|
260
233
|
if (this.apiPolicyEngine)
|
|
261
234
|
await this.apiPolicyEngine.runPreflight(name, ctx, resolver, schema);
|
|
262
235
|
const inputResult = schema.input.safeParse(ctx.apiPayload);
|
|
@@ -282,6 +255,13 @@ export default class Lambder {
|
|
|
282
255
|
match: (ctx) => ctx.apiName === name ? {} : false,
|
|
283
256
|
actionFn: async (ctx, resolver) => {
|
|
284
257
|
await this.requireSession(ctx, resolver);
|
|
258
|
+
// Replay fast path (after the session fetch: the replay scope
|
|
259
|
+
// is keyed per session): see addApi.
|
|
260
|
+
if (this.apiPolicyEngine && schema.idempotency) {
|
|
261
|
+
const replay = await this.apiPolicyEngine.findReplay(name, ctx);
|
|
262
|
+
if (replay)
|
|
263
|
+
return replay;
|
|
264
|
+
}
|
|
285
265
|
if (this.apiPolicyEngine)
|
|
286
266
|
await this.apiPolicyEngine.runPreflight(name, ctx, resolver, schema);
|
|
287
267
|
const inputResult = schema.input.safeParse(ctx.apiPayload);
|
|
@@ -330,7 +310,7 @@ export default class Lambder {
|
|
|
330
310
|
}
|
|
331
311
|
getSessionController(ctx) {
|
|
332
312
|
if (!this.lambderSessionManager)
|
|
333
|
-
throw new Error("Session is not enabled.
|
|
313
|
+
throw new Error("Session is not enabled. Configure the session option at creation.");
|
|
334
314
|
return new LambderSessionController({
|
|
335
315
|
lambderSessionManager: this.lambderSessionManager,
|
|
336
316
|
sessionTokenCookieKey: this.sessionTokenCookieKey,
|
|
@@ -567,6 +547,46 @@ export default class Lambder {
|
|
|
567
547
|
}
|
|
568
548
|
}
|
|
569
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* The canonical way to create an instance: fix the session data type first,
|
|
552
|
+
* then create with the full configuration in one declaration; the policy,
|
|
553
|
+
* guard, and idempotency types are INFERRED from the options, so the
|
|
554
|
+
* instance is born fully typed and `typeof lambderApp` is the annotation
|
|
555
|
+
* type for api modules. No enable/define chain exists, so there are no
|
|
556
|
+
* ordering rules and nothing can be half-configured.
|
|
557
|
+
*
|
|
558
|
+
* ```typescript
|
|
559
|
+
* // app.ts (imports no api modules, so modules can import the type back)
|
|
560
|
+
* export const lambderApp = initLambder<SessionData>().create({
|
|
561
|
+
* apiPath: "/api",
|
|
562
|
+
* session: { tableName: "app-session", tableRegion: "us-east-1", sessionSalt: "..." },
|
|
563
|
+
* rateLimits: { limiter, policies },
|
|
564
|
+
* guards,
|
|
565
|
+
* idempotency: { store },
|
|
566
|
+
* });
|
|
567
|
+
* export type AppLambder = typeof lambderApp;
|
|
568
|
+
*
|
|
569
|
+
* // orders.ts
|
|
570
|
+
* export const orderApi = (lambder: AppLambder) => lambder.addSessionApi(...);
|
|
571
|
+
*
|
|
572
|
+
* // index.ts: registration only
|
|
573
|
+
* const lambder = lambderApp.addHook(...).use(orderApi)...;
|
|
574
|
+
* export const handler = lambder.getHandler();
|
|
575
|
+
* ```
|
|
576
|
+
*
|
|
577
|
+
* Why curried (`initLambder<S>().create(...)` rather than
|
|
578
|
+
* `new Lambder<S>(...)`): TypeScript type arguments are all-or-nothing per
|
|
579
|
+
* call, so explicitly passing the session data type to the constructor
|
|
580
|
+
* would silently WIDEN the inferred policy and guard types to their {}
|
|
581
|
+
* defaults. Fixing the session type in the first call lets the second call
|
|
582
|
+
* infer everything else from the options. `new Lambder(options)` remains
|
|
583
|
+
* for untyped or session-data-free instances.
|
|
584
|
+
*/
|
|
585
|
+
export const initLambder = () => ({
|
|
586
|
+
create(options) {
|
|
587
|
+
return new Lambder(options);
|
|
588
|
+
},
|
|
589
|
+
});
|
|
570
590
|
/** Rebuild the query string from the API Gateway event for redirects. */
|
|
571
591
|
const buildQueryString = (ctx) => {
|
|
572
592
|
if (isV2HttpEvent(ctx.event)) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { APIGatewayProxyEvent, APIGatewayProxyEventV2, APIGatewayProxyEventHeaders, Context } from "aws-lambda";
|
|
2
|
-
import type { LambderSessionContext } from "
|
|
2
|
+
import type { LambderSessionContext } from "../session/LambderSessionManager.js";
|
|
3
3
|
import type { LambderHttpEventFormat } from "./LambderResponse.js";
|
|
4
4
|
export type LambderHttpEvent = APIGatewayProxyEvent | APIGatewayProxyEventV2;
|
|
5
5
|
/** True for API Gateway HTTP API / Lambda Function URL (payload v2) events. */
|
|
6
6
|
export declare const isV2HttpEvent: (event: unknown) => event is APIGatewayProxyEventV2;
|
|
7
|
-
export type LambderRenderContext<TApiPayload = any, TPathParams extends Record<string, string> = Record<string, string
|
|
7
|
+
export type LambderRenderContext<TApiPayload = any, TPathParams extends Record<string, string> = Record<string, string>, TGuardData = {}> = {
|
|
8
8
|
host: string;
|
|
9
9
|
path: string;
|
|
10
10
|
pathParams: TPathParams;
|
|
@@ -15,6 +15,12 @@ export type LambderRenderContext<TApiPayload = any, TPathParams extends Record<s
|
|
|
15
15
|
session: null;
|
|
16
16
|
apiName: string | null;
|
|
17
17
|
apiPayload: TApiPayload;
|
|
18
|
+
/**
|
|
19
|
+
* Outputs of this API's guards, keyed by guard name. Only guards the API
|
|
20
|
+
* declares AND that return a value appear (typed via the declarative
|
|
21
|
+
* guards option); void guards never do.
|
|
22
|
+
*/
|
|
23
|
+
guardData: TGuardData;
|
|
18
24
|
headers: APIGatewayProxyEventHeaders;
|
|
19
25
|
/** Decoded request body, exactly as received (e.g. for webhook signature verification). */
|
|
20
26
|
rawBody: string;
|
|
@@ -39,7 +45,7 @@ export type LambderRenderContext<TApiPayload = any, TPathParams extends Record<s
|
|
|
39
45
|
logToApiResponseAccumulator: any[];
|
|
40
46
|
};
|
|
41
47
|
};
|
|
42
|
-
export type LambderSessionRenderContext<TApiPayload = any, SessionData = any, TPathParams extends Record<string, string> = Record<string, string
|
|
48
|
+
export type LambderSessionRenderContext<TApiPayload = any, SessionData = any, TPathParams extends Record<string, string> = Record<string, string>, TGuardData = {}> = Omit<LambderRenderContext<TApiPayload, TPathParams, TGuardData>, 'session'> & {
|
|
43
49
|
session: LambderSessionContext<SessionData>;
|
|
44
50
|
};
|
|
45
51
|
export declare const createContext: (event: LambderHttpEvent, lambdaContext: Context, apiPath: string) => LambderRenderContext;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import mimeTypeResolver from "mime-types";
|
|
2
|
-
import { getFS, getPath } from "
|
|
2
|
+
import { getFS, getPath } from "../shared/node-polyfills.js";
|
|
3
3
|
import { LambderResponse } from "./LambderResponse.js";
|
|
4
4
|
// Content-hashed build outputs (Vite/webpack/Rollup): a [-.] separated run of
|
|
5
5
|
// 8+ hash chars containing at least one digit, before the extension.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getZlib, getCrypto } from "
|
|
1
|
+
import { getZlib, getCrypto } from "../shared/node-polyfills.js";
|
|
2
2
|
export const normalizeHeaders = (headers) => Object.fromEntries(Object.entries(headers ?? {}).map(([k, v]) => [k, Array.isArray(v) ? [...v] : [v]]));
|
|
3
3
|
/**
|
|
4
4
|
* Intermediate response object returned by all response builder methods and by
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { LambderRenderContext } from "./LambderContext.js";
|
|
2
2
|
import { LambderResponse, type HttpStatusCode, type LambderHeadersInput } from "./LambderResponse.js";
|
|
3
|
-
import { LambderSafeHtml } from "
|
|
3
|
+
import { LambderSafeHtml } from "../shared/LambderHtml.js";
|
|
4
4
|
import { type LambderTemplateData } from "./LambderTemplatingEngine.js";
|
|
5
|
+
import type { LambderApiResponseConfig } from "../shared/LambderApiContract.js";
|
|
6
|
+
export type { LambderApiResponse, LambderApiResponseConfig } from "../shared/LambderApiContract.js";
|
|
5
7
|
export type LambderResponseOptions = {
|
|
6
8
|
statusCode?: HttpStatusCode;
|
|
7
9
|
headers?: LambderHeadersInput;
|
|
@@ -12,23 +14,9 @@ export type LambderResponseOptions = {
|
|
|
12
14
|
/** "auto" (default): ETag on GET/HEAD 200 when globally enabled. true: force. false: never. */
|
|
13
15
|
etag?: boolean | "auto";
|
|
14
16
|
};
|
|
15
|
-
export type LambderApiResponseConfig = {
|
|
16
|
-
versionExpired?: boolean;
|
|
17
|
-
sessionExpired?: boolean;
|
|
18
|
-
notAuthorized?: boolean;
|
|
19
|
-
message?: any;
|
|
20
|
-
errorMessage?: any;
|
|
21
|
-
logList?: any[];
|
|
22
|
-
};
|
|
23
|
-
export type LambderApiResponse<T> = LambderApiResponseConfig & {
|
|
24
|
-
apiVersion?: string | null;
|
|
25
|
-
payload?: T | null;
|
|
26
|
-
};
|
|
27
17
|
export type LambderRawResponseInit = {
|
|
28
18
|
statusCode: HttpStatusCode;
|
|
29
19
|
headers?: LambderHeadersInput;
|
|
30
|
-
/** Legacy alias for headers (API Gateway naming). */
|
|
31
|
-
multiValueHeaders?: Record<string, string[]>;
|
|
32
20
|
body: string | Buffer | null;
|
|
33
21
|
/** True when body is already a base64-encoded string. */
|
|
34
22
|
isBase64Encoded?: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import mimeTypeResolver from "mime-types";
|
|
2
|
-
import { getFS, getPath } from "
|
|
2
|
+
import { getFS, getPath } from "../shared/node-polyfills.js";
|
|
3
3
|
import { LambderResponse } from "./LambderResponse.js";
|
|
4
4
|
import { LambderTemplatingEngine } from "./LambderTemplatingEngine.js";
|
|
5
5
|
// Compiled templates survive across requests (builder instances are per-request).
|
|
@@ -72,7 +72,7 @@ export default class LambderResponseBuilder {
|
|
|
72
72
|
raw(init) {
|
|
73
73
|
return new LambderResponse({
|
|
74
74
|
statusCode: init.statusCode,
|
|
75
|
-
headers: init.headers
|
|
75
|
+
headers: init.headers,
|
|
76
76
|
body: init.body,
|
|
77
77
|
isBodyBase64: init.isBase64Encoded ?? false,
|
|
78
78
|
compress: init.compress ?? (init.isBase64Encoded ? false : "auto"),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getFS } from "
|
|
2
|
-
import { renderHtmlValue } from "
|
|
1
|
+
import { getFS } from "../shared/node-polyfills.js";
|
|
2
|
+
import { renderHtmlValue } from "../shared/LambderHtml.js";
|
|
3
3
|
const TOKEN_PATTERN = /<!--\s*(?:(slot:([\w-]+)\s*\/)|(slot:([\w-]+))|(\/slot:([\w-]+))|(if:(!?)([\w-]+))|(else)|(\/if:(!?)([\w-]+)))\s*-->/g;
|
|
4
4
|
/**
|
|
5
5
|
* Fail loudly on slot positions where HTML escaping cannot protect against
|