lambder 3.8.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.
Files changed (62) hide show
  1. package/Readme.md +79 -14
  2. package/dist/{LambderCaller.d.ts → client/LambderCaller.d.ts} +30 -12
  3. package/dist/{LambderCaller.js → client/LambderCaller.js} +21 -12
  4. package/dist/{LambderMSW.d.ts → client/LambderMSW.d.ts} +1 -1
  5. package/dist/client.d.ts +16 -0
  6. package/dist/client.js +17 -0
  7. package/dist/{Lambder.d.ts → core/Lambder.d.ts} +45 -20
  8. package/dist/{Lambder.js → core/Lambder.js} +28 -13
  9. package/dist/{LambderContext.d.ts → core/LambderContext.d.ts} +9 -3
  10. package/dist/{LambderContext.js → core/LambderContext.js} +1 -0
  11. package/dist/{LambderPublicFiles.js → core/LambderPublicFiles.js} +1 -1
  12. package/dist/{LambderResponse.js → core/LambderResponse.js} +1 -1
  13. package/dist/{LambderResponseBuilder.d.ts → core/LambderResponseBuilder.d.ts} +3 -15
  14. package/dist/{LambderResponseBuilder.js → core/LambderResponseBuilder.js} +2 -2
  15. package/dist/{LambderTemplatingEngine.d.ts → core/LambderTemplatingEngine.d.ts} +1 -1
  16. package/dist/{LambderTemplatingEngine.js → core/LambderTemplatingEngine.js} +2 -2
  17. package/dist/index.d.ts +36 -35
  18. package/dist/index.js +21 -20
  19. package/dist/policies/LambderApiGuards.d.ts +221 -0
  20. package/dist/policies/LambderApiGuards.js +79 -0
  21. package/dist/policies/LambderApiIdempotency.d.ts +58 -0
  22. package/dist/policies/LambderApiIdempotency.js +215 -0
  23. package/dist/policies/LambderApiPolicies.d.ts +40 -0
  24. package/dist/policies/LambderApiPolicies.js +47 -0
  25. package/dist/policies/LambderApiRateLimits.d.ts +90 -0
  26. package/dist/policies/LambderApiRateLimits.js +77 -0
  27. package/dist/{LambderSessionController.d.ts → session/LambderSessionController.d.ts} +2 -1
  28. package/dist/{LambderSessionController.js → session/LambderSessionController.js} +16 -11
  29. package/dist/{LambderSessionManager.d.ts → session/LambderSessionManager.d.ts} +38 -4
  30. package/dist/{LambderSessionManager.js → session/LambderSessionManager.js} +49 -16
  31. package/dist/{LambderApiContract.d.ts → shared/LambderApiContract.d.ts} +14 -0
  32. package/dist/{LambderApiError.d.ts → shared/LambderApiError.d.ts} +1 -1
  33. package/dist/{LambderDdbCache.js → stores/LambderDdbCache.js} +7 -38
  34. package/dist/stores/LambderDdbCompression.d.ts +3 -0
  35. package/dist/stores/LambderDdbCompression.js +39 -0
  36. package/dist/{LambderDdbIdempotency.d.ts → stores/LambderDdbIdempotency.d.ts} +41 -12
  37. package/dist/{LambderDdbIdempotency.js → stores/LambderDdbIdempotency.js} +100 -11
  38. package/dist/{LambderDdbRateLimiter.js → stores/LambderDdbRateLimiter.js} +5 -1
  39. package/dist/testing.d.ts +9 -0
  40. package/dist/testing.js +8 -0
  41. package/package.json +19 -1
  42. package/dist/LambderApiPolicies.d.ts +0 -212
  43. package/dist/LambderApiPolicies.js +0 -228
  44. /package/dist/{LambderMSW.js → client/LambderMSW.js} +0 -0
  45. /package/dist/{LambderCors.d.ts → core/LambderCors.d.ts} +0 -0
  46. /package/dist/{LambderCors.js → core/LambderCors.js} +0 -0
  47. /package/dist/{LambderPublicFiles.d.ts → core/LambderPublicFiles.d.ts} +0 -0
  48. /package/dist/{LambderResolver.d.ts → core/LambderResolver.d.ts} +0 -0
  49. /package/dist/{LambderResolver.js → core/LambderResolver.js} +0 -0
  50. /package/dist/{LambderResponse.d.ts → core/LambderResponse.d.ts} +0 -0
  51. /package/dist/{LambderRouting.d.ts → core/LambderRouting.d.ts} +0 -0
  52. /package/dist/{LambderRouting.js → core/LambderRouting.js} +0 -0
  53. /package/dist/{LambderApiContract.js → shared/LambderApiContract.js} +0 -0
  54. /package/dist/{LambderApiError.js → shared/LambderApiError.js} +0 -0
  55. /package/dist/{LambderHtml.d.ts → shared/LambderHtml.d.ts} +0 -0
  56. /package/dist/{LambderHtml.js → shared/LambderHtml.js} +0 -0
  57. /package/dist/{LambderI18n.d.ts → shared/LambderI18n.d.ts} +0 -0
  58. /package/dist/{LambderI18n.js → shared/LambderI18n.js} +0 -0
  59. /package/dist/{node-polyfills.d.ts → shared/node-polyfills.d.ts} +0 -0
  60. /package/dist/{node-polyfills.js → shared/node-polyfills.js} +0 -0
  61. /package/dist/{LambderDdbCache.d.ts → stores/LambderDdbCache.d.ts} +0 -0
  62. /package/dist/{LambderDdbRateLimiter.d.ts → stores/LambderDdbRateLimiter.d.ts} +0 -0
