lambder 10.0.1 → 10.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/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,37 @@ sit on its first published patch, and later patches list only what they changed.
|
|
|
9
9
|
Releases up to 3.2.6 carry git tags; the ones after it were published without
|
|
10
10
|
one, so versions are not cross-linked to tag comparisons here.
|
|
11
11
|
|
|
12
|
+
## [10.1.1] - 2026-09-28
|
|
13
|
+
|
|
14
|
+
A minor for an app made of parts. Each part can declare its own guards and
|
|
15
|
+
rate-limit policies beside the APIs that use them, and the instance composes
|
|
16
|
+
the maps at creation.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`guards` and `rateLimits.policies` take a list of maps** as well as one
|
|
21
|
+
map. The instance declares every name in the list, so an API names a guard
|
|
22
|
+
or a policy from any map as it would from the only one, and
|
|
23
|
+
`apiSignatures()` and `apiOptionEntries()` read the merged map. A name two
|
|
24
|
+
maps declare is a compile error on the list, whose message names it, and a
|
|
25
|
+
throw at creation, where a spread would have let one replace the other
|
|
26
|
+
without a word. Every map of a list is held to the same surplus-key and
|
|
27
|
+
refusal-code checks as a lone map. The mock runtime's `create()` still
|
|
28
|
+
takes one map of each.
|
|
29
|
+
|
|
30
|
+
## [10.0.2] - 2026-09-28
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **A guard an init's `guard()` builds inside a map held to `LambderApiGuard`**
|
|
35
|
+
(`{ ... } satisfies Record<string, LambderApiGuard<any, any, any>>`, the
|
|
36
|
+
shape an app's guards file takes) did not compile when it named no
|
|
37
|
+
`refusals`: the map's contextual type widened the guard's refusals to
|
|
38
|
+
`readonly string[]`, which the vocabulary check read as codes it does not
|
|
39
|
+
hold, so every such guard was asked for a `refusals` option. The refusals in
|
|
40
|
+
a built guard's type are no longer an inference site, so a guard that names
|
|
41
|
+
none declares none wherever it is built.
|
|
42
|
+
|
|
12
43
|
## [10.0.1] - 2026-09-28
|
|
13
44
|
|
|
14
45
|
A major that makes both sides of an API answer exact. A success is only ever
|
|
@@ -168,7 +168,7 @@ export type LambderGuardBuilder<TCtx, TSessionCtx, TVocabulary = never, TCodesRe
|
|
|
168
168
|
session: true;
|
|
169
169
|
handler: (ctx: TSessionCtx & LambderGuardRefuse<TVocabulary, TRefusals, TCodesRequired>, payload: z.output<TInput>, param: TParam) => TOutput | Promise<TOutput>;
|
|
170
170
|
} & LambderGuardPlacement & LambderGuardRefusals<TRefusals> & LambderGuardRefusalsKnownIn<TVocabulary, TRefusals> & LambderGuardAnswerCheck<TOutput>): LambderGuardOf<TOutput, {
|
|
171
|
-
refusals?: TRefusals
|
|
171
|
+
refusals?: NoInfer<TRefusals>;
|
|
172
172
|
apiInput: TInput;
|
|
173
173
|
guardInput?: undefined;
|
|
174
174
|
session: true;
|
|
@@ -178,7 +178,7 @@ export type LambderGuardBuilder<TCtx, TSessionCtx, TVocabulary = never, TCodesRe
|
|
|
178
178
|
apiInput: TInput;
|
|
179
179
|
handler: (ctx: TCtx & LambderGuardRefuse<TVocabulary, TRefusals, TCodesRequired>, payload: z.output<TInput>, param: TParam) => TOutput | Promise<TOutput>;
|
|
180
180
|
} & LambderGuardPlacement & LambderGuardRefusals<TRefusals> & LambderGuardRefusalsKnownIn<TVocabulary, TRefusals> & LambderGuardAnswerCheck<TOutput>): LambderGuardOf<TOutput, {
|
|
181
|
-
refusals?: TRefusals
|
|
181
|
+
refusals?: NoInfer<TRefusals>;
|
|
182
182
|
apiInput: TInput;
|
|
183
183
|
guardInput?: undefined;
|
|
184
184
|
session?: undefined;
|
|
@@ -189,7 +189,7 @@ export type LambderGuardBuilder<TCtx, TSessionCtx, TVocabulary = never, TCodesRe
|
|
|
189
189
|
session: true;
|
|
190
190
|
handler: (ctx: TSessionCtx & LambderGuardRefuse<TVocabulary, TRefusals, TCodesRequired>, payload: z.output<TInput>, param: TParam) => TOutput | Promise<TOutput>;
|
|
191
191
|
} & LambderGuardPlacement & LambderGuardRefusals<TRefusals> & LambderGuardRefusalsKnownIn<TVocabulary, TRefusals> & LambderGuardAnswerCheck<TOutput>): LambderGuardOf<TOutput, {
|
|
192
|
-
refusals?: TRefusals
|
|
192
|
+
refusals?: NoInfer<TRefusals>;
|
|
193
193
|
guardInput: TInput;
|
|
194
194
|
apiInput?: undefined;
|
|
195
195
|
session: true;
|
|
@@ -199,7 +199,7 @@ export type LambderGuardBuilder<TCtx, TSessionCtx, TVocabulary = never, TCodesRe
|
|
|
199
199
|
guardInput: TInput;
|
|
200
200
|
handler: (ctx: TCtx & LambderGuardRefuse<TVocabulary, TRefusals, TCodesRequired>, payload: z.output<TInput>, param: TParam) => TOutput | Promise<TOutput>;
|
|
201
201
|
} & LambderGuardPlacement & LambderGuardRefusals<TRefusals> & LambderGuardRefusalsKnownIn<TVocabulary, TRefusals> & LambderGuardAnswerCheck<TOutput>): LambderGuardOf<TOutput, {
|
|
202
|
-
refusals?: TRefusals
|
|
202
|
+
refusals?: NoInfer<TRefusals>;
|
|
203
203
|
guardInput: TInput;
|
|
204
204
|
apiInput?: undefined;
|
|
205
205
|
session?: undefined;
|
|
@@ -209,7 +209,7 @@ export type LambderGuardBuilder<TCtx, TSessionCtx, TVocabulary = never, TCodesRe
|
|
|
209
209
|
session: true;
|
|
210
210
|
handler: (ctx: TSessionCtx & LambderGuardRefuse<TVocabulary, TRefusals, TCodesRequired>, payload: undefined, param: TParam) => TOutput | Promise<TOutput>;
|
|
211
211
|
} & LambderGuardPlacement & LambderGuardRefusals<TRefusals> & LambderGuardRefusalsKnownIn<TVocabulary, TRefusals> & LambderGuardAnswerCheck<TOutput>): LambderGuardOf<TOutput, {
|
|
212
|
-
refusals?: TRefusals
|
|
212
|
+
refusals?: NoInfer<TRefusals>;
|
|
213
213
|
apiInput?: undefined;
|
|
214
214
|
guardInput?: undefined;
|
|
215
215
|
session: true;
|
|
@@ -218,7 +218,7 @@ export type LambderGuardBuilder<TCtx, TSessionCtx, TVocabulary = never, TCodesRe
|
|
|
218
218
|
<TParam = undefined, TOutput = void, const TRefusals extends readonly string[] = readonly []>(guard: {
|
|
219
219
|
handler: (ctx: TCtx & LambderGuardRefuse<TVocabulary, TRefusals, TCodesRequired>, payload: undefined, param: TParam) => TOutput | Promise<TOutput>;
|
|
220
220
|
} & LambderGuardPlacement & LambderGuardRefusals<TRefusals> & LambderGuardRefusalsKnownIn<TVocabulary, TRefusals> & LambderGuardAnswerCheck<TOutput>): LambderGuardOf<TOutput, {
|
|
221
|
-
refusals?: TRefusals
|
|
221
|
+
refusals?: NoInfer<TRefusals>;
|
|
222
222
|
apiInput?: undefined;
|
|
223
223
|
guardInput?: undefined;
|
|
224
224
|
session?: undefined;
|
package/dist/core/Lambder.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { LambderApiIdempotencyConfig } from "../api/LambderApiIdempotency.j
|
|
|
24
24
|
import type { LambderContractEntry, LambderJsonOf, LambderMergeContract } from "../shared/wire/LambderApiContract.js";
|
|
25
25
|
import { type LambderHttpEvent, type LambderRenderContext, type LambderSessionRenderContext } from "./LambderContext.js";
|
|
26
26
|
import type { LambderReadonlyDeep, MaybePromise } from "../shared/util/LambderTypeUtilities.js";
|
|
27
|
-
import { type LambderRouteHandler, type LambderInputValidationHandler, type LambderFallbackHandler, type LambderGlobalErrorHandler, type LambderAfterRenderHook, type LambderBeforeRenderHook, type LambderFallbackHook, type LambderActionTools, type LambderCreateOptions, type LambderGivenOption, type LambderHandler, type LambderNestedOptionChecks, type LambderObjectOutputCheck, type LambderRequirableGuardsField, type LambderSessionEnabledInstance, type LambderSessionRouteHandler } from "./LambderCreateOptions.js";
|
|
27
|
+
import { type LambderMergedNamedMaps, type LambderNamedMapsOption, type LambderRouteHandler, type LambderInputValidationHandler, type LambderFallbackHandler, type LambderGlobalErrorHandler, type LambderAfterRenderHook, type LambderBeforeRenderHook, type LambderFallbackHook, type LambderActionTools, type LambderCreateOptions, type LambderGivenOption, type LambderHandler, type LambderNestedOptionChecks, type LambderObjectOutputCheck, type LambderRequirableGuardsField, type LambderSessionEnabledInstance, type LambderSessionRouteHandler } from "./LambderCreateOptions.js";
|
|
28
28
|
/** Everything `lambder/testing` may put under a built instance: the pipeline's stores, and the source its files are read from. */
|
|
29
29
|
export type LambderInstanceBackends = LambderPipelineBackends & {
|
|
30
30
|
fileSource?: LambderFileSource;
|
|
@@ -96,7 +96,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
96
96
|
private readonly pipeline;
|
|
97
97
|
/** Every registered API by name: the duplicate-name check, and what apiSignatures() digests. */
|
|
98
98
|
private readonly apiDefinitions;
|
|
99
|
-
/** The guards
|
|
99
|
+
/** The guards given at creation, merged into one map, kept for apiSignatures(): a guard's schema is part of the signature of every endpoint declaring it. */
|
|
100
100
|
private readonly guards;
|
|
101
101
|
/** The rate-limit policies given at creation, kept for apiOptionEntries(), which records each one less its key handler. */
|
|
102
102
|
private readonly rateLimitPolicies;
|
|
@@ -129,7 +129,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
129
129
|
private readonly trustedClientIpHeaders;
|
|
130
130
|
private readonly trustedHostHeaders;
|
|
131
131
|
private requirePublicApiGuards;
|
|
132
|
-
constructor(
|
|
132
|
+
constructor(given?: LambderCreateOptions<TSessionData>);
|
|
133
133
|
setRouteFallbackHandler(routeFallbackHandler: LambderFallbackHandler): this;
|
|
134
134
|
setApiFallbackHandler(apiFallbackHandler: LambderFallbackHandler): this;
|
|
135
135
|
setApiInputValidationErrorHandler(apiInputValidationErrorHandler: LambderInputValidationHandler): this;
|
|
@@ -518,9 +518,9 @@ export declare const initLambder: <TSessionData = any>() => {
|
|
|
518
518
|
* endpoint's codes alone.
|
|
519
519
|
*/
|
|
520
520
|
refuse: LambderDeclaredRefuse<LambderHandlerRefusalsOf<TRefusals, keyof TRefusals & string>, TRequireCodes>;
|
|
521
|
-
create<const TOptions extends LambderInitCreateOptions<TSessionData>>(options: TOptions & Record<Exclude<keyof TOptions, "session" | "guards" | "idempotency" | "apiVersion" | "cors" | "apiPath" | "apiSignatures" | "trustedClientIpHeaders" | "trustedHostHeaders" | "files" | "etag" | "
|
|
522
|
-
policies: infer TPolicies
|
|
523
|
-
} ? TPolicies : {}, TOptions["guards"] extends Record<string, LambderApiGuard<any, any, any
|
|
521
|
+
create<const TOptions extends LambderInitCreateOptions<TSessionData>>(options: TOptions & Record<Exclude<keyof TOptions, "session" | "guards" | "idempotency" | "apiVersion" | "cors" | "apiPath" | "apiSignatures" | "trustedClientIpHeaders" | "trustedHostHeaders" | "files" | "etag" | "rateLimits" | "minApiVersion" | "compression" | "maxResponseBytes" | "maxRequestPayloadBytes" | "requireSessionApiGuards" | "requirePublicApiGuards" | "crashes">, never> & LambderNestedOptionChecks<TSessionData, TOptions, TRefusals>): Lambder<TSessionData, {}, TOptions["rateLimits"] extends {
|
|
522
|
+
policies: infer TPolicies;
|
|
523
|
+
} ? LambderMergedNamedMaps<TPolicies> extends infer TMerged extends Record<string, LambderApiRateLimitPolicyConfig> ? TMerged : {} : {}, TOptions["guards"] extends LambderNamedMapsOption<Record<string, LambderApiGuard<any, any, any>>> ? LambderGuardMetaMap<LambderMergedNamedMaps<TOptions["guards"]>> : {}, TOptions["idempotency"] extends LambderApiIdempotencyConfig ? true : false, [LambderGivenOption<TOptions, "requireSessionApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "requirePublicApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "session">] extends [undefined] ? false : true, TRefusals, TRequireCodes>;
|
|
524
524
|
guard: import("../api/LambderApiGuards.js").LambderGuardBuilder<LambderRenderContext<any, Record<string, string>, {}, TSessionData>, LambderSessionRenderContext<any, TSessionData>, TRefusals, TRequireCodes>;
|
|
525
525
|
rateLimitKey: import("../api/LambderApiRateLimits.js").LambderRateLimitKeyBuilder<LambderRenderContext<any, Record<string, string>, {}, TSessionData>>;
|
|
526
526
|
};
|
|
@@ -544,9 +544,9 @@ export declare const initLambder: <TSessionData = any>() => {
|
|
|
544
544
|
code?: undefined;
|
|
545
545
|
data?: undefined;
|
|
546
546
|
}) | undefined) => never;
|
|
547
|
-
create<const TOptions extends LambderInitCreateOptions<TSessionData>>(options: TOptions & Record<Exclude<keyof TOptions, "session" | "guards" | "idempotency" | "apiVersion" | "cors" | "apiPath" | "apiSignatures" | "trustedClientIpHeaders" | "trustedHostHeaders" | "files" | "etag" | "
|
|
548
|
-
policies: infer TPolicies
|
|
549
|
-
} ? TPolicies : {}, TOptions["guards"] extends Record<string, LambderApiGuard<any, any, any
|
|
547
|
+
create<const TOptions extends LambderInitCreateOptions<TSessionData>>(options: TOptions & Record<Exclude<keyof TOptions, "session" | "guards" | "idempotency" | "apiVersion" | "cors" | "apiPath" | "apiSignatures" | "trustedClientIpHeaders" | "trustedHostHeaders" | "files" | "etag" | "rateLimits" | "minApiVersion" | "compression" | "maxResponseBytes" | "maxRequestPayloadBytes" | "requireSessionApiGuards" | "requirePublicApiGuards" | "crashes">, never> & LambderNestedOptionChecks<TSessionData, TOptions, {}>): Lambder<TSessionData, {}, TOptions["rateLimits"] extends {
|
|
548
|
+
policies: infer TPolicies;
|
|
549
|
+
} ? LambderMergedNamedMaps<TPolicies> extends infer TMerged extends Record<string, LambderApiRateLimitPolicyConfig> ? TMerged : {} : {}, TOptions["guards"] extends LambderNamedMapsOption<Record<string, LambderApiGuard<any, any, any>>> ? LambderGuardMetaMap<LambderMergedNamedMaps<TOptions["guards"]>> : {}, TOptions["idempotency"] extends LambderApiIdempotencyConfig ? true : false, [LambderGivenOption<TOptions, "requireSessionApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "requirePublicApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "session">] extends [undefined] ? false : true, {}, false>;
|
|
550
550
|
guard: import("../api/LambderApiGuards.js").LambderGuardBuilder<LambderRenderContext<any, Record<string, string>, {}, TSessionData>, LambderSessionRenderContext<any, TSessionData>, {}, false>;
|
|
551
551
|
rateLimitKey: import("../api/LambderApiRateLimits.js").LambderRateLimitKeyBuilder<LambderRenderContext<any, Record<string, string>, {}, TSessionData>>;
|
|
552
552
|
};
|
package/dist/core/Lambder.js
CHANGED
|
@@ -27,7 +27,7 @@ import { COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PA
|
|
|
27
27
|
import { coerceToError } from "../shared/wire/LambderCrashDetail.js";
|
|
28
28
|
import { LambderCrashHandling } from "./LambderCrashHandling.js";
|
|
29
29
|
import { policyBuildersFor } from "./LambderPolicyBuilders.js";
|
|
30
|
-
import { assertCreateOptions, } from "./LambderCreateOptions.js";
|
|
30
|
+
import { assertCreateOptions, mergeNamedMaps, } from "./LambderCreateOptions.js";
|
|
31
31
|
/** The refusals of a name no API is registered under: no code, and none required. */
|
|
32
32
|
const NO_DECLARED_REFUSALS = { codes: new Map(), codeRequired: false };
|
|
33
33
|
/**
|
|
@@ -88,7 +88,7 @@ export default class Lambder {
|
|
|
88
88
|
pipeline;
|
|
89
89
|
/** Every registered API by name: the duplicate-name check, and what apiSignatures() digests. */
|
|
90
90
|
apiDefinitions = new Map();
|
|
91
|
-
/** The guards
|
|
91
|
+
/** The guards given at creation, merged into one map, kept for apiSignatures(): a guard's schema is part of the signature of every endpoint declaring it. */
|
|
92
92
|
guards;
|
|
93
93
|
/** The rate-limit policies given at creation, kept for apiOptionEntries(), which records each one less its key handler. */
|
|
94
94
|
rateLimitPolicies;
|
|
@@ -121,7 +121,15 @@ export default class Lambder {
|
|
|
121
121
|
trustedClientIpHeaders;
|
|
122
122
|
trustedHostHeaders;
|
|
123
123
|
requirePublicApiGuards;
|
|
124
|
-
constructor(
|
|
124
|
+
constructor(given = {}) {
|
|
125
|
+
// The guards and the rate-limit policies as one map each, whether
|
|
126
|
+
// they were given as one or as a list; a name two maps declare
|
|
127
|
+
// throws here.
|
|
128
|
+
const options = {
|
|
129
|
+
...given,
|
|
130
|
+
guards: given.guards && mergeNamedMaps(given.guards, "guard"),
|
|
131
|
+
rateLimits: given.rateLimits && { ...given.rateLimits, policies: mergeNamedMaps(given.rateLimits.policies, "rate-limit policy") },
|
|
132
|
+
};
|
|
125
133
|
assertCreateOptions(options);
|
|
126
134
|
this.files = options.files ? new LambderFiles(options.files) : null;
|
|
127
135
|
this.apiPath = options.apiPath ?? DEFAULT_API_PATH;
|
|
@@ -112,6 +112,26 @@ export type LambderHandler = {
|
|
|
112
112
|
(event: LambderHttpEvent, context: Context): Promise<LambderHttpResponse>;
|
|
113
113
|
(event: unknown, context: Context): Promise<unknown>;
|
|
114
114
|
};
|
|
115
|
+
/**
|
|
116
|
+
* One map of named declarations, or a list of them. An app made of parts,
|
|
117
|
+
* each declaring its own guards or rate-limit policies beside its APIs, hands
|
|
118
|
+
* create() the list, and the instance declares every name in it. A name two
|
|
119
|
+
* maps declare is refused rather than one quietly replacing the other.
|
|
120
|
+
*/
|
|
121
|
+
export type LambderNamedMapsOption<TMap> = TMap | readonly TMap[];
|
|
122
|
+
/** The one map a named-maps option declares: a list's members intersected, a lone map as it is. */
|
|
123
|
+
export type LambderMergedNamedMaps<T> = T extends readonly [] ? {} : T extends readonly [infer TFirst, ...infer TRest] ? TFirst & LambderMergedNamedMaps<TRest> : T extends readonly (infer TMember)[] ? TMember : T;
|
|
124
|
+
/** The names a list declares in more than one of its maps: never for a lone map, or for a list whose members are not known one by one. */
|
|
125
|
+
type LambderRepeatedNames<T, TSeen extends PropertyKey = never> = T extends readonly [infer TFirst, ...infer TRest] ? (keyof TFirst & TSeen) | LambderRepeatedNames<TRest, TSeen | keyof TFirst> : never;
|
|
126
|
+
/** Intersected onto a list: a name declared in two of its maps is a compile error, and the property name is the message. */
|
|
127
|
+
type LambderNoRepeatedNames<T> = [LambderRepeatedNames<T>] extends [never] ? unknown : {
|
|
128
|
+
readonly "lambder: a name is declared in more than one of these maps. Declare each guard and each rate-limit policy once.": LambderRepeatedNames<T>;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* A named-maps option as one map: a lone map as it is, a list merged in
|
|
132
|
+
* order. A name declared in two maps of the list is refused.
|
|
133
|
+
*/
|
|
134
|
+
export declare const mergeNamedMaps: <TMap extends Record<string, unknown>>(option: LambderNamedMapsOption<TMap>, what: "guard" | "rate-limit policy") => TMap;
|
|
115
135
|
/** Session configuration (the `session` option of create/new): where sessions rest, and how their cookies are scoped. */
|
|
116
136
|
export type LambderSessionOptions<TSessionData = any> = {
|
|
117
137
|
/**
|
|
@@ -147,6 +167,20 @@ export type LambderSessionOptions<TSessionData = any> = {
|
|
|
147
167
|
/** Hashing and randomness for the session tokens. Default: WebCrypto. */
|
|
148
168
|
crypto?: LambderSessionCrypto;
|
|
149
169
|
};
|
|
170
|
+
/** One map of the server's guards, each pinned to the render contexts of the app's session type. */
|
|
171
|
+
export type LambderGuardsMap<TSessionData = any> = Record<string, LambderApiGuard<any, any, any, LambderRenderContext<any, Record<string, string>, {}, TSessionData>, LambderSessionRenderContext<any, TSessionData>>>;
|
|
172
|
+
/** One map of the server's rate-limit policies. */
|
|
173
|
+
export type LambderRateLimitPoliciesMap = Record<string, LambderApiRateLimitPolicyConfig<LambderRenderContext>>;
|
|
174
|
+
/** The `rateLimits` option of create(): the engine's config, with `policies` as one map or a list of maps. */
|
|
175
|
+
export type LambderCreateRateLimitsOption = Omit<LambderApiRateLimitsConfig<LambderRateLimitPoliciesMap>, "policies"> & {
|
|
176
|
+
/** Named policies referenced (typed) from addApi/addSessionApi: one map, or a list of maps merged in order. */
|
|
177
|
+
policies: LambderNamedMapsOption<LambderRateLimitPoliciesMap>;
|
|
178
|
+
};
|
|
179
|
+
/** The options as the instance holds them: the guards and the rate-limit policies merged into one map each. */
|
|
180
|
+
export type LambderMergedCreateOptions<TSessionData = any> = Omit<LambderCreateOptions<TSessionData>, "guards" | "rateLimits"> & {
|
|
181
|
+
guards?: LambderGuardsMap<TSessionData>;
|
|
182
|
+
rateLimits?: LambderApiRateLimitsConfig<LambderRateLimitPoliciesMap>;
|
|
183
|
+
};
|
|
150
184
|
/**
|
|
151
185
|
* Everything an instance is configured with, in ONE declaration: base
|
|
152
186
|
* serving options plus the type-affecting policy layer (rate limits, guards,
|
|
@@ -244,16 +278,22 @@ export type LambderCreateOptions<TSessionData = any> = {
|
|
|
244
278
|
cors?: boolean | LambderCorsConfig;
|
|
245
279
|
/** Sessions over a store of your choosing; required for addSessionApi/addSessionRoute. */
|
|
246
280
|
session?: LambderSessionOptions<TSessionData>;
|
|
247
|
-
/**
|
|
248
|
-
|
|
281
|
+
/**
|
|
282
|
+
* Declarative per-API rate limiting: your limiter plus named policies APIs
|
|
283
|
+
* reference (typed) via the `rateLimit` option. `policies` is one map, or
|
|
284
|
+
* a list of maps (one per part of the app) that the instance merges.
|
|
285
|
+
*/
|
|
286
|
+
rateLimits?: LambderCreateRateLimitsOption;
|
|
249
287
|
/**
|
|
250
288
|
* Named guards APIs reference (typed) via the `guards` option; build each
|
|
251
289
|
* with `initLambder<SessionData>().guard()`, whose handlers see the app's
|
|
252
290
|
* session type, or lambderGuard(). Pinned to the render contexts, so a
|
|
253
291
|
* guard built for another adapter, or for another session type, is
|
|
254
292
|
* rejected here rather than reading fields that are not on its context.
|
|
293
|
+
* One map, or a list of maps (one per part of the app) that the instance
|
|
294
|
+
* merges.
|
|
255
295
|
*/
|
|
256
|
-
guards?:
|
|
296
|
+
guards?: LambderNamedMapsOption<LambderGuardsMap<TSessionData>>;
|
|
257
297
|
/**
|
|
258
298
|
* The app's refusal vocabulary: every code an API or a guard may refuse
|
|
259
299
|
* with, each declared once with the schema of its data (`{ data: schema }`
|
|
@@ -386,6 +426,22 @@ type LambderGuardRefusalsKnown<TGuard, TRefusals> = TGuard extends {
|
|
|
386
426
|
} ? NonNullable<TNamed> extends readonly (infer TCode extends string)[] ? string extends TCode ? unknown : [Exclude<TCode, keyof NonNullable<TRefusals> & string>] extends [never] ? unknown : {
|
|
387
427
|
refusals: readonly (keyof NonNullable<TRefusals> & string)[];
|
|
388
428
|
} : unknown : unknown;
|
|
429
|
+
/** Every policy in a map, or in each map of a list, held to the policy shape, its message included; a list also declares no name twice. */
|
|
430
|
+
type LambderRateLimitPolicyChecks<TPolicies> = TPolicies extends readonly unknown[] ? {
|
|
431
|
+
[TIndex in keyof TPolicies]: LambderRateLimitPolicyChecks<TPolicies[TIndex]>;
|
|
432
|
+
} & LambderNoRepeatedNames<TPolicies> : {
|
|
433
|
+
[TPolicy in keyof TPolicies]: LambderNoExtraKeys<TPolicies[TPolicy], LambderApiRateLimitPolicyConfig<LambderRenderContext>> & (TPolicies[TPolicy] extends {
|
|
434
|
+
refusal: infer TMessage;
|
|
435
|
+
} ? {
|
|
436
|
+
refusal?: LambderNoExtraKeys<TMessage, LambderRateLimitMessage>;
|
|
437
|
+
} : unknown);
|
|
438
|
+
};
|
|
439
|
+
/** Every guard in a map, or in each map of a list, held to the guard shape and its refusals to the vocabulary; a list also declares no name twice. */
|
|
440
|
+
type LambderGuardChecks<TSessionData, TGuards, TRefusals> = TGuards extends readonly unknown[] ? {
|
|
441
|
+
[TIndex in keyof TGuards]: LambderGuardChecks<TSessionData, TGuards[TIndex], TRefusals>;
|
|
442
|
+
} & LambderNoRepeatedNames<TGuards> : {
|
|
443
|
+
[TGuard in keyof TGuards]: LambderNoExtraKeys<TGuards[TGuard], LambderGuardsMap<TSessionData>[string]> & LambderGuardRefusalsKnown<TGuards[TGuard], TRefusals>;
|
|
444
|
+
};
|
|
389
445
|
/**
|
|
390
446
|
* The surplus-key rule one level down, over the option objects a typo is
|
|
391
447
|
* worst on. Excess-property checking is off for the WHOLE literal under
|
|
@@ -405,17 +461,9 @@ export type LambderNestedOptionChecks<TSessionData, TOptions extends LambderCrea
|
|
|
405
461
|
idempotency?: LambderNoExtraKeys<NonNullable<TOptions["idempotency"]>, LambderOptionShape<TSessionData, "idempotency">>;
|
|
406
462
|
crashes?: LambderNoExtraKeys<NonNullable<TOptions["crashes"]>, LambderOptionShape<TSessionData, "crashes">>;
|
|
407
463
|
rateLimits?: LambderNoExtraKeys<NonNullable<TOptions["rateLimits"]>, LambderOptionShape<TSessionData, "rateLimits">> & {
|
|
408
|
-
policies?:
|
|
409
|
-
[TPolicy in keyof NonNullable<TOptions["rateLimits"]>["policies"]]: LambderNoExtraKeys<NonNullable<TOptions["rateLimits"]>["policies"][TPolicy], LambderApiRateLimitPolicyConfig<LambderRenderContext>> & (NonNullable<TOptions["rateLimits"]>["policies"][TPolicy] extends {
|
|
410
|
-
refusal: infer TMessage;
|
|
411
|
-
} ? {
|
|
412
|
-
refusal?: LambderNoExtraKeys<TMessage, LambderRateLimitMessage>;
|
|
413
|
-
} : unknown);
|
|
414
|
-
};
|
|
415
|
-
};
|
|
416
|
-
guards?: {
|
|
417
|
-
[TGuard in keyof NonNullable<TOptions["guards"]>]: LambderNoExtraKeys<NonNullable<TOptions["guards"]>[TGuard], LambderOptionShape<TSessionData, "guards">[string]> & LambderGuardRefusalsKnown<NonNullable<TOptions["guards"]>[TGuard], TRefusals>;
|
|
464
|
+
policies?: LambderRateLimitPolicyChecks<NonNullable<TOptions["rateLimits"]>["policies"]>;
|
|
418
465
|
};
|
|
466
|
+
guards?: LambderGuardChecks<TSessionData, NonNullable<TOptions["guards"]>, TRefusals>;
|
|
419
467
|
files?: TOptions["files"] extends {
|
|
420
468
|
source: unknown;
|
|
421
469
|
} ? LambderNoExtraKeys<TOptions["files"], Extract<LambderFilesOption, {
|
|
@@ -430,4 +478,4 @@ export type LambderNestedOptionChecks<TSessionData, TOptions extends LambderCrea
|
|
|
430
478
|
* every API call (an apiPath with no leading slash) or a 500 on every
|
|
431
479
|
* response (maxResponseBytes: 0) that an app discovers in production.
|
|
432
480
|
*/
|
|
433
|
-
export declare const assertCreateOptions: (options:
|
|
481
|
+
export declare const assertCreateOptions: (options: LambderMergedCreateOptions<any>) => void;
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { assertRefusalCodesDeclared, readRefusalVocabulary } from "../api/LambderApiRefusals.js";
|
|
2
2
|
import { assertPositiveInteger } from "../shared/util/LambderOptionChecks.js";
|
|
3
|
+
/**
|
|
4
|
+
* A named-maps option as one map: a lone map as it is, a list merged in
|
|
5
|
+
* order. A name declared in two maps of the list is refused.
|
|
6
|
+
*/
|
|
7
|
+
export const mergeNamedMaps = (option, what) => {
|
|
8
|
+
if (!Array.isArray(option))
|
|
9
|
+
return option;
|
|
10
|
+
const merged = {};
|
|
11
|
+
for (const map of option) {
|
|
12
|
+
for (const [name, declaration] of Object.entries(map)) {
|
|
13
|
+
if (Object.prototype.hasOwnProperty.call(merged, name)) {
|
|
14
|
+
throw new Error(`Lambder: the ${what} "${name}" is declared in two of the maps given to create(). Declare each name once.`);
|
|
15
|
+
}
|
|
16
|
+
merged[name] = declaration;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return merged;
|
|
20
|
+
};
|
|
3
21
|
/**
|
|
4
22
|
* Everything create() refuses before an instance exists, in one place: a
|
|
5
23
|
* value that cannot work is a startup error naming the option, not a 404 on
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lambder",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.1.1",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"description": "Opinionated serverless web framework for TypeScript on AWS Lambda: type-safe APIs from Zod schemas, DynamoDB sessions, and declarative rate limits, authorization guards and idempotency.",
|
|
6
6
|
"keywords": [
|