lambder 3.7.1 → 4.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/Readme.md +92 -24
- package/dist/{LambderCaller.d.ts → client/LambderCaller.d.ts} +50 -14
- package/dist/{LambderCaller.js → client/LambderCaller.js} +29 -17
- 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} +45 -19
- package/dist/{Lambder.js → core/Lambder.js} +28 -12
- 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 +36 -35
- package/dist/index.js +21 -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} +2 -1
- package/dist/{LambderSessionController.js → session/LambderSessionController.js} +16 -11
- package/dist/{LambderSessionManager.d.ts → session/LambderSessionManager.d.ts} +38 -4
- package/dist/{LambderSessionManager.js → session/LambderSessionManager.js} +49 -16
- package/dist/shared/LambderApiContract.d.ts +41 -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/LambderApiContract.d.ts +0 -21
- package/dist/LambderApiPolicies.d.ts +0 -177
- package/dist/LambderApiPolicies.js +0 -219
- /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
|
@@ -3,11 +3,11 @@ 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
13
|
* Main Lambder class for building type-safe serverless APIs
|
|
@@ -193,14 +193,15 @@ export default class Lambder {
|
|
|
193
193
|
}
|
|
194
194
|
/**
|
|
195
195
|
* Define named guards that APIs reference (typed) via the `guards`
|
|
196
|
-
* option. Each guard is
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
196
|
+
* option. Each guard is built with lambderGuard(): its input mode
|
|
197
|
+
* (apiInput slice of the API's own payload, a separate client-sent
|
|
198
|
+
* guardInput, or none), an optional `session: true` requirement, an
|
|
199
|
+
* optional parameter APIs pass in their declaration (`guards: { name:
|
|
200
|
+
* param }`), and an optional return value that lands typed on the
|
|
201
|
+
* handler's ctx.guardData[name]. Guards run before input validation, in
|
|
202
|
+
* the order the API declares them; a handler refuses by throwing
|
|
203
|
+
* (typically refuse()). Callable multiple times so domain modules can
|
|
204
|
+
* contribute their own; names must not collide.
|
|
204
205
|
*/
|
|
205
206
|
defineApiGuards(guards) {
|
|
206
207
|
this.getOrCreatePolicyEngine().addGuards(guards);
|
|
@@ -256,6 +257,14 @@ export default class Lambder {
|
|
|
256
257
|
this.actionList.push({
|
|
257
258
|
match: (ctx) => ctx.apiName === name ? {} : false,
|
|
258
259
|
actionFn: async (ctx, resolver) => {
|
|
260
|
+
// Replay fast path first: a completed idempotent request must
|
|
261
|
+
// answer its stored response without burning rate-limit quota
|
|
262
|
+
// or re-running guards (no handler executes either way).
|
|
263
|
+
if (this.apiPolicyEngine && schema.idempotency) {
|
|
264
|
+
const replay = await this.apiPolicyEngine.findReplay(name, ctx);
|
|
265
|
+
if (replay)
|
|
266
|
+
return replay;
|
|
267
|
+
}
|
|
259
268
|
if (this.apiPolicyEngine)
|
|
260
269
|
await this.apiPolicyEngine.runPreflight(name, ctx, resolver, schema);
|
|
261
270
|
const inputResult = schema.input.safeParse(ctx.apiPayload);
|
|
@@ -281,6 +290,13 @@ export default class Lambder {
|
|
|
281
290
|
match: (ctx) => ctx.apiName === name ? {} : false,
|
|
282
291
|
actionFn: async (ctx, resolver) => {
|
|
283
292
|
await this.requireSession(ctx, resolver);
|
|
293
|
+
// Replay fast path (after the session fetch: the replay scope
|
|
294
|
+
// is keyed per session): see addApi.
|
|
295
|
+
if (this.apiPolicyEngine && schema.idempotency) {
|
|
296
|
+
const replay = await this.apiPolicyEngine.findReplay(name, ctx);
|
|
297
|
+
if (replay)
|
|
298
|
+
return replay;
|
|
299
|
+
}
|
|
284
300
|
if (this.apiPolicyEngine)
|
|
285
301
|
await this.apiPolicyEngine.runPreflight(name, ctx, resolver, schema);
|
|
286
302
|
const inputResult = schema.input.safeParse(ctx.apiPayload);
|
|
@@ -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
|
package/dist/index.d.ts
CHANGED
|
@@ -1,36 +1,37 @@
|
|
|
1
|
-
import Lambder from './Lambder.js';
|
|
1
|
+
import Lambder from './core/Lambder.js';
|
|
2
2
|
export default Lambder;
|
|
3
|
-
export { default as LambderCaller } from "./LambderCaller.js";
|
|
4
|
-
export type { LambderApiOutcome, LambderApiFailureReason, LambderCallOptions } from "./LambderCaller.js";
|
|
5
|
-
export { LambderApiError, isLambderApiError, refuse } from "./LambderApiError.js";
|
|
6
|
-
export type { LambderApiErrorOptions, LambderRefusalMessage, LambderRefuseOptions } from "./LambderApiError.js";
|
|
7
|
-
export { default as LambderResponseBuilder } from "./LambderResponseBuilder.js";
|
|
8
|
-
export { default as LambderResolver } from "./LambderResolver.js";
|
|
9
|
-
export { default as LambderSessionManager } from "./LambderSessionManager.js";
|
|
10
|
-
export { default as LambderSessionController } from "./LambderSessionController.js";
|
|
11
|
-
export {
|
|
12
|
-
export type
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
-
export type {
|
|
17
|
-
export
|
|
18
|
-
export type {
|
|
19
|
-
export {
|
|
20
|
-
export type {
|
|
21
|
-
export
|
|
22
|
-
export
|
|
23
|
-
export {
|
|
24
|
-
export {
|
|
25
|
-
export type {
|
|
26
|
-
export {
|
|
27
|
-
export type {
|
|
28
|
-
export {
|
|
29
|
-
export type {
|
|
30
|
-
export {
|
|
31
|
-
export type {
|
|
32
|
-
export {
|
|
33
|
-
export
|
|
34
|
-
export {
|
|
35
|
-
export type
|
|
36
|
-
export {
|
|
3
|
+
export { default as LambderCaller } from "./client/LambderCaller.js";
|
|
4
|
+
export type { LambderApiOutcome, LambderApiFailureReason, LambderCallOptions, LambderIdempotencyKeyScope } from "./client/LambderCaller.js";
|
|
5
|
+
export { LambderApiError, isLambderApiError, refuse } from "./shared/LambderApiError.js";
|
|
6
|
+
export type { LambderApiErrorOptions, LambderRefusalMessage, LambderRefuseOptions } from "./shared/LambderApiError.js";
|
|
7
|
+
export { default as LambderResponseBuilder } from "./core/LambderResponseBuilder.js";
|
|
8
|
+
export { default as LambderResolver } from "./core/LambderResolver.js";
|
|
9
|
+
export { default as LambderSessionManager } from "./session/LambderSessionManager.js";
|
|
10
|
+
export { default as LambderSessionController } from "./session/LambderSessionController.js";
|
|
11
|
+
export { LambderResponse, finalizeResponse, acceptsEncoding, type HttpStatusCode, type LambderHttpResponse, type LambderHttpEventFormat, type LambderHeadersInput, type LambderFinalizeOptions, } from "./core/LambderResponse.js";
|
|
12
|
+
export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml, type LambderHtmlValue } from "./shared/LambderHtml.js";
|
|
13
|
+
export { LambderTemplatingEngine } from "./core/LambderTemplatingEngine.js";
|
|
14
|
+
export type { LambderTemplateData, LambderTemplatingEngineOptions } from "./core/LambderTemplatingEngine.js";
|
|
15
|
+
export type { LambderResponseOptions, LambderRawResponseInit, } from "./core/LambderResponseBuilder.js";
|
|
16
|
+
export type { LambderRouteMatcher, LambderCorsConfig, LambderConstructorOptions, ConditionFunction, RouteCondition, PathParamsOf, LambderActionTools, LambderHandler, LambderIndexHtmlOptions, LambderApp, } from "./core/Lambder.js";
|
|
17
|
+
export { LambderPublicFilesHandler } from "./core/LambderPublicFiles.js";
|
|
18
|
+
export type { LambderPublicFilesOptions } from "./core/LambderPublicFiles.js";
|
|
19
|
+
export type { LambderSessionCookieOptions } from "./session/LambderSessionController.js";
|
|
20
|
+
export type { LambderSessionContext, LambderCreatedSession, LambderSessionDataRefreshConfig } from "./session/LambderSessionManager.js";
|
|
21
|
+
export { LambderSessionDataRefreshError, LambderSessionReadError } from "./session/LambderSessionManager.js";
|
|
22
|
+
export { LambderDdbCache } from "./stores/LambderDdbCache.js";
|
|
23
|
+
export type { LambderDdbCacheOptions, LambderDdbCacheSetOptions, LambderDdbCacheGetOrSetOptions, } from "./stores/LambderDdbCache.js";
|
|
24
|
+
export { LambderDdbRateLimiter } from "./stores/LambderDdbRateLimiter.js";
|
|
25
|
+
export type { LambderDdbRateLimiterOptions, LambderRateLimitPolicy, LambderRateLimitExceededMap, LambderRateLimitResult, } from "./stores/LambderDdbRateLimiter.js";
|
|
26
|
+
export { LambderDdbIdempotency } from "./stores/LambderDdbIdempotency.js";
|
|
27
|
+
export type { LambderDdbIdempotencyOptions, LambderIdempotencyBeginResult, LambderIdempotencyDoneRecord, } from "./stores/LambderDdbIdempotency.js";
|
|
28
|
+
export { lambderGuard } from "./policies/LambderApiGuards.js";
|
|
29
|
+
export type { LambderApiGuard, LambderGuardMeta, LambderGuardMetaMap, LambderAllowedGuardNames, LambderParamlessGuardNames, LambderGuardsOption, LambderGuardsOptionValue, LambderGuardDataOf, LambderGuardInputsOf, } from "./policies/LambderApiGuards.js";
|
|
30
|
+
export { lambderRateLimitKey } from "./policies/LambderApiRateLimits.js";
|
|
31
|
+
export type { LambderRateLimitKeyFn, LambderRateLimitPer, LambderApiRateLimitPolicyConfig, LambderApiRateLimitsConfig, LambderAllowedPolicyNames, } from "./policies/LambderApiRateLimits.js";
|
|
32
|
+
export type { LambderApiIdempotencyConfig } from "./policies/LambderApiIdempotency.js";
|
|
33
|
+
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
34
|
+
export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./shared/LambderI18n.js";
|
|
35
|
+
export { type ApiContractShape, type LambderApiResponse, type LambderApiResponseConfig, } from "./shared/LambderApiContract.js";
|
|
36
|
+
export type { LambderRenderContext, LambderSessionRenderContext, LambderHttpEvent } from "./core/LambderContext.js";
|
|
37
|
+
export { createContext, isV2HttpEvent } from "./core/LambderContext.js";
|
package/dist/index.js
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
import Lambder from './Lambder.js';
|
|
1
|
+
import Lambder from './core/Lambder.js';
|
|
2
2
|
export default Lambder;
|
|
3
|
-
export { default as LambderCaller } from "./LambderCaller.js";
|
|
3
|
+
export { default as LambderCaller } from "./client/LambderCaller.js";
|
|
4
4
|
// Typed API refusals (isomorphic: shared code may throw them from anywhere)
|
|
5
|
-
export { LambderApiError, isLambderApiError, refuse } from "./LambderApiError.js";
|
|
6
|
-
export { default as LambderResponseBuilder } from "./LambderResponseBuilder.js";
|
|
7
|
-
export { default as LambderResolver } from "./LambderResolver.js";
|
|
8
|
-
export { default as LambderSessionManager } from "./LambderSessionManager.js";
|
|
9
|
-
export { default as LambderSessionController } from "./LambderSessionController.js";
|
|
10
|
-
export { default as LambderMSW } from "./LambderMSW.js";
|
|
5
|
+
export { LambderApiError, isLambderApiError, refuse } from "./shared/LambderApiError.js";
|
|
6
|
+
export { default as LambderResponseBuilder } from "./core/LambderResponseBuilder.js";
|
|
7
|
+
export { default as LambderResolver } from "./core/LambderResolver.js";
|
|
8
|
+
export { default as LambderSessionManager } from "./session/LambderSessionManager.js";
|
|
9
|
+
export { default as LambderSessionController } from "./session/LambderSessionController.js";
|
|
11
10
|
// Response model
|
|
12
|
-
export { LambderResponse, finalizeResponse, acceptsEncoding, } from "./LambderResponse.js";
|
|
11
|
+
export { LambderResponse, finalizeResponse, acceptsEncoding, } from "./core/LambderResponse.js";
|
|
13
12
|
// Type-safe templating (tagged templates with auto-escaping)
|
|
14
|
-
export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml } from "./LambderHtml.js";
|
|
13
|
+
export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml } from "./shared/LambderHtml.js";
|
|
15
14
|
// Comment-based HTML templating engine (build-pipeline-safe slots and conditionals, standalone)
|
|
16
|
-
export { LambderTemplatingEngine } from "./LambderTemplatingEngine.js";
|
|
15
|
+
export { LambderTemplatingEngine } from "./core/LambderTemplatingEngine.js";
|
|
17
16
|
// Public file serving
|
|
18
|
-
export { LambderPublicFilesHandler } from "./LambderPublicFiles.js";
|
|
19
|
-
export { LambderSessionDataRefreshError } from "./LambderSessionManager.js";
|
|
17
|
+
export { LambderPublicFilesHandler } from "./core/LambderPublicFiles.js";
|
|
18
|
+
export { LambderSessionDataRefreshError, LambderSessionReadError } from "./session/LambderSessionManager.js";
|
|
20
19
|
// DynamoDB-backed compressed cache (standalone, server-only)
|
|
21
|
-
export { LambderDdbCache } from "./LambderDdbCache.js";
|
|
20
|
+
export { LambderDdbCache } from "./stores/LambderDdbCache.js";
|
|
22
21
|
// DynamoDB-backed fixed-window rate limiter (standalone, server-only)
|
|
23
|
-
export { LambderDdbRateLimiter } from "./LambderDdbRateLimiter.js";
|
|
22
|
+
export { LambderDdbRateLimiter } from "./stores/LambderDdbRateLimiter.js";
|
|
24
23
|
// DynamoDB-backed idempotency records (standalone, server-only)
|
|
25
|
-
export { LambderDdbIdempotency } from "./LambderDdbIdempotency.js";
|
|
26
|
-
// Declarative per-API policies
|
|
27
|
-
export { lambderGuard
|
|
24
|
+
export { LambderDdbIdempotency } from "./stores/LambderDdbIdempotency.js";
|
|
25
|
+
// Declarative per-API policies: guards
|
|
26
|
+
export { lambderGuard } from "./policies/LambderApiGuards.js";
|
|
27
|
+
// Declarative per-API policies: rate limits
|
|
28
|
+
export { lambderRateLimitKey } from "./policies/LambderApiRateLimits.js";
|
|
28
29
|
// Typed translations (standalone, isomorphic)
|
|
29
|
-
export { createLambderI18n } from "./LambderI18n.js";
|
|
30
|
-
export { createContext, isV2HttpEvent } from "./LambderContext.js";
|
|
30
|
+
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
31
|
+
export { createContext, isV2HttpEvent } from "./core/LambderContext.js";
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { LambderRenderContext, LambderSessionRenderContext } from "../core/LambderContext.js";
|
|
3
|
+
import type LambderResolver from "../core/LambderResolver.js";
|
|
4
|
+
/**
|
|
5
|
+
* A named guard, run before the API's own input validation. Three input
|
|
6
|
+
* modes:
|
|
7
|
+
*
|
|
8
|
+
* - `apiInput`: the guard checks fields of the API's OWN payload. The slice
|
|
9
|
+
* is validated against the raw payload before `handler` runs and handed to
|
|
10
|
+
* it typed. The API's input schema stays the owner of those fields:
|
|
11
|
+
* declaring the guard on an API whose schema does not carry them is a
|
|
12
|
+
* compile error.
|
|
13
|
+
* - `guardInput`: the guard has its own value the client sends SEPARATELY,
|
|
14
|
+
* outside the API payload, via the caller's options.guardInputs[name].
|
|
15
|
+
* The requirement lands on the API's contract (`guardInputs`), so the
|
|
16
|
+
* typed caller refuses to compile a call that does not send it. The API
|
|
17
|
+
* payload and handler never see the value.
|
|
18
|
+
* - neither: the guard reads only the context.
|
|
19
|
+
*
|
|
20
|
+
* Orthogonally, a guard may also:
|
|
21
|
+
*
|
|
22
|
+
* - declare `session: true`: the guard needs ctx.session, so it is only
|
|
23
|
+
* declarable on addSessionApi (compile error and startup assert on public
|
|
24
|
+
* APIs) and its handler receives the session-typed context.
|
|
25
|
+
* - take a PARAMETER: annotate a 4th handler argument
|
|
26
|
+
* (`(ctx, payload, res, param: YourType) => ...`) and APIs pass the value
|
|
27
|
+
* in their declaration: `guards: { yourGuard: paramValue }`. The value is
|
|
28
|
+
* trusted registration-time code (never client data), typed per guard.
|
|
29
|
+
* - RETURN a value: whatever the handler returns (awaited) is attached to
|
|
30
|
+
* the API handler's context as `ctx.guardData[guardName]`, fully typed.
|
|
31
|
+
* Guards that return nothing never appear in guardData.
|
|
32
|
+
*
|
|
33
|
+
* A validation failure answers the standard 422 shape, and the handler
|
|
34
|
+
* refuses by throwing (typically refuse()/LambderApiError). Build with
|
|
35
|
+
* lambderGuard() so the handler's payload/ctx/param types line up.
|
|
36
|
+
*/
|
|
37
|
+
export type LambderApiGuard<TInput extends z.ZodTypeAny = z.ZodTypeAny, TParam = any, TOutput = any> = {
|
|
38
|
+
apiInput: TInput;
|
|
39
|
+
guardInput?: undefined;
|
|
40
|
+
session?: boolean;
|
|
41
|
+
handler: (ctx: any, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
42
|
+
} | {
|
|
43
|
+
guardInput: TInput;
|
|
44
|
+
apiInput?: undefined;
|
|
45
|
+
session?: boolean;
|
|
46
|
+
handler: (ctx: any, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
47
|
+
} | {
|
|
48
|
+
apiInput?: undefined;
|
|
49
|
+
guardInput?: undefined;
|
|
50
|
+
session?: boolean;
|
|
51
|
+
handler: (ctx: any, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
52
|
+
};
|
|
53
|
+
type GuardCtx = LambderRenderContext;
|
|
54
|
+
type GuardSessionCtx = LambderSessionRenderContext<any, any>;
|
|
55
|
+
/**
|
|
56
|
+
* Builder that ties the handler's payload, context, param, and output types
|
|
57
|
+
* together inside one literal. Returns the exact shape so type extraction
|
|
58
|
+
* (mode, session, param, output) works downstream. The param type is
|
|
59
|
+
* inferred from the handler's 4th argument annotation; the output from its
|
|
60
|
+
* return type.
|
|
61
|
+
*/
|
|
62
|
+
export declare function lambderGuard<TInput extends z.ZodTypeAny, TParam = undefined, TOutput = void>(guard: {
|
|
63
|
+
apiInput: TInput;
|
|
64
|
+
session: true;
|
|
65
|
+
handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
66
|
+
}): {
|
|
67
|
+
apiInput: TInput;
|
|
68
|
+
guardInput?: undefined;
|
|
69
|
+
session: true;
|
|
70
|
+
handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
71
|
+
};
|
|
72
|
+
export declare function lambderGuard<TInput extends z.ZodTypeAny, TParam = undefined, TOutput = void>(guard: {
|
|
73
|
+
apiInput: TInput;
|
|
74
|
+
handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
75
|
+
}): {
|
|
76
|
+
apiInput: TInput;
|
|
77
|
+
guardInput?: undefined;
|
|
78
|
+
session?: undefined;
|
|
79
|
+
handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
80
|
+
};
|
|
81
|
+
export declare function lambderGuard<TInput extends z.ZodTypeAny, TParam = undefined, TOutput = void>(guard: {
|
|
82
|
+
guardInput: TInput;
|
|
83
|
+
session: true;
|
|
84
|
+
handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
85
|
+
}): {
|
|
86
|
+
guardInput: TInput;
|
|
87
|
+
apiInput?: undefined;
|
|
88
|
+
session: true;
|
|
89
|
+
handler: (ctx: GuardSessionCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
90
|
+
};
|
|
91
|
+
export declare function lambderGuard<TInput extends z.ZodTypeAny, TParam = undefined, TOutput = void>(guard: {
|
|
92
|
+
guardInput: TInput;
|
|
93
|
+
handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
94
|
+
}): {
|
|
95
|
+
guardInput: TInput;
|
|
96
|
+
apiInput?: undefined;
|
|
97
|
+
session?: undefined;
|
|
98
|
+
handler: (ctx: GuardCtx, payload: z.output<TInput>, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
99
|
+
};
|
|
100
|
+
export declare function lambderGuard<TParam = undefined, TOutput = void>(guard: {
|
|
101
|
+
session: true;
|
|
102
|
+
handler: (ctx: GuardSessionCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
103
|
+
}): {
|
|
104
|
+
apiInput?: undefined;
|
|
105
|
+
guardInput?: undefined;
|
|
106
|
+
session: true;
|
|
107
|
+
handler: (ctx: GuardSessionCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
108
|
+
};
|
|
109
|
+
export declare function lambderGuard<TParam = undefined, TOutput = void>(guard: {
|
|
110
|
+
handler: (ctx: GuardCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
111
|
+
}): {
|
|
112
|
+
apiInput?: undefined;
|
|
113
|
+
guardInput?: undefined;
|
|
114
|
+
session?: undefined;
|
|
115
|
+
handler: (ctx: GuardCtx, payload: undefined, res: LambderResolver, param: TParam) => TOutput | Promise<TOutput>;
|
|
116
|
+
};
|
|
117
|
+
/** The param type a guard's handler declares as its 4th argument; undefined for paramless guards. */
|
|
118
|
+
type LambderGuardParamOf<G> = G extends {
|
|
119
|
+
handler: (...args: infer A) => any;
|
|
120
|
+
} ? (A extends [any, any, any, infer P, ...any[]] ? P : undefined) : undefined;
|
|
121
|
+
/** What a guard's handler returns (awaited); void for check-only guards. */
|
|
122
|
+
type LambderGuardOutputOf<G> = G extends {
|
|
123
|
+
handler: (...args: any[]) => infer R;
|
|
124
|
+
} ? Awaited<R> : never;
|
|
125
|
+
/** Per-guard metadata carried on the Lambder instance: input mode, session requirement, param type, output type. */
|
|
126
|
+
export type LambderGuardMeta<G> = (G extends {
|
|
127
|
+
apiInput: infer S extends z.ZodTypeAny;
|
|
128
|
+
} ? {
|
|
129
|
+
apiInput: z.output<S>;
|
|
130
|
+
} : G extends {
|
|
131
|
+
guardInput: infer S extends z.ZodTypeAny;
|
|
132
|
+
} ? {
|
|
133
|
+
guardInput: z.output<S>;
|
|
134
|
+
} : {}) & (G extends {
|
|
135
|
+
session: true;
|
|
136
|
+
} ? {
|
|
137
|
+
session: true;
|
|
138
|
+
} : {}) & {
|
|
139
|
+
param: LambderGuardParamOf<G>;
|
|
140
|
+
output: LambderGuardOutputOf<G>;
|
|
141
|
+
};
|
|
142
|
+
export type LambderGuardMetaMap<TGuards> = {
|
|
143
|
+
[K in keyof TGuards]: LambderGuardMeta<TGuards[K]>;
|
|
144
|
+
};
|
|
145
|
+
/** Guard names referenced by a guards option, whichever of its three forms is used. */
|
|
146
|
+
type NamesIn<TOpt> = TOpt extends string ? TOpt : TOpt extends readonly (infer N extends string)[] ? N : TOpt extends object ? keyof TOpt & string : never;
|
|
147
|
+
type LambderGuardNameIfPayloadOk<TGuards, K extends keyof TGuards, TPayload> = TGuards[K] extends {
|
|
148
|
+
apiInput: infer R;
|
|
149
|
+
} ? (TPayload extends R ? K : never) : K;
|
|
150
|
+
/**
|
|
151
|
+
* Guard names an API may declare: apiInput-mode guards only when the API's
|
|
152
|
+
* payload carries their fields, session guards only on session APIs.
|
|
153
|
+
*/
|
|
154
|
+
export type LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession extends boolean = true> = {
|
|
155
|
+
[K in keyof TGuards]: TGuards[K] extends {
|
|
156
|
+
session: true;
|
|
157
|
+
} ? (TIncludeSession extends true ? LambderGuardNameIfPayloadOk<TGuards, K, TPayload> : never) : LambderGuardNameIfPayloadOk<TGuards, K, TPayload>;
|
|
158
|
+
}[keyof TGuards] & string;
|
|
159
|
+
/** The allowed guard names whose handler takes no param (usable in the string/array forms). */
|
|
160
|
+
export type LambderParamlessGuardNames<TGuards, TPayload, TIncludeSession extends boolean> = {
|
|
161
|
+
[K in LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession> & keyof TGuards]: TGuards[K] extends {
|
|
162
|
+
param: undefined;
|
|
163
|
+
} ? K & string : never;
|
|
164
|
+
}[LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession> & keyof TGuards];
|
|
165
|
+
/**
|
|
166
|
+
* The per-API `guards` option: one paramless guard name, an ordered list of
|
|
167
|
+
* paramless names, or an object map that can carry each guard's param
|
|
168
|
+
* (`true` enables a paramless guard). Map entries run in insertion order.
|
|
169
|
+
*/
|
|
170
|
+
export type LambderGuardsOption<TGuards, TPayload, TIncludeSession extends boolean> = LambderParamlessGuardNames<TGuards, TPayload, TIncludeSession> | readonly LambderParamlessGuardNames<TGuards, TPayload, TIncludeSession>[] | {
|
|
171
|
+
readonly [K in LambderAllowedGuardNames<TGuards, TPayload, TIncludeSession> & keyof TGuards]?: TGuards[K] extends {
|
|
172
|
+
param: undefined;
|
|
173
|
+
} ? true : TGuards[K] extends {
|
|
174
|
+
param: infer P;
|
|
175
|
+
} ? P : true;
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* The typed ctx.guardData an API's handler sees: declared guards that return
|
|
179
|
+
* a value, keyed by name. Check-only (void) guards never appear.
|
|
180
|
+
*/
|
|
181
|
+
export type LambderGuardDataOf<TGuards, TOpt> = {
|
|
182
|
+
[K in NamesIn<TOpt> & keyof TGuards as [
|
|
183
|
+
TGuards[K] extends {
|
|
184
|
+
output: infer O;
|
|
185
|
+
} ? O : never
|
|
186
|
+
] extends [void] ? never : K & string]: TGuards[K] extends {
|
|
187
|
+
output: infer O;
|
|
188
|
+
} ? O : never;
|
|
189
|
+
};
|
|
190
|
+
type GuardInputsEntries<TGuards, TOpt> = {
|
|
191
|
+
[K in Extract<NamesIn<TOpt>, keyof TGuards> as TGuards[K] extends {
|
|
192
|
+
guardInput: any;
|
|
193
|
+
} ? K : never]: TGuards[K] extends {
|
|
194
|
+
guardInput: infer V;
|
|
195
|
+
} ? V : never;
|
|
196
|
+
};
|
|
197
|
+
/** The guardInputs map an API's contract requires clients to send; never when no declared guard uses guardInput mode. */
|
|
198
|
+
export type LambderGuardInputsOf<TGuards, TOpt> = keyof GuardInputsEntries<TGuards, TOpt> extends never ? never : GuardInputsEntries<TGuards, TOpt>;
|
|
199
|
+
/** The guards option's runtime shape: a name, ordered names, or a name-to-param map. */
|
|
200
|
+
export type LambderGuardsOptionValue = string | readonly string[] | Readonly<Record<string, unknown>>;
|
|
201
|
+
/**
|
|
202
|
+
* Validate a preflight input slice (an apiInput slice of the raw payload, or
|
|
203
|
+
* a guardInput value from the raw guardInputs map). Runs before the API's
|
|
204
|
+
* own validation; failures answer the same 422 shape as regular input
|
|
205
|
+
* validation. Shared with the rate-limit engine's apiInput-keyed policies.
|
|
206
|
+
*/
|
|
207
|
+
export declare const parsePreflightSlice: (input: z.ZodTypeAny, value: unknown, resolver: LambderResolver) => unknown;
|
|
208
|
+
/**
|
|
209
|
+
* Runtime side of the guards subsystem: holds the defined guards, asserts
|
|
210
|
+
* API registrations against them at startup, and executes an API's declared
|
|
211
|
+
* guards during preflight. Composed into LambderApiPolicyEngine.
|
|
212
|
+
*/
|
|
213
|
+
export declare class LambderApiGuardsEngine {
|
|
214
|
+
private guards;
|
|
215
|
+
addGuards(guards: Record<string, LambderApiGuard<any, any, any>>): void;
|
|
216
|
+
/** Startup validation of one API registration's guards option. */
|
|
217
|
+
assertRegistration(apiName: string, mode: "public" | "session", guardsOption?: LambderGuardsOptionValue): void;
|
|
218
|
+
/** Run the API's guards in declared order. Refusals throw; outputs land on ctx.guardData. */
|
|
219
|
+
run(ctx: LambderRenderContext, resolver: LambderResolver, guardsOption?: LambderGuardsOptionValue): Promise<void>;
|
|
220
|
+
}
|
|
221
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export function lambderGuard(guard) { return guard; }
|
|
2
|
+
/** Normalize the three guards-option forms into ordered { name, param } entries. */
|
|
3
|
+
const toGuardEntries = (value) => {
|
|
4
|
+
if (value === undefined)
|
|
5
|
+
return [];
|
|
6
|
+
if (typeof value === "string")
|
|
7
|
+
return [{ name: value, param: undefined }];
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return value.map((name) => ({ name: String(name), param: undefined }));
|
|
10
|
+
// Object form: insertion order, params passed verbatim (paramless guards
|
|
11
|
+
// are declared with `true` and their handlers take no param argument).
|
|
12
|
+
return Object.entries(value).map(([name, param]) => ({ name, param }));
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Validate a preflight input slice (an apiInput slice of the raw payload, or
|
|
16
|
+
* a guardInput value from the raw guardInputs map). Runs before the API's
|
|
17
|
+
* own validation; failures answer the same 422 shape as regular input
|
|
18
|
+
* validation. Shared with the rate-limit engine's apiInput-keyed policies.
|
|
19
|
+
*/
|
|
20
|
+
export const parsePreflightSlice = (input, value, resolver) => {
|
|
21
|
+
const parsed = input.safeParse(value);
|
|
22
|
+
if (!parsed.success) {
|
|
23
|
+
throw resolver.json({ error: "Input validation failed", zodError: parsed.error }, { statusCode: 422 });
|
|
24
|
+
}
|
|
25
|
+
return parsed.data;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Runtime side of the guards subsystem: holds the defined guards, asserts
|
|
29
|
+
* API registrations against them at startup, and executes an API's declared
|
|
30
|
+
* guards during preflight. Composed into LambderApiPolicyEngine.
|
|
31
|
+
*/
|
|
32
|
+
export class LambderApiGuardsEngine {
|
|
33
|
+
guards = {};
|
|
34
|
+
addGuards(guards) {
|
|
35
|
+
for (const [name, guardDef] of Object.entries(guards)) {
|
|
36
|
+
if (this.guards[name])
|
|
37
|
+
throw new Error(`Lambder: guard "${name}" is already defined.`);
|
|
38
|
+
if (typeof guardDef?.handler !== "function")
|
|
39
|
+
throw new Error(`Lambder: guard "${name}" has no handler function.`);
|
|
40
|
+
if (guardDef.apiInput && guardDef.guardInput)
|
|
41
|
+
throw new Error(`Lambder: guard "${name}" declares both apiInput and guardInput; pick one.`);
|
|
42
|
+
this.guards[name] = guardDef;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Startup validation of one API registration's guards option. */
|
|
46
|
+
assertRegistration(apiName, mode, guardsOption) {
|
|
47
|
+
for (const { name } of toGuardEntries(guardsOption)) {
|
|
48
|
+
const guardDef = this.guards[name];
|
|
49
|
+
if (!guardDef) {
|
|
50
|
+
throw new Error(`Lambder: API "${apiName}" references unknown guard "${name}". Define it via defineApiGuards() before registering the API.`);
|
|
51
|
+
}
|
|
52
|
+
if (guardDef.session && mode !== "session") {
|
|
53
|
+
throw new Error(`Lambder: API "${apiName}" uses guard "${name}" (session: true), which requires addSessionApi.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Run the API's guards in declared order. Refusals throw; outputs land on ctx.guardData. */
|
|
58
|
+
async run(ctx, resolver, guardsOption) {
|
|
59
|
+
for (const { name, param } of toGuardEntries(guardsOption)) {
|
|
60
|
+
const guardDef = this.guards[name];
|
|
61
|
+
if (!guardDef)
|
|
62
|
+
throw new Error(`Lambder: guard "${name}" is not configured.`);
|
|
63
|
+
const post = ctx.post;
|
|
64
|
+
let payload;
|
|
65
|
+
if (guardDef.apiInput) {
|
|
66
|
+
payload = parsePreflightSlice(guardDef.apiInput, post?.payload, resolver);
|
|
67
|
+
}
|
|
68
|
+
else if (guardDef.guardInput) {
|
|
69
|
+
payload = parsePreflightSlice(guardDef.guardInput, post?.guardInputs?.[name], resolver);
|
|
70
|
+
}
|
|
71
|
+
// A guard's return value becomes the handler's typed
|
|
72
|
+
// ctx.guardData[name]; check-only guards return undefined.
|
|
73
|
+
const output = await guardDef.handler(ctx, payload, resolver, param);
|
|
74
|
+
if (output !== undefined) {
|
|
75
|
+
ctx.guardData[name] = output;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|