lambder 8.1.2 → 9.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/CHANGELOG.md +208 -0
- package/README.md +23 -31
- package/dist/api/LambderApiCallContext.d.ts +31 -1
- package/dist/api/LambderApiCallContext.js +8 -0
- package/dist/api/LambderApiDefinition.d.ts +2 -2
- package/dist/api/LambderApiEnvelope.d.ts +1 -1
- package/dist/api/LambderApiEnvelope.js +3 -4
- package/dist/api/LambderApiGuards.d.ts +2 -17
- package/dist/api/LambderApiIdempotency.js +5 -7
- package/dist/api/LambderApiRateLimits.d.ts +2 -29
- package/dist/build/generatedTables.d.ts +72 -0
- package/dist/build/generatedTables.js +99 -0
- package/dist/build/writeApiGuardParams.d.ts +60 -0
- package/dist/build/writeApiGuardParams.js +85 -0
- package/dist/build/writeApiOptions.d.ts +68 -0
- package/dist/build/writeApiOptions.js +102 -0
- package/dist/build.d.ts +10 -4
- package/dist/build.js +7 -4
- package/dist/client/LambderCaller.d.ts +0 -4
- package/dist/client/LambderCaller.js +1 -9
- package/dist/client/LambderUploadRunner.d.ts +7 -7
- package/dist/client/LambderUploadRunner.js +12 -21
- package/dist/client.d.ts +7 -0
- package/dist/client.js +11 -0
- package/dist/core/Lambder.d.ts +71 -12
- package/dist/core/Lambder.js +116 -38
- package/dist/core/LambderContext.d.ts +9 -6
- package/dist/core/LambderContext.js +2 -1
- package/dist/core/LambderResolver.d.ts +6 -12
- package/dist/core/LambderResolver.js +2 -14
- package/dist/core/LambderResponseBuilder.d.ts +15 -70
- package/dist/core/LambderResponseBuilder.js +15 -99
- package/dist/index.d.ts +16 -3
- package/dist/index.js +14 -1
- package/dist/invoke/LambderInvokeCaller.js +3 -4
- package/dist/mock/LambderMockApp.d.ts +34 -17
- package/dist/mock/LambderMockApp.js +69 -24
- package/dist/mock/LambderMockCreateOptions.d.ts +70 -7
- package/dist/mock/LambderMockTypes.d.ts +31 -21
- package/dist/mock/lambderMockPoliciesFrom.d.ts +51 -0
- package/dist/mock/lambderMockPoliciesFrom.js +46 -0
- package/dist/mock.d.ts +3 -0
- package/dist/mock.js +3 -0
- package/dist/secrets/LambderOneShotSecrets.d.ts +166 -0
- package/dist/secrets/LambderOneShotSecrets.js +217 -0
- package/dist/session/LambderSessionCrypto.js +6 -16
- package/dist/shared/contracts/LambderIdempotencyStore.d.ts +3 -2
- package/dist/shared/contracts/LambderOneShotSecretStore.d.ts +122 -0
- package/dist/shared/contracts/LambderOneShotSecretStore.js +38 -0
- package/dist/shared/util/LambderBackoffTimer.d.ts +82 -0
- package/dist/shared/util/LambderBackoffTimer.js +86 -0
- package/dist/shared/util/LambderBase64.d.ts +14 -0
- package/dist/shared/util/LambderBase64.js +17 -0
- package/dist/shared/util/LambderSignedClaims.d.ts +78 -0
- package/dist/shared/util/LambderSignedClaims.js +109 -0
- package/dist/shared/util/LambderTextDigest.d.ts +19 -5
- package/dist/shared/util/LambderTextDigest.js +30 -5
- package/dist/shared/util/LambderTypeUtilities.d.ts +18 -0
- package/dist/shared/util/assertPlainData.d.ts +9 -0
- package/dist/shared/util/assertPlainData.js +41 -0
- package/dist/shared/wire/LambderAnswerHeaders.d.ts +3 -2
- package/dist/shared/wire/LambderAnswerHeaders.js +3 -2
- package/dist/shared/wire/LambderApiContract.d.ts +9 -14
- package/dist/shared/wire/LambderApiOptionEntries.d.ts +148 -0
- package/dist/shared/wire/LambderApiOptionEntries.js +35 -0
- package/dist/shared/wire/LambderApiRefusal.d.ts +3 -4
- package/dist/shared/wire/LambderApiRefusal.js +3 -4
- package/dist/stores/LambderDdbOneShotSecretStore.d.ts +64 -0
- package/dist/stores/LambderDdbOneShotSecretStore.js +266 -0
- package/dist/stores/LambderMemoryIdempotencyStore.d.ts +3 -2
- package/dist/stores/LambderMemoryIdempotencyStore.js +3 -2
- package/dist/stores/LambderMemoryOneShotSecretStore.d.ts +36 -0
- package/dist/stores/LambderMemoryOneShotSecretStore.js +93 -0
- package/dist/testing/LambderConformanceRunner.d.ts +46 -0
- package/dist/testing/LambderConformanceRunner.js +21 -0
- package/dist/testing/lambderIdempotencyStoreConformance.d.ts +33 -0
- package/dist/testing/lambderIdempotencyStoreConformance.js +237 -0
- package/dist/testing/lambderOneShotSecretStoreConformance.d.ts +43 -0
- package/dist/testing/lambderOneShotSecretStoreConformance.js +224 -0
- package/dist/testing/lambderRateLimiterConformance.d.ts +20 -0
- package/dist/testing/lambderRateLimiterConformance.js +72 -0
- package/dist/testing/lambderSessionStoreConformance.d.ts +27 -0
- package/dist/testing/lambderSessionStoreConformance.js +165 -0
- package/dist/testing.d.ts +14 -0
- package/dist/testing.js +12 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export type { LambderIdempotencyKeyScope } from "./shared/wire/LambderIdempotenc
|
|
|
26
26
|
export { LambderApiRefusal, isLambderApiRefusal, refuse, refusalMessageOf, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
27
27
|
export type { LambderApiRefusalOptions, LambderRefusalMessage, LambderAppRefusalMessage, LambderRefusalCode, LambderRefuseOptions } from "./shared/wire/LambderApiRefusal.js";
|
|
28
28
|
export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiResponseConfig, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractKeysWithGuard, LambderJsonOf, LambderJsonOutputOf, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractRateLimitNames, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
|
|
29
|
+
export { apiGuardParam } from "./shared/wire/LambderApiOptionEntries.js";
|
|
30
|
+
export type { LambderApiOptionEntries, LambderApiOptionEntry, LambderRateLimitPolicyEntry, LambderGuardDeclarationEntry, LambderApisWithGuard, LambderApisGuardedBy, LambderApisWithMode, LambderGuardParamOf, } from "./shared/wire/LambderApiOptionEntries.js";
|
|
29
31
|
export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
|
|
30
32
|
export type { LambderCrashDetail, LambderCrashCause } from "./shared/wire/LambderCrashDetail.js";
|
|
31
33
|
export { compressPayloadGzip, isRequestCompressionAvailable, COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, DEFAULT_REQUEST_COMPRESSION_SETTINGS, } from "./shared/wire/LambderRequestPayload.js";
|
|
@@ -36,6 +38,11 @@ export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtm
|
|
|
36
38
|
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
37
39
|
export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nDictionaryLoader, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./shared/LambderI18n.js";
|
|
38
40
|
export type { LambderHttpStatusCode } from "./shared/wire/LambderHttpStatus.js";
|
|
41
|
+
export { LambderBackoffTimer } from "./shared/util/LambderBackoffTimer.js";
|
|
42
|
+
export type { LambderBackoffTimerOptions } from "./shared/util/LambderBackoffTimer.js";
|
|
43
|
+
export { LambderSignedClaims, keyedDigest, randomSecret } from "./shared/util/LambderSignedClaims.js";
|
|
44
|
+
export type { LambderSignedClaimsOptions } from "./shared/util/LambderSignedClaims.js";
|
|
45
|
+
export { constantTimeEquals } from "./shared/util/LambderTextDigest.js";
|
|
39
46
|
export { LambderUploadRunner, LambderUploadError } from "./client/LambderUploadRunner.js";
|
|
40
47
|
export type { LambderUploadRunnerOptions, LambderUploadProgress, LambderUploadPhase, LambderUploadFailureReason } from "./client/LambderUploadRunner.js";
|
|
41
48
|
export { checkUploadRule } from "./shared/contracts/LambderUploadBucket.js";
|
package/dist/client.js
CHANGED
|
@@ -23,6 +23,9 @@ export { createIdempotencyKey, createIdempotencyKeyScope } from "./shared/wire/L
|
|
|
23
23
|
// Typed API refusals (isomorphic: shared code may throw them from anywhere;
|
|
24
24
|
// in the browser they are plain Errors).
|
|
25
25
|
export { LambderApiRefusal, isLambderApiRefusal, refuse, refusalMessageOf, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
26
|
+
// The server's declared options as plain data (the generated options module's
|
|
27
|
+
// entry types) and the readers a client derives its own facts from.
|
|
28
|
+
export { apiGuardParam } from "./shared/wire/LambderApiOptionEntries.js";
|
|
26
29
|
// A crash described for a caller allowed to see it (the envelope's `crash` field; pure, no Node built-ins).
|
|
27
30
|
export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
|
|
28
31
|
// Request payload compression (browser-safe: gzip via CompressionStream, no Node built-ins).
|
|
@@ -33,6 +36,14 @@ export { resolveCompressionOption } from "./shared/wire/LambderCompressionOption
|
|
|
33
36
|
export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml } from "./shared/LambderHtml.js";
|
|
34
37
|
// Typed translations (standalone, isomorphic)
|
|
35
38
|
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
39
|
+
// Waiting longer after each failure, once: for a reconnecting client or a
|
|
40
|
+
// screen that has to come back by itself.
|
|
41
|
+
export { LambderBackoffTimer } from "./shared/util/LambderBackoffTimer.js";
|
|
42
|
+
// Signed claims tokens, for the isomorphic code that verifies them where a
|
|
43
|
+
// server's secret is at hand (an edge Worker, a shared backend package);
|
|
44
|
+
// never in a page, which holds no secret to verify with.
|
|
45
|
+
export { LambderSignedClaims, keyedDigest, randomSecret } from "./shared/util/LambderSignedClaims.js";
|
|
46
|
+
export { constantTimeEquals } from "./shared/util/LambderTextDigest.js";
|
|
36
47
|
// Direct uploads: the runner that takes a file from the browser straight to
|
|
37
48
|
// storage, and the vocabulary it shares with the server's bucket.
|
|
38
49
|
export { LambderUploadRunner, LambderUploadError } from "./client/LambderUploadRunner.js";
|
package/dist/core/Lambder.d.ts
CHANGED
|
@@ -14,13 +14,14 @@ import type { LambderFileSource } from "../shared/contracts/LambderFileSource.js
|
|
|
14
14
|
import { LAMBDER_BACKEND_SWAP, LAMBDER_CRASH_WATCH } from "../shared/util/LambderTestingDoors.js";
|
|
15
15
|
import { type LambderApiSignatureEntry } from "../api/LambderApiSignature.js";
|
|
16
16
|
import { type LambderApiSignatureMap } from "../shared/wire/LambderApiSignature.js";
|
|
17
|
+
import type { LambderApiOptionEntries } from "../shared/wire/LambderApiOptionEntries.js";
|
|
17
18
|
import type { LambderApiIdempotencyOption } from "../shared/wire/LambderApiOptionValues.js";
|
|
18
19
|
import type { LambderApiGuard, LambderGuardMetaMap, LambderGuardsOption, LambderGuardDataOf, LambderGuardInputsOf } from "../api/LambderApiGuards.js";
|
|
19
20
|
import type { LambderApiRateLimitPolicyConfig, LambderRateLimitOption } from "../api/LambderApiRateLimits.js";
|
|
20
21
|
import type { LambderApiIdempotencyConfig } from "../api/LambderApiIdempotency.js";
|
|
21
22
|
import type { LambderContractEntry, LambderJsonOutputOf, LambderMergeContract } from "../shared/wire/LambderApiContract.js";
|
|
22
23
|
import { type LambderHttpEvent, type LambderRenderContext, type LambderSessionRenderContext } from "./LambderContext.js";
|
|
23
|
-
import type { MaybePromise } from "../shared/util/LambderTypeUtilities.js";
|
|
24
|
+
import type { LambderReadonlyDeep, MaybePromise } from "../shared/util/LambderTypeUtilities.js";
|
|
24
25
|
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 LambderNoExtraKeys, type LambderRequirableGuardsField, type LambderSessionEnabledInstance, type LambderSessionRouteHandler } from "./LambderCreateOptions.js";
|
|
25
26
|
/** Everything `lambder/testing` may put under a built instance: the pipeline's stores, and the source its files are read from. */
|
|
26
27
|
export type LambderInstanceBackends = LambderPipelineBackends & {
|
|
@@ -91,6 +92,8 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
91
92
|
private readonly apiDefinitions;
|
|
92
93
|
/** The guards map given at creation, kept for apiSignatures(): a guard's schema is part of the signature of every endpoint declaring it. */
|
|
93
94
|
private readonly guards;
|
|
95
|
+
/** The rate-limit policies given at creation, kept for apiOptionEntries(), which records each one less its key handler. */
|
|
96
|
+
private readonly rateLimitPolicies;
|
|
94
97
|
private hookList;
|
|
95
98
|
private createdHooks;
|
|
96
99
|
private initPromise;
|
|
@@ -152,7 +155,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
152
155
|
addRoute(condition: RegExp | LambderRouteConditionFn | LambderRouteMatcher, actionFn: LambderRouteHandler): this;
|
|
153
156
|
addSessionRoute<TPath extends LambderRoutePath>(condition: TPath, actionFn: ((ctx: LambderSessionRenderContext<any, TSessionData, LambderPathParamsOf<TPath>, {}, _TRateLimitPolicies>, resolver: LambderResolver) => MaybePromise<LambderResponse>) & LambderSessionEnabledInstance<_TSessionsEnabled>): this;
|
|
154
157
|
addSessionRoute(condition: RegExp | LambderRouteConditionFn | LambderRouteMatcher, actionFn: LambderSessionRouteHandler<TSessionData> & LambderSessionEnabledInstance<_TSessionsEnabled>): this;
|
|
155
|
-
addApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.input<TInput>, false> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.input<TInput>, false> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never>(name: TName, schema: {
|
|
158
|
+
addApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TAnswer extends LambderReadonlyDeep<z.input<TOutput>>, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.input<TInput>, false> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.input<TInput>, false> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never>(name: TName, schema: {
|
|
156
159
|
input: TInput;
|
|
157
160
|
output: TOutput;
|
|
158
161
|
} & {
|
|
@@ -160,8 +163,18 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
160
163
|
rateLimit?: TRateOpt;
|
|
161
164
|
/** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
|
|
162
165
|
idempotency?: _TIdempotencyEnabled extends true ? TIdempotencyOpt : never;
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Whether this API's answers are compressed for a caller that accepts it: "auto" (the default) when the
|
|
168
|
+
* body is large enough to gain, false never, true always. false suits an answer of base64 bytes: once
|
|
169
|
+
* compressed it leaves the function base64-encoded again, so it is no smaller under Lambda's response
|
|
170
|
+
* cap or to a lambda caller, and a browser gets it only about a quarter smaller for the time spent at
|
|
171
|
+
* both ends. A transport setting of this server's, not part of the API's contract.
|
|
172
|
+
*/
|
|
173
|
+
compress?: boolean | "auto";
|
|
174
|
+
} & LambderRequirableGuardsField<_TPublicGuardsRequired, TGuardsOpt>,
|
|
175
|
+
/** Answers the call by returning its output (parsed through `output` before it is sent), or refuses it with refuse(). */
|
|
176
|
+
handler: (ctx: LambderRenderContext<z.infer<TInput>, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>, TSessionData, _TRateLimitPolicies>) => MaybePromise<TAnswer>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.input<TInput>, LambderJsonOutputOf<z.output<TOutput>>, "public", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
|
|
177
|
+
addSessionApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TAnswer extends LambderReadonlyDeep<z.input<TOutput>>, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.input<TInput>, true> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.input<TInput>, true> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never>(name: TName, schema: {
|
|
165
178
|
input: TInput;
|
|
166
179
|
output: TOutput;
|
|
167
180
|
} & {
|
|
@@ -169,7 +182,17 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
169
182
|
rateLimit?: TRateOpt;
|
|
170
183
|
/** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
|
|
171
184
|
idempotency?: _TIdempotencyEnabled extends true ? TIdempotencyOpt : never;
|
|
172
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Whether this API's answers are compressed for a caller that accepts it: "auto" (the default) when the
|
|
187
|
+
* body is large enough to gain, false never, true always. false suits an answer of base64 bytes: once
|
|
188
|
+
* compressed it leaves the function base64-encoded again, so it is no smaller under Lambda's response
|
|
189
|
+
* cap or to a lambda caller, and a browser gets it only about a quarter smaller for the time spent at
|
|
190
|
+
* both ends. A transport setting of this server's, not part of the API's contract.
|
|
191
|
+
*/
|
|
192
|
+
compress?: boolean | "auto";
|
|
193
|
+
} & LambderRequirableGuardsField<_TSessionGuardsRequired, TGuardsOpt> & LambderSessionEnabledInstance<_TSessionsEnabled>,
|
|
194
|
+
/** Answers the call by returning its output (parsed through `output` before it is sent), or refuses it with refuse(). */
|
|
195
|
+
handler: (ctx: LambderSessionRenderContext<z.infer<TInput>, TSessionData, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>, _TRateLimitPolicies>) => MaybePromise<TAnswer>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.input<TInput>, LambderJsonOutputOf<z.output<TOutput>>, "session", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
|
|
173
196
|
/**
|
|
174
197
|
* What registering an API is, for addApi and addSessionApi alike: the
|
|
175
198
|
* checks that can refuse it, then its definition recorded (what
|
|
@@ -246,7 +269,25 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
246
269
|
* unless the generator writes it there.
|
|
247
270
|
*/
|
|
248
271
|
apiSignatureEntries(): Promise<LambderApiSignatureEntry[]>;
|
|
249
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Every registered API's mode and declared options as plain data, with
|
|
274
|
+
* the rate-limit policies and guards they name reduced to what is not
|
|
275
|
+
* code: what writeApiOptions (lambder/build) writes to a module a client,
|
|
276
|
+
* a mock or a test imports instead of the server. The contract carries
|
|
277
|
+
* the same options as types; this is the same fact as a value, for code
|
|
278
|
+
* that decides something at runtime with it.
|
|
279
|
+
*
|
|
280
|
+
* Nothing here is a secret or a handler by construction. A guard's
|
|
281
|
+
* parameter is written as it was declared, so it has to be plain data
|
|
282
|
+
* (a permission string, a list, a reason); one that is not fails by API
|
|
283
|
+
* and guard name. A policy's key handler is never written: its `per`
|
|
284
|
+
* says "custom" and no more. A guard's input schema is never written
|
|
285
|
+
* either; its declaration says only which of the three input modes it
|
|
286
|
+
* has. Every table is sorted by name, so the module diffs by endpoint
|
|
287
|
+
* and never moves when registrations are reordered.
|
|
288
|
+
*/
|
|
289
|
+
apiOptionEntries(): LambderApiOptionEntries;
|
|
290
|
+
getResponseBuilder(ctx?: LambderRenderContext): LambderResponseBuilder;
|
|
250
291
|
private getResolver;
|
|
251
292
|
getHandler(): LambderHandler;
|
|
252
293
|
/** True when the Lambda event is an API Gateway HTTP event (REST API v1 or HTTP API / Function URL v2). */
|
|
@@ -344,12 +385,30 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
344
385
|
private inputValidationRefusal;
|
|
345
386
|
/**
|
|
346
387
|
* One API call through the core: the pipeline runs the protocol steps and
|
|
347
|
-
* calls back for the handler, whose
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
388
|
+
* calls back for the handler, whose returned output becomes the answer
|
|
389
|
+
* the pipeline stores and hands back. The context is the pipeline's
|
|
390
|
+
* context, so a session it fetched is on ctx.session and the validated
|
|
391
|
+
* payload is on ctx.apiPayload when the handler runs.
|
|
392
|
+
*
|
|
393
|
+
* The output goes out as the API's schema declares it. The type system
|
|
394
|
+
* accepts a value that carries more than the schema (a row read straight
|
|
395
|
+
* from a table is assignable to a narrower object type), and without the
|
|
396
|
+
* parse the extra fields, a password hash included, would reach the
|
|
397
|
+
* client. zod strips what the schema does not declare, fills its defaults
|
|
398
|
+
* and applies its transforms, so the wire and the idempotency store only
|
|
399
|
+
* see the declared shape. The handler returns the schema's input form, so
|
|
400
|
+
* a transform runs exactly once.
|
|
401
|
+
*
|
|
402
|
+
* An output the schema rejects is the handler breaking its contract,
|
|
403
|
+
* answered as a crash rather than sent (LambderApiOutputValidationError,
|
|
404
|
+
* which an idempotency key records as its answer, since the handler has
|
|
405
|
+
* already run). The parse is synchronous, so an output schema cannot be
|
|
406
|
+
* async: zod throws from a synchronous parse that meets an async
|
|
407
|
+
* refinement or transform, and a transform may throw of its own accord.
|
|
408
|
+
* Either throw becomes the same error, carrying what was thrown as its
|
|
409
|
+
* cause. Left to escape as it is, it would read as the handler crashing
|
|
410
|
+
* before its answer: the idempotency engine would release the key's claim
|
|
411
|
+
* and every retry would run the operation again.
|
|
353
412
|
*/
|
|
354
413
|
private runApi;
|
|
355
414
|
/** A thrown LambderApiRefusal (from a hook, say) as the structured API envelope: the core's one mapping. */
|
package/dist/core/Lambder.js
CHANGED
|
@@ -15,7 +15,9 @@ import { LambderApiPipeline } from "../api/LambderApiPipeline.js";
|
|
|
15
15
|
import { LAMBDER_BACKEND_SWAP, LAMBDER_CRASH_WATCH } from "../shared/util/LambderTestingDoors.js";
|
|
16
16
|
import { apiSignatureOf } from "../api/LambderApiSignature.js";
|
|
17
17
|
import { apiNameKeyOf } from "../shared/wire/LambderApiSignature.js";
|
|
18
|
-
import {
|
|
18
|
+
import { assertPlainData } from "../shared/util/assertPlainData.js";
|
|
19
|
+
import { apiNotFoundAnswer, buildApiEnvelope, envelopeAnswer, refusalAnswer, sessionExpiredAnswer, } from "../api/LambderApiEnvelope.js";
|
|
20
|
+
import { LambderApiOutputValidationError } from "../api/LambderApiOutputValidationError.js";
|
|
19
21
|
import { bindContextTools, createContext, isV2HttpEvent, } from "./LambderContext.js";
|
|
20
22
|
import { COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD } from "../shared/wire/LambderRequestPayload.js";
|
|
21
23
|
import { coerceToError } from "../shared/wire/LambderCrashDetail.js";
|
|
@@ -80,6 +82,8 @@ export default class Lambder {
|
|
|
80
82
|
apiDefinitions = new Map();
|
|
81
83
|
/** The guards map given at creation, kept for apiSignatures(): a guard's schema is part of the signature of every endpoint declaring it. */
|
|
82
84
|
guards;
|
|
85
|
+
/** The rate-limit policies given at creation, kept for apiOptionEntries(), which records each one less its key handler. */
|
|
86
|
+
rateLimitPolicies;
|
|
83
87
|
hookList = { "beforeRender": [], "afterRender": [], "fallback": [] };
|
|
84
88
|
createdHooks = [];
|
|
85
89
|
initPromise = null;
|
|
@@ -122,6 +126,7 @@ export default class Lambder {
|
|
|
122
126
|
}
|
|
123
127
|
const session = options.session;
|
|
124
128
|
this.guards = options.guards;
|
|
129
|
+
this.rateLimitPolicies = options.rateLimits?.policies;
|
|
125
130
|
this.pipeline = new LambderApiPipeline({
|
|
126
131
|
apiVersion: this.apiVersion,
|
|
127
132
|
minApiVersion: options.minApiVersion,
|
|
@@ -259,12 +264,16 @@ export default class Lambder {
|
|
|
259
264
|
return this;
|
|
260
265
|
}
|
|
261
266
|
// Typed API with Zod
|
|
262
|
-
addApi(name, schema,
|
|
267
|
+
addApi(name, schema,
|
|
268
|
+
/** Answers the call by returning its output (parsed through `output` before it is sent), or refuses it with refuse(). */
|
|
269
|
+
handler) {
|
|
263
270
|
this.registerApi(name, "public", schema, handler);
|
|
264
271
|
return this;
|
|
265
272
|
}
|
|
266
273
|
// Typed Session API with Zod
|
|
267
|
-
addSessionApi(name, schema,
|
|
274
|
+
addSessionApi(name, schema,
|
|
275
|
+
/** Answers the call by returning its output (parsed through `output` before it is sent), or refuses it with refuse(). */
|
|
276
|
+
handler) {
|
|
268
277
|
this.registerApi(name, "session", schema, handler);
|
|
269
278
|
return this;
|
|
270
279
|
}
|
|
@@ -299,7 +308,7 @@ export default class Lambder {
|
|
|
299
308
|
this.apiDefinitions.set(name, definition);
|
|
300
309
|
this.actionList.push({
|
|
301
310
|
match: (ctx) => ctx.apiName === name ? {} : false,
|
|
302
|
-
actionFn: (ctx) => this.runApi(ctx, definition, handler),
|
|
311
|
+
actionFn: (ctx) => this.runApi(ctx, definition, schema.output, schema.compress ?? "auto", handler),
|
|
303
312
|
});
|
|
304
313
|
}
|
|
305
314
|
addHook(hookEvent, hookFn, priority = 0) {
|
|
@@ -413,6 +422,71 @@ export default class Lambder {
|
|
|
413
422
|
entries.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
414
423
|
return entries;
|
|
415
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Every registered API's mode and declared options as plain data, with
|
|
427
|
+
* the rate-limit policies and guards they name reduced to what is not
|
|
428
|
+
* code: what writeApiOptions (lambder/build) writes to a module a client,
|
|
429
|
+
* a mock or a test imports instead of the server. The contract carries
|
|
430
|
+
* the same options as types; this is the same fact as a value, for code
|
|
431
|
+
* that decides something at runtime with it.
|
|
432
|
+
*
|
|
433
|
+
* Nothing here is a secret or a handler by construction. A guard's
|
|
434
|
+
* parameter is written as it was declared, so it has to be plain data
|
|
435
|
+
* (a permission string, a list, a reason); one that is not fails by API
|
|
436
|
+
* and guard name. A policy's key handler is never written: its `per`
|
|
437
|
+
* says "custom" and no more. A guard's input schema is never written
|
|
438
|
+
* either; its declaration says only which of the three input modes it
|
|
439
|
+
* has. Every table is sorted by name, so the module diffs by endpoint
|
|
440
|
+
* and never moves when registrations are reordered.
|
|
441
|
+
*/
|
|
442
|
+
apiOptionEntries() {
|
|
443
|
+
const apis = {};
|
|
444
|
+
for (const name of [...this.apiDefinitions.keys()].sort()) {
|
|
445
|
+
const { mode, guards, rateLimit, idempotency } = this.apiDefinitions.get(name);
|
|
446
|
+
const entry = { mode };
|
|
447
|
+
if (guards !== undefined) {
|
|
448
|
+
assertPlainData(guards, `the guards option of API "${name}"`);
|
|
449
|
+
entry.guards = guards;
|
|
450
|
+
}
|
|
451
|
+
if (rateLimit !== undefined) {
|
|
452
|
+
assertPlainData(rateLimit, `the rateLimit option of API "${name}"`);
|
|
453
|
+
entry.rateLimit = rateLimit;
|
|
454
|
+
}
|
|
455
|
+
if (idempotency !== undefined)
|
|
456
|
+
entry.idempotency = idempotency;
|
|
457
|
+
apis[name] = entry;
|
|
458
|
+
}
|
|
459
|
+
const rateLimitPolicies = {};
|
|
460
|
+
for (const name of Object.keys(this.rateLimitPolicies ?? {}).sort()) {
|
|
461
|
+
const { per, budget, chargeAt, errorMessage, ...windows } = this.rateLimitPolicies[name];
|
|
462
|
+
const entry = {};
|
|
463
|
+
for (const [window, limit] of Object.entries(windows)) {
|
|
464
|
+
if (limit !== undefined)
|
|
465
|
+
entry[window] = limit;
|
|
466
|
+
}
|
|
467
|
+
if (per !== undefined)
|
|
468
|
+
entry.per = per === "ip" || per === "session" ? per : "custom";
|
|
469
|
+
if (budget !== undefined)
|
|
470
|
+
entry.budget = budget;
|
|
471
|
+
if (chargeAt !== undefined)
|
|
472
|
+
entry.chargeAt = chargeAt;
|
|
473
|
+
if (errorMessage !== undefined) {
|
|
474
|
+
assertPlainData(errorMessage, `the errorMessage of rate-limit policy "${name}"`);
|
|
475
|
+
entry.errorMessage = errorMessage;
|
|
476
|
+
}
|
|
477
|
+
rateLimitPolicies[name] = entry;
|
|
478
|
+
}
|
|
479
|
+
const guards = {};
|
|
480
|
+
for (const name of Object.keys(this.guards ?? {}).sort()) {
|
|
481
|
+
const guard = this.guards[name];
|
|
482
|
+
guards[name] = {
|
|
483
|
+
input: guard.apiInput ? "apiInput" : guard.guardInput ? "guardInput" : "none",
|
|
484
|
+
session: guard.session === true,
|
|
485
|
+
runAt: guard.runAt ?? "beforeInputValidation",
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
return { apis, rateLimitPolicies, guards };
|
|
489
|
+
}
|
|
416
490
|
getResponseBuilder(ctx) {
|
|
417
491
|
return new LambderResponseBuilder({
|
|
418
492
|
files: this.files,
|
|
@@ -631,8 +705,8 @@ export default class Lambder {
|
|
|
631
705
|
catch (err) {
|
|
632
706
|
response = (await this.answerThrown(err, ctx, resolver)).copy();
|
|
633
707
|
}
|
|
634
|
-
// Only what the hooks themselves wrote (
|
|
635
|
-
// hook) is left to apply, which leaves their overrides standing.
|
|
708
|
+
// Only what the hooks themselves wrote (ctx.setResponseHeader
|
|
709
|
+
// inside a hook) is left to apply, which leaves their overrides standing.
|
|
636
710
|
// A hook that answered with a different response takes the whole
|
|
637
711
|
// set instead: headers belong to the call, not to the response
|
|
638
712
|
// that first carried them, so the call's session cookie must
|
|
@@ -814,48 +888,52 @@ export default class Lambder {
|
|
|
814
888
|
}
|
|
815
889
|
/**
|
|
816
890
|
* One API call through the core: the pipeline runs the protocol steps and
|
|
817
|
-
* calls back for the handler, whose
|
|
818
|
-
*
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
*
|
|
822
|
-
*
|
|
891
|
+
* calls back for the handler, whose returned output becomes the answer
|
|
892
|
+
* the pipeline stores and hands back. The context is the pipeline's
|
|
893
|
+
* context, so a session it fetched is on ctx.session and the validated
|
|
894
|
+
* payload is on ctx.apiPayload when the handler runs.
|
|
895
|
+
*
|
|
896
|
+
* The output goes out as the API's schema declares it. The type system
|
|
897
|
+
* accepts a value that carries more than the schema (a row read straight
|
|
898
|
+
* from a table is assignable to a narrower object type), and without the
|
|
899
|
+
* parse the extra fields, a password hash included, would reach the
|
|
900
|
+
* client. zod strips what the schema does not declare, fills its defaults
|
|
901
|
+
* and applies its transforms, so the wire and the idempotency store only
|
|
902
|
+
* see the declared shape. The handler returns the schema's input form, so
|
|
903
|
+
* a transform runs exactly once.
|
|
904
|
+
*
|
|
905
|
+
* An output the schema rejects is the handler breaking its contract,
|
|
906
|
+
* answered as a crash rather than sent (LambderApiOutputValidationError,
|
|
907
|
+
* which an idempotency key records as its answer, since the handler has
|
|
908
|
+
* already run). The parse is synchronous, so an output schema cannot be
|
|
909
|
+
* async: zod throws from a synchronous parse that meets an async
|
|
910
|
+
* refinement or transform, and a transform may throw of its own accord.
|
|
911
|
+
* Either throw becomes the same error, carrying what was thrown as its
|
|
912
|
+
* cause. Left to escape as it is, it would read as the handler crashing
|
|
913
|
+
* before its answer: the idempotency engine would release the key's claim
|
|
914
|
+
* and every retry would run the operation again.
|
|
823
915
|
*/
|
|
824
|
-
async runApi(ctx, definition, handler) {
|
|
916
|
+
async runApi(ctx, definition, output, compress, handler) {
|
|
825
917
|
const request = ctx.api;
|
|
826
918
|
if (!request)
|
|
827
919
|
throw new Error(`Lambder: API "${definition.name}" was matched by a request that is not an API call.`);
|
|
828
|
-
const resolver = new LambderResolver({ files: this.files, apiVersion: this.apiVersion, ctx, apiOutput: definition.output });
|
|
829
|
-
// What the handler produced, in both forms: the answer went to the
|
|
830
|
-
// pipeline, and the response is kept so it can carry on unchanged.
|
|
831
|
-
const handled = { output: null };
|
|
832
920
|
const { answer } = await this.pipeline.run(request, ctx, definition, async () => {
|
|
833
921
|
ctx.apiPayload = request.payload;
|
|
834
|
-
|
|
922
|
+
const returned = await handler(ctx);
|
|
923
|
+
let parsed;
|
|
835
924
|
try {
|
|
836
|
-
|
|
925
|
+
parsed = output.safeParse(returned);
|
|
837
926
|
}
|
|
838
|
-
catch (
|
|
839
|
-
|
|
840
|
-
// answer like a returned one, stored and replayed alike.
|
|
841
|
-
if (err instanceof LambderResponse)
|
|
842
|
-
response = err;
|
|
843
|
-
else
|
|
844
|
-
throw err;
|
|
927
|
+
catch (thrown) {
|
|
928
|
+
throw new LambderApiOutputValidationError(definition.name, { thrown });
|
|
845
929
|
}
|
|
846
|
-
|
|
847
|
-
|
|
930
|
+
if (!parsed.success)
|
|
931
|
+
throw new LambderApiOutputValidationError(definition.name, { zodError: parsed.error });
|
|
932
|
+
return envelopeAnswer(buildApiEnvelope(this.apiVersion, parsed.data, { logList: ctx.logList }));
|
|
848
933
|
});
|
|
849
|
-
//
|
|
850
|
-
//
|
|
851
|
-
|
|
852
|
-
// persists), and a response rebuilt from it would hand finalization a
|
|
853
|
-
// base64 string it must pass through uncompressed. Identity decides,
|
|
854
|
-
// since the pipeline may have answered with a stored replay or a
|
|
855
|
-
// refusal instead.
|
|
856
|
-
if (handled.output?.answer === answer)
|
|
857
|
-
return handled.output.response;
|
|
858
|
-
return responseFromAnswer(answer);
|
|
934
|
+
// The API's compress option, on whatever answer the call ended with:
|
|
935
|
+
// a replayed one comes back from its store without the hint.
|
|
936
|
+
return responseFromAnswer({ ...answer, compress });
|
|
859
937
|
}
|
|
860
938
|
/** A thrown LambderApiRefusal (from a hook, say) as the structured API envelope: the core's one mapping. */
|
|
861
939
|
apiErrorResponse(err, ctx) {
|
|
@@ -2,6 +2,7 @@ import type { APIGatewayProxyEvent, APIGatewayProxyEventV2, APIGatewayProxyEvent
|
|
|
2
2
|
import type { LambderSessionRecord } from "../shared/contracts/LambderSessionStore.js";
|
|
3
3
|
import { type LambderApiRequest } from "../api/LambderApiRequest.js";
|
|
4
4
|
import { LambderAnswerHeaders } from "../shared/wire/LambderAnswerHeaders.js";
|
|
5
|
+
import { type LambderResponseTools } from "../api/LambderApiCallContext.js";
|
|
5
6
|
import type LambderSessionController from "../session/LambderSessionController.js";
|
|
6
7
|
import type { LambderApiRateLimitPolicyConfig, LambderContextRateLimit, LambderContextRateLimitCheck, LambderRateLimitCheckResult } from "../api/LambderApiRateLimits.js";
|
|
7
8
|
export type LambderHttpEvent = APIGatewayProxyEvent | APIGatewayProxyEventV2;
|
|
@@ -19,7 +20,9 @@ export declare const isV2HttpEvent: (event: unknown) => event is APIGatewayProxy
|
|
|
19
20
|
* API core's call context (session, guardData, responseHeaders, logList),
|
|
20
21
|
* which is the part the pipeline and the session controller work on; the
|
|
21
22
|
* rest is the HTTP request as the Lambda event delivered it, plus the tools
|
|
22
|
-
* the instance rendering it binds on (sessionController, rateLimit, isRateLimited)
|
|
23
|
+
* the instance rendering it binds on (sessionController, rateLimit, isRateLimited)
|
|
24
|
+
* and the response tools (setResponseHeader, addResponseHeader, setCookie,
|
|
25
|
+
* clearCookie) that write onto whatever answer the request ends with.
|
|
23
26
|
*
|
|
24
27
|
* TRateLimitPolicies is the app's policies map on a handler registered with
|
|
25
28
|
* addApi, addSessionApi, addRoute or addSessionRoute, so a policy name is
|
|
@@ -102,9 +105,9 @@ export type LambderRenderContext<TApiPayload = any, TPathParams extends Record<s
|
|
|
102
105
|
lambdaContext: Context;
|
|
103
106
|
/** Which API Gateway payload format the event arrived in, and the response leaves in. */
|
|
104
107
|
eventFormat: LambderHttpEventFormat;
|
|
105
|
-
/** Response headers written during the request (
|
|
108
|
+
/** Response headers written during the request (the response tools below, session cookies), applied onto the response at the end. */
|
|
106
109
|
responseHeaders: LambderAnswerHeaders;
|
|
107
|
-
/** Entries for the API envelope's logList channel
|
|
110
|
+
/** Entries for the API envelope's logList channel: a handler pushes what it wants the caller's debug log to show. */
|
|
108
111
|
logList: unknown[];
|
|
109
112
|
/**
|
|
110
113
|
* Sessions for this request: read the one it carries
|
|
@@ -125,12 +128,12 @@ export type LambderRenderContext<TApiPayload = any, TPathParams extends Record<s
|
|
|
125
128
|
rateLimit: LambderContextRateLimit<TRateLimitPolicies>;
|
|
126
129
|
/** The same count as rateLimit, answered instead of thrown: false, or the window that refused and its retryAfterSeconds. */
|
|
127
130
|
isRateLimited: LambderContextRateLimitCheck<TRateLimitPolicies>;
|
|
128
|
-
};
|
|
131
|
+
} & LambderResponseTools;
|
|
129
132
|
export type LambderSessionRenderContext<TApiPayload = any, SessionData = any, TPathParams extends Record<string, string> = Record<string, string>, TGuardData = {}, TRateLimitPolicies = Record<string, LambderApiRateLimitPolicyConfig>> = Omit<LambderRenderContext<TApiPayload, TPathParams, TGuardData, SessionData, TRateLimitPolicies>, 'session'> & {
|
|
130
133
|
session: LambderSessionRecord<SessionData>;
|
|
131
134
|
};
|
|
132
|
-
/** The members of a render context that
|
|
133
|
-
type LambderContextToolName = "sessionController" | "rateLimit" | "isRateLimited";
|
|
135
|
+
/** The members of a render context that are bound onto it rather than read from its event. */
|
|
136
|
+
type LambderContextToolName = "sessionController" | "rateLimit" | "isRateLimited" | keyof LambderResponseTools;
|
|
134
137
|
/** What an instance binds onto each context it renders: see bindContextTools. */
|
|
135
138
|
export type LambderContextTools = {
|
|
136
139
|
sessionControllerFor: (ctx: LambderRenderContext) => LambderSessionController<any>;
|
|
@@ -4,7 +4,7 @@ import { base64ToText } from "../shared/util/LambderBase64.js";
|
|
|
4
4
|
import { LambderAnswerHeaders } from "../shared/wire/LambderAnswerHeaders.js";
|
|
5
5
|
import { DEFAULT_API_PATH } from "../shared/wire/LambderDefaultApiPath.js";
|
|
6
6
|
import { LAMBDER_INVOKE_API_ID, LAMBDER_LOCAL_API_ID } from "../shared/wire/LambderInvokeApiId.js";
|
|
7
|
-
import { bindCallTools } from "../api/LambderApiCallContext.js";
|
|
7
|
+
import { bindCallTools, responseToolsOf } from "../api/LambderApiCallContext.js";
|
|
8
8
|
import { decodeRequestPath } from "./LambderRequestPath.js";
|
|
9
9
|
/** True for API Gateway HTTP API / Lambda Function URL (payload v2) events. */
|
|
10
10
|
export const isV2HttpEvent = (event) => !!event && typeof event === "object"
|
|
@@ -23,6 +23,7 @@ export const bindContextTools = (ctx, tools) => {
|
|
|
23
23
|
methods: {
|
|
24
24
|
rateLimit: async (policy, key) => { await tools.chargeRateLimit(bound, policy, key, true); },
|
|
25
25
|
isRateLimited: (policy, key) => tools.chargeRateLimit(bound, policy, key, false),
|
|
26
|
+
...responseToolsOf(bound, bound.host),
|
|
26
27
|
},
|
|
27
28
|
});
|
|
28
29
|
return bound;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import LambderResponseBuilder, { type LambderResolverApiMethod, type LambderApiResponseConfig, type LambderResponseOptions } from "./LambderResponseBuilder.js";
|
|
1
|
+
import LambderResponseBuilder from "./LambderResponseBuilder.js";
|
|
3
2
|
import type { LambderResponse } from "./LambderResponse.js";
|
|
4
3
|
type SyncDie<T extends (...args: any[]) => LambderResponse> = (...args: Parameters<T>) => never;
|
|
5
4
|
type AsyncDie<T extends (...args: any[]) => Promise<LambderResponse>> = (...args: Parameters<T>) => Promise<never>;
|
|
6
5
|
/** The `res.die.*` surface: every builder method, throwing what it built. Internal to the resolver, which is the only thing that has one. */
|
|
7
|
-
interface DieResolverMethods
|
|
6
|
+
interface DieResolverMethods {
|
|
8
7
|
raw: SyncDie<LambderResponseBuilder["raw"]>;
|
|
9
8
|
json: SyncDie<LambderResponseBuilder["json"]>;
|
|
10
9
|
text: SyncDie<LambderResponseBuilder["text"]>;
|
|
@@ -15,25 +14,20 @@ interface DieResolverMethods<TOutput> {
|
|
|
15
14
|
redirect: SyncDie<LambderResponseBuilder["redirect"]>;
|
|
16
15
|
versionExpired: SyncDie<LambderResponseBuilder["versionExpired"]>;
|
|
17
16
|
fileBase64: SyncDie<LambderResponseBuilder["fileBase64"]>;
|
|
18
|
-
api:
|
|
19
|
-
apiBinary: LambderResolverApiMethod<TOutput, never>;
|
|
17
|
+
api: SyncDie<LambderResponseBuilder["api"]>;
|
|
20
18
|
file: AsyncDie<LambderResponseBuilder["file"]>;
|
|
21
19
|
templateFile: AsyncDie<LambderResponseBuilder["templateFile"]>;
|
|
22
20
|
}
|
|
23
21
|
/**
|
|
24
|
-
* Response builder passed to route
|
|
22
|
+
* Response builder passed to route handlers and hooks.
|
|
25
23
|
*
|
|
26
24
|
* `res.die.*` builds the response and THROWS it, immediately halting the
|
|
27
25
|
* request at any call depth (handlers, hooks, nested service functions).
|
|
28
26
|
* Lambder's render pipeline catches thrown LambderResponse instances and uses
|
|
29
27
|
* them as the response. Plain `throw res.html(...)` works the same way.
|
|
30
28
|
*/
|
|
31
|
-
export default class LambderResolver
|
|
32
|
-
die: DieResolverMethods
|
|
29
|
+
export default class LambderResolver extends LambderResponseBuilder {
|
|
30
|
+
die: DieResolverMethods;
|
|
33
31
|
constructor(...args: ConstructorParameters<typeof LambderResponseBuilder>);
|
|
34
|
-
api(payload: TOutput, config?: LambderApiResponseConfig, options?: LambderResponseOptions): LambderResponse;
|
|
35
|
-
api(payload: null, config: LambderApiNullAnswerConfig, options?: LambderResponseOptions): LambderResponse;
|
|
36
|
-
apiBinary(payload: TOutput, config?: LambderApiResponseConfig, options?: LambderResponseOptions): LambderResponse;
|
|
37
|
-
apiBinary(payload: null, config: LambderApiNullAnswerConfig, options?: LambderResponseOptions): LambderResponse;
|
|
38
32
|
}
|
|
39
33
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import LambderResponseBuilder from "./LambderResponseBuilder.js";
|
|
2
2
|
/**
|
|
3
|
-
* Response builder passed to route
|
|
3
|
+
* Response builder passed to route handlers and hooks.
|
|
4
4
|
*
|
|
5
5
|
* `res.die.*` builds the response and THROWS it, immediately halting the
|
|
6
6
|
* request at any call depth (handlers, hooks, nested service functions).
|
|
@@ -22,21 +22,9 @@ export default class LambderResolver extends LambderResponseBuilder {
|
|
|
22
22
|
redirect: (...a) => { throw this.redirect(...a); },
|
|
23
23
|
versionExpired: (...a) => { throw this.versionExpired(...a); },
|
|
24
24
|
fileBase64: (...a) => { throw this.fileBase64(...a); },
|
|
25
|
-
|
|
26
|
-
api: ((payload, config, options) => {
|
|
27
|
-
throw this.api(payload, config, options);
|
|
28
|
-
}),
|
|
29
|
-
apiBinary: ((payload, config, options) => {
|
|
30
|
-
throw this.apiBinary(payload, config, options);
|
|
31
|
-
}),
|
|
25
|
+
api: (...a) => { throw this.api(...a); },
|
|
32
26
|
file: async (...a) => { throw await this.file(...a); },
|
|
33
27
|
templateFile: async (...a) => { throw await this.templateFile(...a); },
|
|
34
28
|
};
|
|
35
29
|
}
|
|
36
|
-
api(payload, config, options) {
|
|
37
|
-
return super.api(payload, config, options);
|
|
38
|
-
}
|
|
39
|
-
apiBinary(payload, config, options) {
|
|
40
|
-
return super.apiBinary(payload, config, options);
|
|
41
|
-
}
|
|
42
30
|
}
|