@@ -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 "./LambderHtml.js";
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 "./node-polyfills.js";
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 ?? init.multiValueHeaders,
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,4 +1,4 @@
1
- import { type LambderHtmlValue } from "./LambderHtml.js";
1
+ import { type LambderHtmlValue } from "../shared/LambderHtml.js";
2
2
  /**
3
3
  * LambderTemplatingEngine: a comment-only HTML template engine.
4
4
  *
@@ -1,5 +1,5 @@
1
- import { getFS } from "./node-polyfills.js";
2
- import { renderHtmlValue } from "./LambderHtml.js";
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 { default as LambderMSW } from "./LambderMSW.js";
12
- export type { LambderMswModule } from "./LambderMSW.js";
13
- export { LambderResponse, finalizeResponse, acceptsEncoding, type HttpStatusCode, type LambderHttpResponse, type LambderHttpEventFormat, type LambderHeadersInput, type LambderFinalizeOptions, } from "./LambderResponse.js";
14
- export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml, type LambderHtmlValue } from "./LambderHtml.js";
15
- export { LambderTemplatingEngine } from "./LambderTemplatingEngine.js";
16
- export type { LambderTemplateData, LambderTemplatingEngineOptions } from "./LambderTemplatingEngine.js";
17
- export type { LambderResponseOptions, LambderApiResponse, LambderApiResponseConfig, LambderRawResponseInit, } from "./LambderResponseBuilder.js";
18
- export type { LambderRouteMatcher, LambderCorsConfig, LambderConstructorOptions, ConditionFunction, RouteCondition, PathParamsOf, LambderActionTools, LambderHandler, LambderIndexHtmlOptions, } from "./Lambder.js";
19
- export { LambderPublicFilesHandler } from "./LambderPublicFiles.js";
20
- export type { LambderPublicFilesOptions } from "./LambderPublicFiles.js";
21
- export type { LambderSessionCookieOptions } from "./LambderSessionController.js";
22
- export type { LambderSessionContext, LambderSessionDataRefreshConfig } from "./LambderSessionManager.js";
23
- export { LambderSessionDataRefreshError } from "./LambderSessionManager.js";
24
- export { LambderDdbCache } from "./LambderDdbCache.js";
25
- export type { LambderDdbCacheOptions, LambderDdbCacheSetOptions, LambderDdbCacheGetOrSetOptions, } from "./LambderDdbCache.js";
26
- export { LambderDdbRateLimiter } from "./LambderDdbRateLimiter.js";
27
- export type { LambderDdbRateLimiterOptions, LambderRateLimitPolicy, LambderRateLimitExceededMap, LambderRateLimitResult, } from "./LambderDdbRateLimiter.js";
28
- export { LambderDdbIdempotency } from "./LambderDdbIdempotency.js";
29
- export type { LambderDdbIdempotencyOptions, LambderIdempotencyBeginResult, } from "./LambderDdbIdempotency.js";
30
- export { lambderGuard, lambderRateLimitKey } from "./LambderApiPolicies.js";
31
- export type { LambderApiGuard, LambderRateLimitKeyFn, LambderRateLimitPer, LambderApiRateLimitPolicyConfig, LambderApiRateLimitsConfig, LambderApiIdempotencyConfig, LambderGuardMeta, LambderGuardMetaMap, LambderAllowedGuardNames, LambderAllowedPolicyNames, LambderGuardInputsOf, } from "./LambderApiPolicies.js";
32
- export { createLambderI18n } from "./LambderI18n.js";
33
- export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./LambderI18n.js";
34
- export { type ApiContractShape, } from "./LambderApiContract.js";
35
- export type { LambderRenderContext, LambderSessionRenderContext, LambderHttpEvent } from "./LambderContext.js";
36
- export { createContext, isV2HttpEvent } from "./LambderContext.js";
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 (rate limits, guards, idempotency)
27
- export { lambderGuard, lambderRateLimitKey } from "./LambderApiPolicies.js";
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
+ }
@@ -0,0 +1,58 @@
1
+ import type { LambderRenderContext } from "../core/LambderContext.js";
2
+ import type { LambderDdbIdempotency } from "../stores/LambderDdbIdempotency.js";
3
+ import { LambderResponse } from "../core/LambderResponse.js";
4
+ export type LambderApiIdempotencyConfig = {
5
+ /** Your idempotency store instance; may share the rate limiter's table (distinct key prefix). */
6
+ store: LambderDdbIdempotency;
7
+ /** Seconds a stored response replays for. Default: 86400 (24h). Per-API override: idempotency: { ttlSeconds }. */
8
+ defaultTtlSeconds?: number;
9
+ /** Skip idempotency (execute normally) when DynamoDB errors, instead of failing the request. Default: true. */
10
+ failOpen?: boolean;
11
+ };
12
+ /**
13
+ * Runtime side of the idempotency subsystem: claims a per-operation scope
14
+ * around handler execution, replays stored responses, and settles claims.
15
+ * Composed into LambderApiPolicyEngine.
16
+ */
17
+ export declare class LambderApiIdempotencyEngine {
18
+ private store;
19
+ private defaultTtlSeconds;
20
+ private failOpen;
21
+ configure(config: LambderApiIdempotencyConfig): void;
22
+ /** True once enableApiIdempotency() ran; registration asserts check it. */
23
+ get isConfigured(): boolean;
24
+ /**
25
+ * The request's idempotencyKey: null when absent, the key when valid, a
26
+ * 400 refusal when malformed. The minimum length matters for security:
27
+ * see IDEMPOTENCY_MIN_KEY_LENGTH.
28
+ */
29
+ private readKey;
30
+ /**
31
+ * The record's scope. Session APIs scope per session, so even a leaked
32
+ * key cannot cross users. Public APIs scope by the key alone: the key is
33
+ * required to be long (and documented to be random), and identity proxies
34
+ * like the client IP are deliberately NOT part of the scope, because the
35
+ * retry idempotency exists for (a timeout followed by a network change)
36
+ * frequently arrives from a different IP.
37
+ */
38
+ private scopeOf;
39
+ /**
40
+ * Replay fast path, run BEFORE rate limits and guards: a completed record
41
+ * answers with its stored response so a legitimate retry neither burns
42
+ * rate-limit quota nor re-runs guards (the original already passed them,
43
+ * and no handler executes). Misses fall through to the normal pipeline;
44
+ * store errors follow the failOpen setting.
45
+ */
46
+ findReplay(apiName: string, ctx: LambderRenderContext): Promise<LambderResponse | null>;
47
+ /**
48
+ * Idempotency wrapper around validation-passed handler execution. Without
49
+ * a client idempotencyKey the handler just runs; with one, the scope
50
+ * (identity + api + key) is claimed atomically: duplicates of an
51
+ * in-flight original refuse with 409, replays of a completed one return
52
+ * the stored response verbatim, and a crashed original releases its claim
53
+ * so a retry actually retries.
54
+ */
55
+ withIdempotency(apiName: string, ctx: LambderRenderContext, config: boolean | {
56
+ ttlSeconds?: number;
57
+ }, exec: () => Promise<LambderResponse>): Promise<LambderResponse>;
58
+ }