lambder 6.0.2 → 7.0.2
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 +2316 -0
- package/README.md +60 -33
- package/dist/api/LambderApiAnswer.d.ts +40 -0
- package/dist/api/LambderApiAnswer.js +19 -0
- package/dist/api/LambderApiCallContext.d.ts +38 -0
- package/dist/api/LambderApiCallContext.js +13 -0
- package/dist/api/LambderApiDefinition.d.ts +18 -0
- package/dist/api/LambderApiDefinition.js +1 -0
- package/dist/api/LambderApiEnvelope.d.ts +67 -0
- package/dist/api/LambderApiEnvelope.js +180 -0
- package/dist/api/LambderApiGuards.d.ts +302 -0
- package/dist/api/LambderApiGuards.js +134 -0
- package/dist/api/LambderApiIdempotency.d.ts +122 -0
- package/dist/api/LambderApiIdempotency.js +330 -0
- package/dist/api/LambderApiPipeline.d.ts +134 -0
- package/dist/api/LambderApiPipeline.js +221 -0
- package/dist/api/LambderApiPolicyEngine.d.ts +36 -0
- package/dist/api/LambderApiPolicyEngine.js +77 -0
- package/dist/api/LambderApiRateLimits.d.ts +206 -0
- package/dist/api/LambderApiRateLimits.js +239 -0
- package/dist/api/LambderApiRequest.d.ts +101 -0
- package/dist/api/LambderApiRequest.js +129 -0
- package/dist/api/LambderApiValidationRefusal.d.ts +32 -0
- package/dist/api/LambderApiValidationRefusal.js +40 -0
- package/dist/client/LambderCaller.d.ts +62 -55
- package/dist/client/LambderCaller.js +147 -90
- package/dist/client/lambderFetchTransport.d.ts +9 -0
- package/dist/client/lambderFetchTransport.js +71 -0
- package/dist/client.d.ts +20 -10
- package/dist/client.js +11 -5
- package/dist/core/Lambder.d.ts +117 -253
- package/dist/core/Lambder.js +374 -341
- package/dist/core/LambderContext.d.ts +54 -44
- package/dist/core/LambderContext.js +41 -110
- package/dist/core/LambderCreateOptions.d.ts +285 -0
- package/dist/core/LambderCreateOptions.js +44 -0
- package/dist/core/LambderFiles.d.ts +1 -45
- package/dist/core/LambderFiles.js +18 -38
- package/dist/core/LambderIndexHtml.d.ts +37 -0
- package/dist/core/LambderIndexHtml.js +87 -0
- package/dist/core/LambderPolicyBuilders.d.ts +17 -0
- package/dist/core/LambderPolicyBuilders.js +16 -0
- package/dist/core/LambderPublicFiles.d.ts +5 -2
- package/dist/core/LambderPublicFiles.js +7 -2
- package/dist/core/LambderResolver.d.ts +8 -6
- package/dist/core/LambderResponse.d.ts +29 -11
- package/dist/core/LambderResponse.js +96 -49
- package/dist/core/LambderResponseBuilder.d.ts +18 -14
- package/dist/core/LambderResponseBuilder.js +19 -25
- package/dist/core/LambderRouting.d.ts +18 -7
- package/dist/core/LambderRouting.js +17 -7
- package/dist/core/LambderTemplatingEngine.d.ts +0 -62
- package/dist/core/LambderTemplatingEngine.js +7 -3
- package/dist/index.d.ts +85 -32
- package/dist/index.js +44 -16
- package/dist/invoke/LambderInvokeCaller.d.ts +46 -139
- package/dist/invoke/LambderInvokeCaller.js +140 -335
- package/dist/invoke/LambderInvokeOutcome.d.ts +165 -0
- package/dist/invoke/LambderInvokeOutcome.js +129 -0
- package/dist/invoke/LambderLambdaEvent.d.ts +81 -0
- package/dist/invoke/LambderLambdaEvent.js +187 -0
- package/dist/invoke/lambderHandlerTransport.d.ts +36 -0
- package/dist/invoke/lambderHandlerTransport.js +89 -0
- package/dist/mock/LambderMockApp.d.ts +352 -0
- package/dist/mock/LambderMockApp.js +815 -0
- package/dist/mock/LambderMockBrowserCookies.d.ts +55 -0
- package/dist/mock/LambderMockBrowserCookies.js +76 -0
- package/dist/mock/LambderMockCallRecorder.d.ts +85 -0
- package/dist/mock/LambderMockCallRecorder.js +183 -0
- package/dist/mock/LambderMockCreateOptions.d.ts +161 -0
- package/dist/mock/LambderMockCreateOptions.js +9 -0
- package/dist/mock/LambderMockEntryRegistry.d.ts +52 -0
- package/dist/mock/LambderMockEntryRegistry.js +126 -0
- package/dist/mock/LambderMockFailureInjector.d.ts +60 -0
- package/dist/mock/LambderMockFailureInjector.js +138 -0
- package/dist/mock/LambderMockTypes.d.ts +421 -0
- package/dist/mock/LambderMockTypes.js +8 -0
- package/dist/mock/lambderMockConsoleLogger.d.ts +16 -0
- package/dist/mock/lambderMockConsoleLogger.js +35 -0
- package/dist/mock/lambderMockInvokeTransport.d.ts +50 -0
- package/dist/mock/lambderMockInvokeTransport.js +52 -0
- package/dist/mock/lambderMockMswHandler.d.ts +99 -0
- package/dist/mock/lambderMockMswHandler.js +126 -0
- package/dist/mock.d.ts +34 -0
- package/dist/mock.js +27 -0
- package/dist/session/LambderSessionController.d.ts +199 -30
- package/dist/session/LambderSessionController.js +396 -82
- package/dist/session/LambderSessionCrypto.d.ts +66 -0
- package/dist/session/LambderSessionCrypto.js +101 -0
- package/dist/session/LambderSessionManager.d.ts +118 -80
- package/dist/session/LambderSessionManager.js +212 -184
- package/dist/shared/LambderI18n.d.ts +6 -6
- package/dist/shared/LambderI18n.js +1 -1
- package/dist/shared/contracts/LambderFileSource.d.ts +33 -0
- package/dist/shared/contracts/LambderFileSource.js +19 -0
- package/dist/shared/contracts/LambderIdempotencyStore.d.ts +66 -0
- package/dist/shared/contracts/LambderIdempotencyStore.js +12 -0
- package/dist/shared/contracts/LambderRateLimiter.d.ts +71 -0
- package/dist/shared/contracts/LambderRateLimiter.js +24 -0
- package/dist/shared/contracts/LambderSessionStore.d.ts +72 -0
- package/dist/shared/contracts/LambderSessionStore.js +13 -0
- package/dist/shared/transport/LambderApiTransport.d.ts +139 -0
- package/dist/shared/transport/LambderApiTransport.js +65 -0
- package/dist/shared/transport/LambderCookieJar.d.ts +121 -0
- package/dist/shared/transport/LambderCookieJar.js +246 -0
- package/dist/shared/transport/lambderCookieJarTransport.d.ts +30 -0
- package/dist/shared/transport/lambderCookieJarTransport.js +60 -0
- package/dist/shared/util/LambderBase64.d.ts +10 -0
- package/dist/shared/util/LambderBase64.js +27 -0
- package/dist/shared/util/LambderCallAbort.d.ts +62 -0
- package/dist/shared/util/LambderCallAbort.js +80 -0
- package/dist/shared/util/LambderClientIp.d.ts +32 -0
- package/dist/shared/util/LambderClientIp.js +56 -0
- package/dist/shared/util/LambderExpiringMap.d.ts +119 -0
- package/dist/shared/util/LambderExpiringMap.js +217 -0
- package/dist/shared/util/LambderKeyFields.d.ts +32 -0
- package/dist/shared/util/LambderKeyFields.js +34 -0
- package/dist/shared/util/LambderNodeModules.d.ts +9 -0
- package/dist/shared/util/LambderNodeModules.js +39 -0
- package/dist/shared/util/LambderOptionChecks.d.ts +17 -0
- package/dist/shared/util/LambderOptionChecks.js +33 -0
- package/dist/shared/util/LambderResponseBrand.d.ts +20 -0
- package/dist/shared/util/LambderResponseBrand.js +18 -0
- package/dist/shared/util/LambderTextDigest.d.ts +17 -0
- package/dist/shared/util/LambderTextDigest.js +34 -0
- package/dist/shared/util/LambderTypeUtilities.d.ts +33 -0
- package/dist/shared/util/LambderTypeUtilities.js +8 -0
- package/dist/shared/wire/LambderAnswerHeaders.d.ts +60 -0
- package/dist/shared/wire/LambderAnswerHeaders.js +94 -0
- package/dist/shared/wire/LambderApiContract.d.ts +129 -0
- package/dist/shared/wire/LambderApiOptionValues.d.ts +39 -0
- package/dist/shared/wire/LambderApiOptionValues.js +11 -0
- package/dist/shared/wire/LambderApiOutcome.d.ts +128 -0
- package/dist/shared/{LambderApiOutcome.js → wire/LambderApiOutcome.js} +16 -9
- package/dist/shared/{LambderApiError.d.ts → wire/LambderApiRefusal.d.ts} +48 -26
- package/dist/shared/{LambderApiError.js → wire/LambderApiRefusal.js} +13 -11
- package/dist/shared/wire/LambderCallOptions.d.ts +171 -0
- package/dist/shared/wire/LambderCallOptions.js +17 -0
- package/dist/shared/{LambderCompressionCodec.d.ts → wire/LambderCompressionCodec.d.ts} +10 -6
- package/dist/shared/{LambderCompressionCodec.js → wire/LambderCompressionCodec.js} +67 -23
- package/dist/shared/{LambderCompressionOption.d.ts → wire/LambderCompressionOption.d.ts} +1 -1
- package/dist/shared/{LambderCompressionOption.js → wire/LambderCompressionOption.js} +3 -4
- package/dist/shared/{LambderCrashDetail.d.ts → wire/LambderCrashDetail.d.ts} +10 -0
- package/dist/shared/{LambderCrashDetail.js → wire/LambderCrashDetail.js} +30 -0
- package/dist/shared/wire/LambderHttpStatus.d.ts +12 -0
- package/dist/shared/wire/LambderHttpStatus.js +1 -0
- package/dist/shared/{LambderRequestPayload.d.ts → wire/LambderRequestPayload.d.ts} +25 -17
- package/dist/shared/{LambderRequestPayload.js → wire/LambderRequestPayload.js} +29 -52
- package/dist/shared/wire/LambderSessionCookieNames.d.ts +9 -0
- package/dist/shared/wire/LambderSessionCookieNames.js +9 -0
- package/dist/stores/LambderDdbCache.d.ts +12 -9
- package/dist/stores/LambderDdbCache.js +56 -47
- package/dist/stores/{LambderDdbIdempotency.d.ts → LambderDdbIdempotencyStore.d.ts} +41 -31
- package/dist/stores/LambderDdbIdempotencyStore.js +319 -0
- package/dist/stores/LambderDdbRateLimiter.d.ts +30 -49
- package/dist/stores/LambderDdbRateLimiter.js +47 -45
- package/dist/stores/LambderDdbSdk.d.ts +83 -6
- package/dist/stores/LambderDdbSdk.js +83 -2
- package/dist/stores/LambderDdbSessionStore.d.ts +65 -0
- package/dist/stores/LambderDdbSessionStore.js +161 -0
- package/dist/stores/LambderHttpFileSource.d.ts +1 -1
- package/dist/stores/LambderHttpFileSource.js +10 -1
- package/dist/stores/LambderLocalFileSource.d.ts +15 -0
- package/dist/stores/LambderLocalFileSource.js +28 -0
- package/dist/stores/LambderMemoryIdempotencyStore.d.ts +63 -0
- package/dist/stores/LambderMemoryIdempotencyStore.js +113 -0
- package/dist/stores/LambderMemoryRateLimiter.d.ts +34 -0
- package/dist/stores/LambderMemoryRateLimiter.js +64 -0
- package/dist/stores/LambderMemorySessionStore.d.ts +48 -0
- package/dist/stores/LambderMemorySessionStore.js +74 -0
- package/dist/stores/LambderS3FileSource.d.ts +1 -1
- package/dist/stores/LambderS3FileSource.js +1 -1
- package/package.json +21 -19
- package/dist/client/LambderMSW.d.ts +0 -69
- package/dist/client/LambderMSW.js +0 -121
- package/dist/policies/LambderApiGuards.d.ts +0 -256
- package/dist/policies/LambderApiGuards.js +0 -94
- package/dist/policies/LambderApiIdempotency.d.ts +0 -58
- package/dist/policies/LambderApiIdempotency.js +0 -219
- package/dist/policies/LambderApiPolicies.d.ts +0 -42
- package/dist/policies/LambderApiPolicies.js +0 -52
- package/dist/policies/LambderApiRateLimits.d.ts +0 -132
- package/dist/policies/LambderApiRateLimits.js +0 -119
- package/dist/shared/LambderApiContract.d.ts +0 -57
- package/dist/shared/LambderApiOutcome.d.ts +0 -69
- package/dist/shared/LambderCallOptions.d.ts +0 -71
- package/dist/shared/LambderCallOptions.js +0 -16
- package/dist/shared/node-polyfills.d.ts +0 -4
- package/dist/shared/node-polyfills.js +0 -58
- package/dist/stores/LambderDdbIdempotency.js +0 -229
- package/dist/testing.d.ts +0 -9
- package/dist/testing.js +0 -8
- /package/dist/shared/{LambderApiContract.js → wire/LambderApiContract.js} +0 -0
- /package/dist/{core → shared/wire}/LambderCookie.d.ts +0 -0
- /package/dist/{core → shared/wire}/LambderCookie.js +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { LambderIdempotencyStore, LambderIdempotencyDoneRecord, LambderIdempotencyBeginResult } from "../shared/contracts/LambderIdempotencyStore.js";
|
|
2
|
+
type MemoryIdempotencyRecord = {
|
|
3
|
+
state: "pending";
|
|
4
|
+
ownerToken: string;
|
|
5
|
+
} | ({
|
|
6
|
+
state: "done";
|
|
7
|
+
ownerToken: string;
|
|
8
|
+
} & LambderIdempotencyDoneRecord);
|
|
9
|
+
/**
|
|
10
|
+
* Idempotency records held in memory: the same claim, settle and replay
|
|
11
|
+
* semantics as LambderDdbIdempotencyStore (owner tokens, pending expiry, lost
|
|
12
|
+
* claims as silent no-ops), over a LambderExpiringMap in place of the table
|
|
13
|
+
* and its TTL. For tests and for the mock runtime; tests/store-conformance
|
|
14
|
+
* drives this and the DynamoDB store through one set of rules.
|
|
15
|
+
*
|
|
16
|
+
* `maxBodyBytes` stands in for the DynamoDB item budget, so the "too-large"
|
|
17
|
+
* path can be exercised; unbounded by default. `now` is injectable so a test
|
|
18
|
+
* can expire a claim without waiting.
|
|
19
|
+
*
|
|
20
|
+
* `maxEntries` is the map's ceiling, 100,000 by default, and it is the one
|
|
21
|
+
* way this store differs from a table: a process cannot hold records without
|
|
22
|
+
* bound, so past the ceiling the SETTLED records are dropped, soonest expiry
|
|
23
|
+
* first, and a retry whose record was dropped executes again instead of
|
|
24
|
+
* replaying. Pending claims are never dropped for room, because losing one
|
|
25
|
+
* lets two concurrent retries execute at once, which is the thing idempotency
|
|
26
|
+
* exists to prevent; a claim that cannot be made room for is reported as
|
|
27
|
+
* "pending" instead, so the duplicate is refused rather than run.
|
|
28
|
+
*/
|
|
29
|
+
export declare class LambderMemoryIdempotencyStore implements LambderIdempotencyStore {
|
|
30
|
+
private readonly records;
|
|
31
|
+
private readonly maxBodyBytes;
|
|
32
|
+
private readonly now;
|
|
33
|
+
private ownerCounter;
|
|
34
|
+
/** Set while the store is refusing claims, so a flood reports the saturation once rather than once per request. */
|
|
35
|
+
private claimCeilingReported;
|
|
36
|
+
constructor(options?: {
|
|
37
|
+
maxBodyBytes?: number;
|
|
38
|
+
now?: () => number;
|
|
39
|
+
maxEntries?: number;
|
|
40
|
+
});
|
|
41
|
+
private nowSeconds;
|
|
42
|
+
/** A settled record as the engine reads it: a copy, so a caller writing onto what it got back cannot rewrite the record. */
|
|
43
|
+
private static answerOf;
|
|
44
|
+
peek(scopeKey: string): Promise<LambderIdempotencyDoneRecord | null>;
|
|
45
|
+
begin(scopeKey: string, { pendingTtlSeconds }: {
|
|
46
|
+
pendingTtlSeconds: number;
|
|
47
|
+
}): Promise<LambderIdempotencyBeginResult>;
|
|
48
|
+
complete(scopeKey: string, ownerToken: string, { statusCode, headers, body, ttlSeconds }: LambderIdempotencyDoneRecord & {
|
|
49
|
+
ttlSeconds: number;
|
|
50
|
+
}): Promise<"stored" | "too-large" | "lost">;
|
|
51
|
+
abandon(scopeKey: string, ownerToken: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* The record under a scope, for assertions; null when absent or expired.
|
|
54
|
+
* A copy, like every other read here, so an assertion that pokes at what
|
|
55
|
+
* it got back cannot edit the stored record.
|
|
56
|
+
*/
|
|
57
|
+
recordOf(scopeKey: string): MemoryIdempotencyRecord | null;
|
|
58
|
+
/** Number of live records held. */
|
|
59
|
+
get size(): number;
|
|
60
|
+
/** Forgets every record. */
|
|
61
|
+
reset(): void;
|
|
62
|
+
}
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { LambderExpiringMap, LambderExpiringMapFullError } from "../shared/util/LambderExpiringMap.js";
|
|
2
|
+
/**
|
|
3
|
+
* Idempotency records held in memory: the same claim, settle and replay
|
|
4
|
+
* semantics as LambderDdbIdempotencyStore (owner tokens, pending expiry, lost
|
|
5
|
+
* claims as silent no-ops), over a LambderExpiringMap in place of the table
|
|
6
|
+
* and its TTL. For tests and for the mock runtime; tests/store-conformance
|
|
7
|
+
* drives this and the DynamoDB store through one set of rules.
|
|
8
|
+
*
|
|
9
|
+
* `maxBodyBytes` stands in for the DynamoDB item budget, so the "too-large"
|
|
10
|
+
* path can be exercised; unbounded by default. `now` is injectable so a test
|
|
11
|
+
* can expire a claim without waiting.
|
|
12
|
+
*
|
|
13
|
+
* `maxEntries` is the map's ceiling, 100,000 by default, and it is the one
|
|
14
|
+
* way this store differs from a table: a process cannot hold records without
|
|
15
|
+
* bound, so past the ceiling the SETTLED records are dropped, soonest expiry
|
|
16
|
+
* first, and a retry whose record was dropped executes again instead of
|
|
17
|
+
* replaying. Pending claims are never dropped for room, because losing one
|
|
18
|
+
* lets two concurrent retries execute at once, which is the thing idempotency
|
|
19
|
+
* exists to prevent; a claim that cannot be made room for is reported as
|
|
20
|
+
* "pending" instead, so the duplicate is refused rather than run.
|
|
21
|
+
*/
|
|
22
|
+
export class LambderMemoryIdempotencyStore {
|
|
23
|
+
records;
|
|
24
|
+
maxBodyBytes;
|
|
25
|
+
now;
|
|
26
|
+
ownerCounter = 0;
|
|
27
|
+
/** Set while the store is refusing claims, so a flood reports the saturation once rather than once per request. */
|
|
28
|
+
claimCeilingReported = false;
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.maxBodyBytes = options.maxBodyBytes ?? Number.POSITIVE_INFINITY;
|
|
31
|
+
this.now = options.now ?? (() => Date.now());
|
|
32
|
+
this.records = new LambderExpiringMap({ now: this.now, maxEntries: options.maxEntries });
|
|
33
|
+
}
|
|
34
|
+
nowSeconds() { return Math.floor(this.now() / 1000); }
|
|
35
|
+
/** A settled record as the engine reads it: a copy, so a caller writing onto what it got back cannot rewrite the record. */
|
|
36
|
+
static answerOf(record) {
|
|
37
|
+
return { statusCode: record.statusCode, headers: structuredClone(record.headers), body: record.body };
|
|
38
|
+
}
|
|
39
|
+
async peek(scopeKey) {
|
|
40
|
+
const record = this.records.get(scopeKey);
|
|
41
|
+
if (record?.state !== "done")
|
|
42
|
+
return null;
|
|
43
|
+
return LambderMemoryIdempotencyStore.answerOf(record);
|
|
44
|
+
}
|
|
45
|
+
async begin(scopeKey, { pendingTtlSeconds }) {
|
|
46
|
+
const existing = this.records.get(scopeKey);
|
|
47
|
+
if (existing) {
|
|
48
|
+
if (existing.state === "done")
|
|
49
|
+
return { state: "done", ...LambderMemoryIdempotencyStore.answerOf(existing) };
|
|
50
|
+
return { state: "pending" };
|
|
51
|
+
}
|
|
52
|
+
this.ownerCounter += 1;
|
|
53
|
+
const ownerToken = `owner-${this.ownerCounter}`;
|
|
54
|
+
try {
|
|
55
|
+
// Not evictable: the claim is the only thing standing between two
|
|
56
|
+
// concurrent retries and two executions, and it is also the entry
|
|
57
|
+
// the soonest-expiry rule would otherwise take first, since a
|
|
58
|
+
// claim lives for minutes and the record it becomes for a day.
|
|
59
|
+
this.records.set(scopeKey, { state: "pending", ownerToken }, this.nowSeconds() + pendingTtlSeconds, { evictable: false });
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (!(error instanceof LambderExpiringMapFullError))
|
|
63
|
+
throw error;
|
|
64
|
+
// Every record held is a live claim, so there is no room and
|
|
65
|
+
// nothing safe to take. "pending" is the interface's shape for
|
|
66
|
+
// "the claim is not yours": the caller refuses the duplicate,
|
|
67
|
+
// which is the answer that cannot execute anything twice, and a
|
|
68
|
+
// retry once the flood drains claims normally.
|
|
69
|
+
if (!this.claimCeilingReported) {
|
|
70
|
+
this.claimCeilingReported = true;
|
|
71
|
+
console.error("LambderMemoryIdempotencyStore: every record held is a live claim, so new requests are refused as duplicates until one settles. Raise maxEntries or move to LambderDdbIdempotencyStore.");
|
|
72
|
+
}
|
|
73
|
+
return { state: "pending" };
|
|
74
|
+
}
|
|
75
|
+
this.claimCeilingReported = false;
|
|
76
|
+
return { state: "new", ownerToken };
|
|
77
|
+
}
|
|
78
|
+
async complete(scopeKey, ownerToken, { statusCode, headers, body, ttlSeconds }) {
|
|
79
|
+
// Size first, ownership second, the order LambderDdbIdempotencyStore works
|
|
80
|
+
// in: it settles with one conditional write, so the only thing it can
|
|
81
|
+
// decide before going to the table is whether the body fits.
|
|
82
|
+
if (new TextEncoder().encode(body).length > this.maxBodyBytes)
|
|
83
|
+
return "too-large";
|
|
84
|
+
const existing = this.records.get(scopeKey);
|
|
85
|
+
if (!existing || existing.ownerToken !== ownerToken)
|
|
86
|
+
return "lost";
|
|
87
|
+
// Settled records ARE evictable: the claim's job is done, and a record
|
|
88
|
+
// the ceiling drops costs a retry its replay, not its exclusivity.
|
|
89
|
+
// The key is already held, so this write cannot cross the ceiling.
|
|
90
|
+
this.records.set(scopeKey, { state: "done", ownerToken, statusCode, headers: structuredClone(headers), body }, this.nowSeconds() + ttlSeconds);
|
|
91
|
+
return "stored";
|
|
92
|
+
}
|
|
93
|
+
async abandon(scopeKey, ownerToken) {
|
|
94
|
+
const existing = this.records.get(scopeKey);
|
|
95
|
+
if (existing && existing.ownerToken === ownerToken)
|
|
96
|
+
this.records.delete(scopeKey);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The record under a scope, for assertions; null when absent or expired.
|
|
100
|
+
* A copy, like every other read here, so an assertion that pokes at what
|
|
101
|
+
* it got back cannot edit the stored record.
|
|
102
|
+
*/
|
|
103
|
+
recordOf(scopeKey) {
|
|
104
|
+
const record = this.records.get(scopeKey);
|
|
105
|
+
return record ? structuredClone(record) : null;
|
|
106
|
+
}
|
|
107
|
+
/** Number of live records held. */
|
|
108
|
+
get size() { return this.records.size; }
|
|
109
|
+
/** Forgets every record. */
|
|
110
|
+
reset() {
|
|
111
|
+
this.records.clear();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type LambderRateLimiter, type LambderRateLimitPolicy, type LambderRateLimitResult, type LambderRateLimitWindow } from "../shared/contracts/LambderRateLimiter.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fixed-window rate limiter held in memory: the same windows, the same
|
|
4
|
+
* attempts-count semantics and the same evaluation order as
|
|
5
|
+
* LambderDdbRateLimiter, with a Map in place of the table. For tests and
|
|
6
|
+
* for the mock runtime; a single-process server could use it too, with the
|
|
7
|
+
* caveat that its counters are per process and vanish with it.
|
|
8
|
+
*
|
|
9
|
+
* `now` is injectable so a test can move time forward and watch a window
|
|
10
|
+
* reset without waiting for it.
|
|
11
|
+
*
|
|
12
|
+
* `maxEntries` is the ceiling on counters held at once, 100,000 by default,
|
|
13
|
+
* and it is the one way this limiter differs from the table: a process cannot
|
|
14
|
+
* hold counters without bound, so past the ceiling the counters closest to
|
|
15
|
+
* their window's end are dropped and a key whose counter was dropped starts
|
|
16
|
+
* that window again from zero. It takes one distinct key per counter to get
|
|
17
|
+
* there (a limit keyed per IP under a flood from many of them), and the
|
|
18
|
+
* counters with the most life left, which are the long-window ones, are the
|
|
19
|
+
* last to go. A deployment where that matters wants LambderDdbRateLimiter,
|
|
20
|
+
* whose counters are not held in the process at all.
|
|
21
|
+
*/
|
|
22
|
+
export declare class LambderMemoryRateLimiter implements LambderRateLimiter {
|
|
23
|
+
private readonly counters;
|
|
24
|
+
private readonly now;
|
|
25
|
+
constructor(options?: {
|
|
26
|
+
now?: () => number;
|
|
27
|
+
maxEntries?: number;
|
|
28
|
+
});
|
|
29
|
+
isRateLimited(trackerKey: string, policy: LambderRateLimitPolicy): Promise<LambderRateLimitResult>;
|
|
30
|
+
/** The attempts counted so far for a key in the current window of `window`; for assertions. */
|
|
31
|
+
countOf(trackerKey: string, window: LambderRateLimitWindow): number;
|
|
32
|
+
/** Forgets every counter. */
|
|
33
|
+
reset(): void;
|
|
34
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { RATE_LIMIT_WINDOWS, } from "../shared/contracts/LambderRateLimiter.js";
|
|
2
|
+
import { LambderExpiringMap } from "../shared/util/LambderExpiringMap.js";
|
|
3
|
+
import { joinKeyFields } from "../shared/util/LambderKeyFields.js";
|
|
4
|
+
/**
|
|
5
|
+
* Fixed-window rate limiter held in memory: the same windows, the same
|
|
6
|
+
* attempts-count semantics and the same evaluation order as
|
|
7
|
+
* LambderDdbRateLimiter, with a Map in place of the table. For tests and
|
|
8
|
+
* for the mock runtime; a single-process server could use it too, with the
|
|
9
|
+
* caveat that its counters are per process and vanish with it.
|
|
10
|
+
*
|
|
11
|
+
* `now` is injectable so a test can move time forward and watch a window
|
|
12
|
+
* reset without waiting for it.
|
|
13
|
+
*
|
|
14
|
+
* `maxEntries` is the ceiling on counters held at once, 100,000 by default,
|
|
15
|
+
* and it is the one way this limiter differs from the table: a process cannot
|
|
16
|
+
* hold counters without bound, so past the ceiling the counters closest to
|
|
17
|
+
* their window's end are dropped and a key whose counter was dropped starts
|
|
18
|
+
* that window again from zero. It takes one distinct key per counter to get
|
|
19
|
+
* there (a limit keyed per IP under a flood from many of them), and the
|
|
20
|
+
* counters with the most life left, which are the long-window ones, are the
|
|
21
|
+
* last to go. A deployment where that matters wants LambderDdbRateLimiter,
|
|
22
|
+
* whose counters are not held in the process at all.
|
|
23
|
+
*/
|
|
24
|
+
export class LambderMemoryRateLimiter {
|
|
25
|
+
counters;
|
|
26
|
+
now;
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.now = options.now ?? (() => Date.now());
|
|
29
|
+
this.counters = new LambderExpiringMap({ now: this.now, maxEntries: options.maxEntries });
|
|
30
|
+
}
|
|
31
|
+
async isRateLimited(trackerKey, policy) {
|
|
32
|
+
const nowSeconds = Math.floor(this.now() / 1000);
|
|
33
|
+
for (const { key, seconds } of RATE_LIMIT_WINDOWS) {
|
|
34
|
+
const limit = policy[key];
|
|
35
|
+
if (!limit)
|
|
36
|
+
continue;
|
|
37
|
+
const windowStart = Math.floor(nowSeconds / seconds) * seconds;
|
|
38
|
+
// The table gives the tracker key and the window separate
|
|
39
|
+
// attributes; one string here has to keep them as distinct, so the
|
|
40
|
+
// fields are joined through the escaping join rather than glued.
|
|
41
|
+
const counterKey = joinKeyFields(trackerKey, key, String(windowStart));
|
|
42
|
+
const counter = this.counters.get(counterKey) ?? { count: 0 };
|
|
43
|
+
// The DynamoDB limiter's conditional ADD: allowed while the count
|
|
44
|
+
// before this attempt is under the limit, and the refused attempt
|
|
45
|
+
// leaves the counter where it was.
|
|
46
|
+
if (counter.count >= limit)
|
|
47
|
+
return { window: key, limit, resetAt: windowStart + seconds };
|
|
48
|
+
counter.count += 1;
|
|
49
|
+
// The counter dies with its window, which is also the table's TTL.
|
|
50
|
+
this.counters.set(counterKey, counter, windowStart + seconds);
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
/** The attempts counted so far for a key in the current window of `window`; for assertions. */
|
|
55
|
+
countOf(trackerKey, window) {
|
|
56
|
+
const seconds = RATE_LIMIT_WINDOWS.find((entry) => entry.key === window).seconds;
|
|
57
|
+
const windowStart = Math.floor(Math.floor(this.now() / 1000) / seconds) * seconds;
|
|
58
|
+
return this.counters.get(joinKeyFields(trackerKey, window, String(windowStart)))?.count ?? 0;
|
|
59
|
+
}
|
|
60
|
+
/** Forgets every counter. */
|
|
61
|
+
reset() {
|
|
62
|
+
this.counters.clear();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { LambderSessionRecord, LambderSessionStore } from "../shared/contracts/LambderSessionStore.js";
|
|
2
|
+
/**
|
|
3
|
+
* Session records held in memory: a Map in place of the DynamoDB table. For
|
|
4
|
+
* tests and for the mock runtime, where the session model (tokens, expiry,
|
|
5
|
+
* sliding writes, dataRefresh) runs as it does in production over a store
|
|
6
|
+
* that lives for the life of the process or page.
|
|
7
|
+
*
|
|
8
|
+
* Records are copied on the way in and out through JSON, the way a real store
|
|
9
|
+
* serializes them, so a caller mutating a fetched record changes nothing until
|
|
10
|
+
* it is put back, and data DynamoDB would reject is rejected here too. A
|
|
11
|
+
* record also leaves at its own expiresAt, the way the table's TTL removes
|
|
12
|
+
* it, so a long-lived process does not hold every session it ever issued.
|
|
13
|
+
*/
|
|
14
|
+
export declare class LambderMemorySessionStore<SessionData = unknown> implements LambderSessionStore<SessionData> {
|
|
15
|
+
/** Nothing here outlives the process. */
|
|
16
|
+
readonly isMemoryOnly = true;
|
|
17
|
+
private readonly records;
|
|
18
|
+
/**
|
|
19
|
+
* `now` is injectable so a test can move past an expiry without waiting.
|
|
20
|
+
* `maxEntries` caps how many live sessions are held; at the ceiling the
|
|
21
|
+
* soonest to expire is evicted, and an evicted session is a logout for
|
|
22
|
+
* whoever held it.
|
|
23
|
+
*/
|
|
24
|
+
constructor(options?: {
|
|
25
|
+
now?: () => number;
|
|
26
|
+
maxEntries?: number;
|
|
27
|
+
});
|
|
28
|
+
private static keyOf;
|
|
29
|
+
/**
|
|
30
|
+
* A record the way a real store hands it back: through JSON, so an
|
|
31
|
+
* undefined field is gone and a cyclic value throws here exactly as it
|
|
32
|
+
* throws on the way into DynamoDB. structuredClone did neither, which made
|
|
33
|
+
* the memory store the more forgiving of the two and let a test pass over
|
|
34
|
+
* data the production store would reject.
|
|
35
|
+
*/
|
|
36
|
+
private static storedCopy;
|
|
37
|
+
get(sessionKeyHash: string, secretHash: string): Promise<LambderSessionRecord<SessionData> | null>;
|
|
38
|
+
put(record: LambderSessionRecord<SessionData>): Promise<void>;
|
|
39
|
+
delete(sessionKeyHash: string, secretHash: string): Promise<void>;
|
|
40
|
+
listSecretHashes(sessionKeyHash: string): Promise<string[]>;
|
|
41
|
+
markDataExpired(sessionKeyHash: string, secretHash: string, at: number): Promise<void>;
|
|
42
|
+
/** Every live record held, for assertions. */
|
|
43
|
+
list(): LambderSessionRecord<SessionData>[];
|
|
44
|
+
/** Number of live records held. */
|
|
45
|
+
get size(): number;
|
|
46
|
+
/** Forgets every record: everyone is signed out. */
|
|
47
|
+
reset(): void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { LambderExpiringMap } from "../shared/util/LambderExpiringMap.js";
|
|
2
|
+
import { joinKeyFields } from "../shared/util/LambderKeyFields.js";
|
|
3
|
+
/**
|
|
4
|
+
* Session records held in memory: a Map in place of the DynamoDB table. For
|
|
5
|
+
* tests and for the mock runtime, where the session model (tokens, expiry,
|
|
6
|
+
* sliding writes, dataRefresh) runs as it does in production over a store
|
|
7
|
+
* that lives for the life of the process or page.
|
|
8
|
+
*
|
|
9
|
+
* Records are copied on the way in and out through JSON, the way a real store
|
|
10
|
+
* serializes them, so a caller mutating a fetched record changes nothing until
|
|
11
|
+
* it is put back, and data DynamoDB would reject is rejected here too. A
|
|
12
|
+
* record also leaves at its own expiresAt, the way the table's TTL removes
|
|
13
|
+
* it, so a long-lived process does not hold every session it ever issued.
|
|
14
|
+
*/
|
|
15
|
+
export class LambderMemorySessionStore {
|
|
16
|
+
/** Nothing here outlives the process. */
|
|
17
|
+
isMemoryOnly = true;
|
|
18
|
+
records;
|
|
19
|
+
/**
|
|
20
|
+
* `now` is injectable so a test can move past an expiry without waiting.
|
|
21
|
+
* `maxEntries` caps how many live sessions are held; at the ceiling the
|
|
22
|
+
* soonest to expire is evicted, and an evicted session is a logout for
|
|
23
|
+
* whoever held it.
|
|
24
|
+
*/
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this.records = new LambderExpiringMap(options);
|
|
27
|
+
}
|
|
28
|
+
static keyOf(sessionKeyHash, secretHash) {
|
|
29
|
+
// Escaped rather than joined plainly: both halves are hex today, but a
|
|
30
|
+
// custom LambderSessionCrypto writes whatever it likes into them, and
|
|
31
|
+
// two records sharing one key would be one session silently.
|
|
32
|
+
return joinKeyFields(sessionKeyHash, secretHash);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A record the way a real store hands it back: through JSON, so an
|
|
36
|
+
* undefined field is gone and a cyclic value throws here exactly as it
|
|
37
|
+
* throws on the way into DynamoDB. structuredClone did neither, which made
|
|
38
|
+
* the memory store the more forgiving of the two and let a test pass over
|
|
39
|
+
* data the production store would reject.
|
|
40
|
+
*/
|
|
41
|
+
static storedCopy(record) {
|
|
42
|
+
return JSON.parse(JSON.stringify(record));
|
|
43
|
+
}
|
|
44
|
+
async get(sessionKeyHash, secretHash) {
|
|
45
|
+
const record = this.records.get(LambderMemorySessionStore.keyOf(sessionKeyHash, secretHash));
|
|
46
|
+
return record ? LambderMemorySessionStore.storedCopy(record) : null;
|
|
47
|
+
}
|
|
48
|
+
async put(record) {
|
|
49
|
+
this.records.set(LambderMemorySessionStore.keyOf(record.sessionKeyHash, record.secretHash), LambderMemorySessionStore.storedCopy(record), record.expiresAt);
|
|
50
|
+
}
|
|
51
|
+
async delete(sessionKeyHash, secretHash) {
|
|
52
|
+
this.records.delete(LambderMemorySessionStore.keyOf(sessionKeyHash, secretHash));
|
|
53
|
+
}
|
|
54
|
+
async listSecretHashes(sessionKeyHash) {
|
|
55
|
+
return this.records.values()
|
|
56
|
+
.filter((record) => record.sessionKeyHash === sessionKeyHash)
|
|
57
|
+
.map((record) => record.secretHash);
|
|
58
|
+
}
|
|
59
|
+
async markDataExpired(sessionKeyHash, secretHash, at) {
|
|
60
|
+
const record = this.records.get(LambderMemorySessionStore.keyOf(sessionKeyHash, secretHash));
|
|
61
|
+
if (record)
|
|
62
|
+
record.dataExpiresAt = at;
|
|
63
|
+
}
|
|
64
|
+
/** Every live record held, for assertions. */
|
|
65
|
+
list() {
|
|
66
|
+
return this.records.values().map((record) => LambderMemorySessionStore.storedCopy(record));
|
|
67
|
+
}
|
|
68
|
+
/** Number of live records held. */
|
|
69
|
+
get size() { return this.records.size; }
|
|
70
|
+
/** Forgets every record: everyone is signed out. */
|
|
71
|
+
reset() {
|
|
72
|
+
this.records.clear();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { S3Client, S3ClientConfig } from "@aws-sdk/client-s3";
|
|
2
|
-
import { type LambderFile, type LambderFileSource } from "../
|
|
2
|
+
import { type LambderFile, type LambderFileSource } from "../shared/contracts/LambderFileSource.js";
|
|
3
3
|
export type LambderS3FileSourceOptions = {
|
|
4
4
|
bucket: string;
|
|
5
5
|
/** Literal key prefix the relative path is appended to, so include the trailing slash: "web/v42/". Default: none. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { remoteStoreFile } from "../
|
|
1
|
+
import { remoteStoreFile } from "../shared/contracts/LambderFileSource.js";
|
|
2
2
|
/**
|
|
3
3
|
* Files from an S3 bucket, or any S3-compatible store such as Cloudflare
|
|
4
4
|
* R2 (pass its endpoint in clientConfig). Needs @aws-sdk/client-s3, an
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lambder",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.2",
|
|
4
|
+
"sideEffects": false,
|
|
4
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.",
|
|
5
6
|
"keywords": [
|
|
6
7
|
"lambda",
|
|
@@ -44,18 +45,19 @@
|
|
|
44
45
|
"types": "./dist/client.d.ts",
|
|
45
46
|
"default": "./dist/client.js"
|
|
46
47
|
},
|
|
47
|
-
"./
|
|
48
|
-
"types": "./dist/
|
|
49
|
-
"default": "./dist/
|
|
50
|
-
}
|
|
48
|
+
"./mock": {
|
|
49
|
+
"types": "./dist/mock.d.ts",
|
|
50
|
+
"default": "./dist/mock.js"
|
|
51
|
+
},
|
|
52
|
+
"./package.json": "./package.json"
|
|
51
53
|
},
|
|
52
54
|
"typesVersions": {
|
|
53
55
|
"*": {
|
|
54
56
|
"client": [
|
|
55
57
|
"./dist/client.d.ts"
|
|
56
58
|
],
|
|
57
|
-
"
|
|
58
|
-
"./dist/
|
|
59
|
+
"mock": [
|
|
60
|
+
"./dist/mock.d.ts"
|
|
59
61
|
]
|
|
60
62
|
}
|
|
61
63
|
},
|
|
@@ -66,24 +68,29 @@
|
|
|
66
68
|
"crypto": false
|
|
67
69
|
},
|
|
68
70
|
"files": [
|
|
69
|
-
"dist"
|
|
71
|
+
"dist",
|
|
72
|
+
"CHANGELOG.md"
|
|
70
73
|
],
|
|
71
74
|
"engines": {
|
|
72
|
-
"node": ">=
|
|
75
|
+
"node": ">=20"
|
|
73
76
|
},
|
|
74
77
|
"scripts": {
|
|
75
78
|
"typecheck": "tsc -p tsconfig.tests.json",
|
|
76
|
-
"test": "npm run typecheck && vitest run",
|
|
79
|
+
"test": "npm run typecheck && npm run lint && npm run build && vitest run",
|
|
77
80
|
"test:watch": "vitest",
|
|
78
|
-
"build": "tsc",
|
|
79
|
-
"lint": "eslint . --ext .ts,.tsx
|
|
81
|
+
"build": "rm -rf dist && tsc",
|
|
82
|
+
"lint": "eslint . --ext .ts,.tsx",
|
|
83
|
+
"lint:fix": "eslint . --ext .ts,.tsx --fix",
|
|
84
|
+
"prepublishOnly": "npm test"
|
|
80
85
|
},
|
|
81
86
|
"dependencies": {
|
|
87
|
+
"@types/aws-lambda": "^8.10.136",
|
|
82
88
|
"cookie": "^1.0.2",
|
|
83
89
|
"js-cookie": "^3.0.5",
|
|
84
90
|
"lru-cache": "^11.5.2",
|
|
85
91
|
"mime-types": "^2.1.35",
|
|
86
|
-
"path-to-regexp": "^6.2.1"
|
|
92
|
+
"path-to-regexp": "^6.2.1",
|
|
93
|
+
"tough-cookie": "^6.0.2"
|
|
87
94
|
},
|
|
88
95
|
"peerDependencies": {
|
|
89
96
|
"@aws-sdk/client-dynamodb": "^3.574.0",
|
|
@@ -108,9 +115,6 @@
|
|
|
108
115
|
},
|
|
109
116
|
"msw": {
|
|
110
117
|
"optional": true
|
|
111
|
-
},
|
|
112
|
-
"zod": {
|
|
113
|
-
"optional": true
|
|
114
118
|
}
|
|
115
119
|
},
|
|
116
120
|
"devDependencies": {
|
|
@@ -118,16 +122,14 @@
|
|
|
118
122
|
"@aws-sdk/client-lambda": "^3.1131.0",
|
|
119
123
|
"@aws-sdk/client-s3": "^3.1127.0",
|
|
120
124
|
"@aws-sdk/lib-dynamodb": "^3.913.0",
|
|
121
|
-
"@types/aws-lambda": "^8.10.136",
|
|
122
|
-
"@types/cookie": "^0.6.0",
|
|
123
125
|
"@types/js-cookie": "^3.0.6",
|
|
124
126
|
"@types/mime-types": "^2.1.4",
|
|
125
127
|
"@types/node": "^24.9.0",
|
|
126
|
-
"@types/path-to-regexp": "^1.7.0",
|
|
127
128
|
"@typescript-eslint/eslint-plugin": "^8.70.0",
|
|
128
129
|
"@typescript-eslint/parser": "^8.70.0",
|
|
129
130
|
"aws-sdk-client-mock": "^4.1.0",
|
|
130
131
|
"eslint": "^8.57.0",
|
|
132
|
+
"msw": "^2.15.0",
|
|
131
133
|
"typescript": "^6.0.3",
|
|
132
134
|
"vitest": "^5.0.0",
|
|
133
135
|
"zod": "^4.6.2"
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { ApiContractShape } from '../shared/LambderApiContract.js';
|
|
2
|
-
type RequestHandler = any;
|
|
3
|
-
/** The parts of the msw module LambderMSW uses: `import { http, HttpResponse } from "msw"`. */
|
|
4
|
-
export type LambderMswModule = {
|
|
5
|
-
http: {
|
|
6
|
-
post: (path: string, resolver: (info: {
|
|
7
|
-
request: Request;
|
|
8
|
-
}) => any) => any;
|
|
9
|
-
};
|
|
10
|
-
HttpResponse: {
|
|
11
|
-
json: (body: any, init?: {
|
|
12
|
-
status?: number;
|
|
13
|
-
}) => any;
|
|
14
|
-
};
|
|
15
|
-
};
|
|
16
|
-
type MockApiOptions = {
|
|
17
|
-
versionExpired?: boolean;
|
|
18
|
-
sessionExpired?: boolean;
|
|
19
|
-
notAuthorized?: boolean;
|
|
20
|
-
message?: any;
|
|
21
|
-
errorMessage?: any;
|
|
22
|
-
logList?: any[];
|
|
23
|
-
delay?: number;
|
|
24
|
-
};
|
|
25
|
-
export default class LambderMSW<TContract extends ApiContractShape = any> {
|
|
26
|
-
private apiPath;
|
|
27
|
-
private apiVersion?;
|
|
28
|
-
private http;
|
|
29
|
-
private HttpResponse;
|
|
30
|
-
constructor({ apiPath, apiVersion, msw, }: {
|
|
31
|
-
apiPath: string;
|
|
32
|
-
apiVersion?: string;
|
|
33
|
-
/** Pass the msw module: `import * as msw from "msw"` (ESM-safe; no hidden require). */
|
|
34
|
-
msw: LambderMswModule;
|
|
35
|
-
});
|
|
36
|
-
/**
|
|
37
|
-
* Mock an API endpoint with MSW
|
|
38
|
-
* @param apiName - The name of the API to mock
|
|
39
|
-
* @param handler - Function that returns the mock payload
|
|
40
|
-
* @param options - Additional response options (session expired, version expired, etc.)
|
|
41
|
-
*
|
|
42
|
-
* Note: TypeScript will check that the return type is assignable to the output type,
|
|
43
|
-
* but due to structural typing, extra properties are allowed. Enable strict checks
|
|
44
|
-
* in your tsconfig.json with "noUncheckedIndexedAccess" and "exactOptionalPropertyTypes"
|
|
45
|
-
* for better type safety.
|
|
46
|
-
*/
|
|
47
|
-
mockApi<TApiName extends keyof TContract>(apiName: TApiName, handler: (payload?: TContract[TApiName]['input']) => Promise<TContract[TApiName]['output']> | TContract[TApiName]['output'], options?: MockApiOptions): RequestHandler;
|
|
48
|
-
/**
|
|
49
|
-
* Mock an API endpoint that returns a session expired error
|
|
50
|
-
*/
|
|
51
|
-
mockSessionExpired<TApiName extends keyof TContract>(apiName: TApiName): RequestHandler;
|
|
52
|
-
/**
|
|
53
|
-
* Mock an API endpoint that returns a version expired error
|
|
54
|
-
*/
|
|
55
|
-
mockVersionExpired<TApiName extends keyof TContract>(apiName: TApiName): RequestHandler;
|
|
56
|
-
/**
|
|
57
|
-
* Mock an API endpoint that returns a not authorized error
|
|
58
|
-
*/
|
|
59
|
-
mockNotAuthorized<TApiName extends keyof TContract>(apiName: TApiName): RequestHandler;
|
|
60
|
-
/**
|
|
61
|
-
* Mock an API endpoint that returns an error message
|
|
62
|
-
*/
|
|
63
|
-
mockError<TApiName extends keyof TContract>(apiName: TApiName, errorMessage: string): RequestHandler;
|
|
64
|
-
/**
|
|
65
|
-
* Mock an API endpoint with a custom message
|
|
66
|
-
*/
|
|
67
|
-
mockWithMessage<TApiName extends keyof TContract>(apiName: TApiName, handler: (payload?: TContract[TApiName]['input']) => Promise<TContract[TApiName]['output']> | TContract[TApiName]['output'], message: any): RequestHandler;
|
|
68
|
-
}
|
|
69
|
-
export {};
|