lambder 14.1.1 → 15.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 +429 -0
- package/README.md +14 -17
- package/dist/api/LambderApiDeclarations.d.ts +11 -5
- package/dist/api/LambderApiDefinition.d.ts +2 -1
- package/dist/api/LambderApiEnvelope.d.ts +2 -2
- package/dist/api/LambderApiEnvelope.js +8 -2
- package/dist/api/LambderApiGuards.d.ts +44 -5
- package/dist/api/LambderApiGuards.js +27 -0
- package/dist/api/LambderApiIdempotency.d.ts +14 -7
- package/dist/api/LambderApiIdempotency.js +39 -27
- package/dist/api/LambderApiPipeline.d.ts +25 -11
- package/dist/api/LambderApiPipeline.js +34 -17
- package/dist/api/LambderApiRateLimits.d.ts +28 -21
- package/dist/api/LambderApiRateLimits.js +55 -35
- package/dist/api/LambderApiRefusals.d.ts +10 -4
- package/dist/api/LambderApiRefusals.js +31 -5
- package/dist/api/LambderApiRequest.d.ts +21 -1
- package/dist/api/LambderApiRequest.js +31 -2
- package/dist/build/checkApiRefusals.d.ts +8 -3
- package/dist/build/checkApiRefusals.js +80 -29
- package/dist/build/contractPrinterProcess.d.ts +11 -0
- package/dist/build/contractPrinterProcess.js +17 -0
- package/dist/build/generateApiFiles.d.ts +26 -5
- package/dist/build/generateApiFiles.js +68 -7
- package/dist/build/writeApiSchemas.d.ts +109 -0
- package/dist/build/writeApiSchemas.js +308 -0
- package/dist/build.d.ts +3 -1
- package/dist/build.js +2 -0
- package/dist/client/LambderCaller.d.ts +25 -11
- package/dist/client/LambderCaller.js +57 -49
- package/dist/client/LambderUploadRunner.d.ts +55 -27
- package/dist/client/LambderUploadRunner.js +89 -34
- package/dist/client.d.ts +6 -6
- package/dist/client.js +2 -2
- package/dist/core/Lambder.d.ts +31 -21
- package/dist/core/Lambder.js +79 -23
- package/dist/core/LambderCallSummary.d.ts +1 -1
- package/dist/core/LambderContext.d.ts +59 -7
- package/dist/core/LambderContext.js +31 -15
- package/dist/core/LambderCrashHandling.d.ts +5 -0
- package/dist/core/LambderCrashHandling.js +11 -1
- package/dist/core/LambderCreateOptions.d.ts +28 -17
- package/dist/core/LambderCreateOptions.js +14 -7
- package/dist/core/LambderResolver.d.ts +3 -2
- package/dist/core/LambderResponseBuilder.d.ts +20 -9
- package/dist/core/LambderResponseBuilder.js +39 -8
- package/dist/core/LambderTemplatingEngine.d.ts +24 -6
- package/dist/core/LambderTemplatingEngine.js +23 -3
- package/dist/index.d.ts +15 -12
- package/dist/index.js +7 -5
- package/dist/invoke/LambderInvokeCaller.d.ts +25 -4
- package/dist/invoke/LambderInvokeCaller.js +47 -9
- package/dist/invoke/LambderInvokeOutcome.d.ts +7 -6
- package/dist/mock/LambderMockAdapterDoor.d.ts +40 -0
- package/dist/mock/LambderMockAdapterDoor.js +10 -0
- package/dist/mock/LambderMockApiSchemas.d.ts +28 -0
- package/dist/mock/LambderMockApiSchemas.js +150 -0
- package/dist/mock/LambderMockApp.d.ts +56 -48
- package/dist/mock/LambderMockApp.js +107 -62
- package/dist/mock/LambderMockBrowserCookies.js +18 -5
- package/dist/mock/LambderMockCreateOptions.d.ts +51 -4
- package/dist/mock/LambderMockFailureInjector.d.ts +12 -3
- package/dist/mock/LambderMockFailureInjector.js +12 -8
- package/dist/mock/LambderMockTypes.d.ts +8 -4
- package/dist/mock/lambderMockInvokeTransport.js +2 -9
- package/dist/mock/lambderMockMswHandler.d.ts +19 -13
- package/dist/mock/lambderMockMswHandler.js +25 -22
- package/dist/mock.d.ts +1 -0
- package/dist/secrets/LambderOneShotSecrets.d.ts +1 -1
- package/dist/secrets/LambderOneShotSecrets.js +6 -36
- package/dist/secrets/LambderPasswordHasher.d.ts +56 -0
- package/dist/secrets/LambderPasswordHasher.js +168 -0
- package/dist/session/LambderSessionController.d.ts +37 -4
- package/dist/session/LambderSessionController.js +36 -6
- package/dist/session/LambderSessionManager.d.ts +33 -3
- package/dist/session/LambderSessionManager.js +55 -16
- package/dist/shared/LambderHtml.d.ts +23 -4
- package/dist/shared/LambderHtml.js +17 -5
- package/dist/shared/LambderI18n.d.ts +106 -16
- package/dist/shared/LambderI18n.js +259 -74
- package/dist/shared/contracts/LambderCache.d.ts +14 -1
- package/dist/shared/contracts/LambderRateLimiter.d.ts +5 -6
- package/dist/shared/transport/LambderApiTransport.d.ts +3 -2
- package/dist/shared/util/LambderBackoffTimer.d.ts +24 -13
- package/dist/shared/util/LambderBackoffTimer.js +40 -12
- package/dist/shared/util/LambderCallAbort.d.ts +22 -6
- package/dist/shared/util/LambderCallAbort.js +37 -9
- package/dist/shared/util/LambderErrorReportMark.d.ts +4 -0
- package/dist/shared/util/LambderErrorReportMark.js +22 -0
- package/dist/shared/util/LambderKeyFieldDigest.d.ts +46 -0
- package/dist/shared/util/LambderKeyFieldDigest.js +75 -0
- package/dist/shared/util/LambderSignedClaims.d.ts +19 -0
- package/dist/shared/util/LambderSignedClaims.js +54 -0
- package/dist/shared/util/LambderSwappableInstances.d.ts +29 -0
- package/dist/shared/util/LambderSwappableInstances.js +54 -0
- package/dist/shared/util/LambderTestingDoors.d.ts +6 -2
- package/dist/shared/util/LambderTestingDoors.js +6 -2
- package/dist/shared/util/LambderTextDigest.d.ts +5 -5
- package/dist/shared/util/LambderTextDigest.js +5 -5
- package/dist/shared/wire/LambderApiContract.d.ts +1 -1
- package/dist/shared/wire/LambderApiOptionEntries.d.ts +9 -1
- package/dist/shared/wire/LambderApiOptionEntries.js +15 -0
- package/dist/shared/wire/LambderApiOptionValues.d.ts +3 -2
- package/dist/shared/wire/LambderApiOutcome.d.ts +37 -19
- package/dist/shared/wire/LambderApiOutcome.js +1 -1
- package/dist/shared/wire/LambderApiRefusal.d.ts +39 -12
- package/dist/shared/wire/LambderApiRefusal.js +3 -3
- package/dist/shared/wire/LambderApiSchemaEntries.d.ts +21 -0
- package/dist/shared/wire/LambderApiSchemaEntries.js +18 -0
- package/dist/shared/wire/LambderCallOptions.d.ts +3 -3
- package/dist/shared/wire/LambderCallOutcome.d.ts +2 -1
- package/dist/shared/wire/LambderCallOutcome.js +3 -2
- package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +5 -3
- package/dist/shared/wire/LambderIdempotencyKeyScope.js +5 -3
- package/dist/shared/wire/LambderInvokeApiId.d.ts +3 -3
- package/dist/shared/wire/LambderInvokeApiId.js +3 -3
- package/dist/shared/wire/LambderOutcomeAssertions.d.ts +7 -6
- package/dist/shared/wire/LambderOutcomeAssertions.js +1 -1
- package/dist/stores/LambderCacheValues.d.ts +2 -5
- package/dist/stores/LambderDdbCache.d.ts +15 -9
- package/dist/stores/LambderDdbCache.js +25 -6
- package/dist/stores/LambderDdbIdempotencyStore.d.ts +6 -4
- package/dist/stores/LambderDdbIdempotencyStore.js +32 -52
- package/dist/stores/LambderDdbOneShotSecretStore.d.ts +11 -0
- package/dist/stores/LambderDdbOneShotSecretStore.js +17 -5
- package/dist/stores/LambderDdbSdk.d.ts +59 -29
- package/dist/stores/LambderDdbSdk.js +116 -44
- package/dist/stores/LambderDdbSessionStore.d.ts +26 -11
- package/dist/stores/LambderDdbSessionStore.js +133 -68
- package/dist/stores/LambderMemoryCache.d.ts +2 -2
- package/dist/stores/LambderS3FileSource.d.ts +3 -2
- package/dist/stores/LambderS3FileSource.js +11 -13
- package/dist/stores/LambderS3UploadBucket.d.ts +12 -0
- package/dist/stores/LambderS3UploadBucket.js +23 -0
- package/dist/stores/LambderStorageBackedCache.d.ts +2 -2
- package/dist/stores/LambderStoredText.d.ts +26 -0
- package/dist/stores/LambderStoredText.js +25 -0
- package/dist/testing/LambderConformanceRunner.d.ts +9 -2
- package/dist/testing/LambderConformanceRunner.js +9 -2
- package/dist/testing/LambderTestApp.d.ts +65 -7
- package/dist/testing/LambderTestApp.js +101 -6
- package/dist/testing/LambderTestVisitor.d.ts +43 -12
- package/dist/testing/LambderTestVisitor.js +35 -11
- package/dist/testing/bootLambdaPackage.d.ts +105 -0
- package/dist/testing/bootLambdaPackage.js +265 -0
- package/dist/testing/lambdaPackageBootProcess.d.ts +39 -0
- package/dist/testing/lambdaPackageBootProcess.js +130 -0
- package/dist/testing/lambderCacheConformance.d.ts +26 -0
- package/dist/testing/lambderCacheConformance.js +287 -0
- package/dist/testing/lambderCacheStorageConformance.d.ts +25 -0
- package/dist/testing/lambderCacheStorageConformance.js +159 -0
- package/dist/testing/lambderSessionStoreConformance.d.ts +3 -3
- package/dist/testing/lambderSessionStoreConformance.js +3 -3
- package/dist/testing.d.ts +7 -1
- package/dist/testing.js +5 -0
- package/package.json +1 -6
- package/dist/shared/util/boundKeyField.d.ts +0 -20
- package/dist/shared/util/boundKeyField.js +0 -34
|
@@ -10,6 +10,13 @@ import type { LambderSessionStore } from "../shared/contracts/LambderSessionStor
|
|
|
10
10
|
import { LAMBDER_BACKEND_SWAP, LAMBDER_CRASH_WATCH } from "../shared/util/LambderTestingDoors.js";
|
|
11
11
|
import type { LambderApiContractShape } from "../shared/wire/LambderApiContract.js";
|
|
12
12
|
import type { LambderCallSummary } from "../core/LambderCallSummary.js";
|
|
13
|
+
import { LambderMemoryCache } from "../stores/LambderMemoryCache.js";
|
|
14
|
+
import { LambderMemoryOneShotSecretStore } from "../stores/LambderMemoryOneShotSecretStore.js";
|
|
15
|
+
import { LambderMemoryUploadBucket } from "../stores/LambderMemoryUploadBucket.js";
|
|
16
|
+
import type { LambderDdbCache } from "../stores/LambderDdbCache.js";
|
|
17
|
+
import type { LambderDdbOneShotSecretStore } from "../stores/LambderDdbOneShotSecretStore.js";
|
|
18
|
+
import type { LambderS3UploadBucket } from "../stores/LambderS3UploadBucket.js";
|
|
19
|
+
import type { LambderMockApp } from "../mock/LambderMockApp.js";
|
|
13
20
|
import { LambderTestVisitor, type LambderTestVisitorArgs, type LambderTestVisitorOptions } from "./LambderTestVisitor.js";
|
|
14
21
|
/**
|
|
15
22
|
* What a test app may be told. Nothing is required: `lambderTestApp(lambder)`
|
|
@@ -17,7 +24,9 @@ import { LambderTestVisitor, type LambderTestVisitorArgs, type LambderTestVisito
|
|
|
17
24
|
*
|
|
18
25
|
* The stores sit where create() takes them (`session.store`,
|
|
19
26
|
* `rateLimits.limiter`, `idempotency.store`, `files`), naming only the part a
|
|
20
|
-
* test replaces; everything else the app configured there stays in force.
|
|
27
|
+
* test replaces; everything else the app configured there stays in force. One
|
|
28
|
+
* given for a subsystem the app was created without throws, naming the
|
|
29
|
+
* option.
|
|
21
30
|
*/
|
|
22
31
|
export type LambderTestAppOptions = {
|
|
23
32
|
/** The host visitors browse unless they name their own. Default: "localhost". An app that scopes its session cookie to a domain needs a host under it. */
|
|
@@ -44,6 +53,16 @@ export type LambderTestAppOptions = {
|
|
|
44
53
|
};
|
|
45
54
|
/** Default: the app's own source, which suits one that reads a local folder. Pass a LambderLocalFileSource over fixtures for an app whose production source is S3 or HTTP. */
|
|
46
55
|
files?: LambderFileSource;
|
|
56
|
+
/**
|
|
57
|
+
* The mock apps that answer the app's invoke callers, by the function
|
|
58
|
+
* name each caller was built with. A LambderInvokeCaller built without a
|
|
59
|
+
* transport is answered by the mock its function has here, through
|
|
60
|
+
* lambderMockInvokeTransport; with none, every call it makes fails,
|
|
61
|
+
* naming the function. Default: none.
|
|
62
|
+
*/
|
|
63
|
+
invokeMocks?: {
|
|
64
|
+
[functionName: string]: Pick<LambderMockApp<any>, "handleRequest">;
|
|
65
|
+
};
|
|
47
66
|
};
|
|
48
67
|
/**
|
|
49
68
|
* A Lambder instance as a test app takes it: any instance, read for its
|
|
@@ -72,6 +91,15 @@ export type LambderTestedInstance<TSessionData, TContract> = Pick<Lambder<any, a
|
|
|
72
91
|
* What the app reaches on its own (its database, a mailer) is the app's to
|
|
73
92
|
* replace.
|
|
74
93
|
*
|
|
94
|
+
* The Lambder classes an app builds itself, beside the instance, are put
|
|
95
|
+
* under test too, wherever they were built: each LambderDdbCache,
|
|
96
|
+
* LambderDdbOneShotSecretStore and LambderS3UploadBucket answers from a
|
|
97
|
+
* memory twin of its own (see memoryTwinOf), and each LambderInvokeCaller
|
|
98
|
+
* built without a transport from the mock app `invokeMocks` names for its
|
|
99
|
+
* function. Those classes register themselves as they are constructed, so
|
|
100
|
+
* one built before the test app and one built after (in a handler, say) are
|
|
101
|
+
* both reached.
|
|
102
|
+
*
|
|
75
103
|
* The sibling of LambderMockApp, which serves a contract from mock handlers:
|
|
76
104
|
* the same verbs (`signIn`, `signOut`, `expireSessionData`, `reset`) over the
|
|
77
105
|
* real handlers.
|
|
@@ -82,6 +110,8 @@ export type LambderTestedInstance<TSessionData, TContract> = Pick<Lambder<any, a
|
|
|
82
110
|
*
|
|
83
111
|
* One test app per instance: a second one puts its own stores under the same
|
|
84
112
|
* instance and takes over its crash watch, so the first stops seeing either.
|
|
113
|
+
* The app's own classes are the process's rather than an instance's, so the
|
|
114
|
+
* test app created last puts its twins under every one of them.
|
|
85
115
|
*/
|
|
86
116
|
export declare class LambderTestApp<TContract extends LambderApiContractShape = any, TSessionData = any> {
|
|
87
117
|
/** The instance's handler: what `event()` and every visitor call. */
|
|
@@ -98,6 +128,8 @@ export declare class LambderTestApp<TContract extends LambderApiContractShape =
|
|
|
98
128
|
private readonly wiring;
|
|
99
129
|
/** The memory stores this test app made itself, which reset() empties. One the test supplied is the test's: nothing here knows what else holds it. */
|
|
100
130
|
private readonly ownStores;
|
|
131
|
+
/** The twin under each of the app's own stores, by the store, for memoryTwinOf and reset. */
|
|
132
|
+
private readonly memoryTwins;
|
|
101
133
|
private visitorCount;
|
|
102
134
|
private resetCount;
|
|
103
135
|
private readonly crashList;
|
|
@@ -126,6 +158,31 @@ export declare class LambderTestApp<TContract extends LambderApiContractShape =
|
|
|
126
158
|
* have been handed, collected here instead of written to stdout.
|
|
127
159
|
*/
|
|
128
160
|
get callSummaries(): readonly LambderCallSummary[];
|
|
161
|
+
/**
|
|
162
|
+
* Fails every test that leaves a crash behind: installs, through the
|
|
163
|
+
* runner's own `afterEach`, a check that throws when `crashes` is not
|
|
164
|
+
* empty, listing each crash with its stack. The crashes it reports are
|
|
165
|
+
* forgotten, so the next test starts clean whether or not it resets. A
|
|
166
|
+
* test that provokes a crash on purpose asserts on `crashes` and ends
|
|
167
|
+
* with `reset()`.
|
|
168
|
+
*
|
|
169
|
+
* ```typescript
|
|
170
|
+
* const app = lambderTestApp(lambder);
|
|
171
|
+
* app.assertNoCrashesAfterEach(afterEach);
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
assertNoCrashesAfterEach(afterEach: (check: () => void) => unknown): void;
|
|
175
|
+
/**
|
|
176
|
+
* The memory twin under one of the app's own stores, to seed, inspect or
|
|
177
|
+
* drive: what the app's LambderDdbCache, LambderDdbOneShotSecretStore or
|
|
178
|
+
* LambderS3UploadBucket answers from under this test app. An upload
|
|
179
|
+
* test hands the bucket twin's handleStorageRequest the request a
|
|
180
|
+
* client would send to the ticket's URL. Throws for anything this test
|
|
181
|
+
* app put no twin under.
|
|
182
|
+
*/
|
|
183
|
+
memoryTwinOf(cache: LambderDdbCache): LambderMemoryCache;
|
|
184
|
+
memoryTwinOf(store: LambderDdbOneShotSecretStore): LambderMemoryOneShotSecretStore;
|
|
185
|
+
memoryTwinOf(bucket: LambderS3UploadBucket): LambderMemoryUploadBucket;
|
|
129
186
|
/** The session manager, for tests that inspect or manipulate sessions directly. Throws when the app has no sessions. */
|
|
130
187
|
get sessionManager(): LambderSessionManager<TSessionData>;
|
|
131
188
|
/**
|
|
@@ -140,8 +197,8 @@ export declare class LambderTestApp<TContract extends LambderApiContractShape =
|
|
|
140
197
|
* no login endpoint has to exist or be called.
|
|
141
198
|
*
|
|
142
199
|
* ```typescript
|
|
143
|
-
* const
|
|
144
|
-
* expect(await
|
|
200
|
+
* const owner = await app.signIn("user:ada", { userId: "ada", role: "owner" });
|
|
201
|
+
* expect(await owner.api("store.rename", { name })).toEqual({ ok: true });
|
|
145
202
|
* ```
|
|
146
203
|
*/
|
|
147
204
|
signIn<TProvidedGuards extends string = never>(sessionKey: string, data: TSessionData, ...[options]: LambderTestVisitorArgs<LambderTestVisitorOptions<TContract, TProvidedGuards> & {
|
|
@@ -168,10 +225,11 @@ export declare class LambderTestApp<TContract extends LambderApiContractShape =
|
|
|
168
225
|
event(event: unknown, context?: Partial<Context>): Promise<unknown>;
|
|
169
226
|
/**
|
|
170
227
|
* Rewinds what accumulated: sessions, rate-limit counters and replay
|
|
171
|
-
* records in the stores this test app made, the
|
|
172
|
-
*
|
|
173
|
-
* (each empties its jar the next
|
|
174
|
-
* app's own data (its database)
|
|
228
|
+
* records in the stores this test app made, the memory twins under the
|
|
229
|
+
* app's own stores, the crashes and call summaries it recorded, and the
|
|
230
|
+
* cookies of every visitor it created (each empties its jar the next
|
|
231
|
+
* time it is used). For a beforeEach. The app's own data (its database)
|
|
232
|
+
* is the app's to rewind, and so are the invoke mocks.
|
|
175
233
|
*/
|
|
176
234
|
reset(): void;
|
|
177
235
|
}
|
|
@@ -6,6 +6,11 @@ import { getAnswerHeader } from "../shared/wire/LambderAnswerHeaders.js";
|
|
|
6
6
|
import { LambderMemoryIdempotencyStore } from "../stores/LambderMemoryIdempotencyStore.js";
|
|
7
7
|
import { LambderMemoryRateLimiter } from "../stores/LambderMemoryRateLimiter.js";
|
|
8
8
|
import { LambderMemorySessionStore } from "../stores/LambderMemorySessionStore.js";
|
|
9
|
+
import { LambderMemoryCache } from "../stores/LambderMemoryCache.js";
|
|
10
|
+
import { LambderMemoryOneShotSecretStore } from "../stores/LambderMemoryOneShotSecretStore.js";
|
|
11
|
+
import { LambderMemoryUploadBucket } from "../stores/LambderMemoryUploadBucket.js";
|
|
12
|
+
import { lambderMockInvokeTransport } from "../mock/lambderMockInvokeTransport.js";
|
|
13
|
+
import { liveSwappableInstances, watchSwappableInstances } from "../shared/util/LambderSwappableInstances.js";
|
|
9
14
|
import { LambderTestVisitor } from "./LambderTestVisitor.js";
|
|
10
15
|
/**
|
|
11
16
|
* A real Lambder app under test: the instance an app already has, with memory
|
|
@@ -20,6 +25,15 @@ import { LambderTestVisitor } from "./LambderTestVisitor.js";
|
|
|
20
25
|
* What the app reaches on its own (its database, a mailer) is the app's to
|
|
21
26
|
* replace.
|
|
22
27
|
*
|
|
28
|
+
* The Lambder classes an app builds itself, beside the instance, are put
|
|
29
|
+
* under test too, wherever they were built: each LambderDdbCache,
|
|
30
|
+
* LambderDdbOneShotSecretStore and LambderS3UploadBucket answers from a
|
|
31
|
+
* memory twin of its own (see memoryTwinOf), and each LambderInvokeCaller
|
|
32
|
+
* built without a transport from the mock app `invokeMocks` names for its
|
|
33
|
+
* function. Those classes register themselves as they are constructed, so
|
|
34
|
+
* one built before the test app and one built after (in a handler, say) are
|
|
35
|
+
* both reached.
|
|
36
|
+
*
|
|
23
37
|
* The sibling of LambderMockApp, which serves a contract from mock handlers:
|
|
24
38
|
* the same verbs (`signIn`, `signOut`, `expireSessionData`, `reset`) over the
|
|
25
39
|
* real handlers.
|
|
@@ -30,6 +44,8 @@ import { LambderTestVisitor } from "./LambderTestVisitor.js";
|
|
|
30
44
|
*
|
|
31
45
|
* One test app per instance: a second one puts its own stores under the same
|
|
32
46
|
* instance and takes over its crash watch, so the first stops seeing either.
|
|
47
|
+
* The app's own classes are the process's rather than an instance's, so the
|
|
48
|
+
* test app created last puts its twins under every one of them.
|
|
33
49
|
*/
|
|
34
50
|
export class LambderTestApp {
|
|
35
51
|
/** The instance's handler: what `event()` and every visitor call. */
|
|
@@ -46,6 +62,8 @@ export class LambderTestApp {
|
|
|
46
62
|
wiring;
|
|
47
63
|
/** The memory stores this test app made itself, which reset() empties. One the test supplied is the test's: nothing here knows what else holds it. */
|
|
48
64
|
ownStores = [];
|
|
65
|
+
/** The twin under each of the app's own stores, by the store, for memoryTwinOf and reset. */
|
|
66
|
+
memoryTwins = new WeakMap();
|
|
49
67
|
visitorCount = 0;
|
|
50
68
|
resetCount = 0;
|
|
51
69
|
crashList = [];
|
|
@@ -74,9 +92,50 @@ export class LambderTestApp {
|
|
|
74
92
|
sessionStore, rateLimiter, idempotencyStore, fileSource: options.files,
|
|
75
93
|
callSummary: (summary) => { this.callSummaryList.push(summary); },
|
|
76
94
|
});
|
|
95
|
+
// A store given for a subsystem the app does not have would be put
|
|
96
|
+
// under nothing: the suite it was meant for would run over no store
|
|
97
|
+
// while reading as if it ran over that one.
|
|
98
|
+
if (options.session?.store && !swap.sessions) {
|
|
99
|
+
throw new Error("lambderTestApp: the session.store option was given, but the app was created without sessions, so nothing reads sessions to put a store under.");
|
|
100
|
+
}
|
|
101
|
+
if (options.rateLimits?.limiter && !swap.rateLimits) {
|
|
102
|
+
throw new Error("lambderTestApp: the rateLimits.limiter option was given, but the app was created without rate limits, so nothing counts calls to put a limiter under.");
|
|
103
|
+
}
|
|
104
|
+
if (options.idempotency?.store && !swap.idempotency) {
|
|
105
|
+
throw new Error("lambderTestApp: the idempotency.store option was given, but the app was created without idempotency, so nothing records answers to put a store under.");
|
|
106
|
+
}
|
|
77
107
|
if (options.files && !swap.files) {
|
|
78
108
|
throw new Error("lambderTestApp: the files option was given, but the app was created without one, so nothing reads files to put a source under.");
|
|
79
109
|
}
|
|
110
|
+
const invokeMocks = options.invokeMocks ?? {};
|
|
111
|
+
for (const [functionName, mockApp] of Object.entries(invokeMocks)) {
|
|
112
|
+
if (typeof mockApp?.handleRequest !== "function") {
|
|
113
|
+
throw new Error(`lambderTestApp: invokeMocks["${functionName}"] is not a LambderMockApp (what initLambderMock().create() returns).`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Every one of the app's own classes alive now, and every one
|
|
117
|
+
// constructed from now on, until another test app takes over.
|
|
118
|
+
const putTwinUnder = (swappable) => {
|
|
119
|
+
const kept = (twin) => { this.memoryTwins.set(swappable.instance, twin); return twin; };
|
|
120
|
+
const twins = {
|
|
121
|
+
cache: (cacheOptions) => kept(new LambderMemoryCache(cacheOptions)),
|
|
122
|
+
oneShotSecretStore: () => kept(new LambderMemoryOneShotSecretStore()),
|
|
123
|
+
uploadBucket: (bucketOptions) => kept(new LambderMemoryUploadBucket(bucketOptions)),
|
|
124
|
+
invokeTransport: ({ functionName, apiPath }) => {
|
|
125
|
+
const mockApp = Object.hasOwn(invokeMocks, functionName) ? invokeMocks[functionName] : undefined;
|
|
126
|
+
if (mockApp)
|
|
127
|
+
return lambderMockInvokeTransport(mockApp, { apiPath });
|
|
128
|
+
return async () => {
|
|
129
|
+
throw new Error(`lambderTestApp: no invoke mock answers the function "${functionName}", and under a test app an invoke caller never reaches AWS. ` +
|
|
130
|
+
`Pass invokeMocks: { "${functionName}": mockApp } to answer it from a LambderMockApp.`);
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
swappable.swapIn(twins);
|
|
135
|
+
};
|
|
136
|
+
for (const swappable of liveSwappableInstances())
|
|
137
|
+
putTwinUnder(swappable);
|
|
138
|
+
watchSwappableInstances(putTwinUnder);
|
|
80
139
|
lambder[LAMBDER_CRASH_WATCH]((error) => {
|
|
81
140
|
this.crashList.push(error);
|
|
82
141
|
const scope = this.crashScope.getStore();
|
|
@@ -131,6 +190,39 @@ export class LambderTestApp {
|
|
|
131
190
|
get callSummaries() {
|
|
132
191
|
return this.callSummaryList;
|
|
133
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Fails every test that leaves a crash behind: installs, through the
|
|
195
|
+
* runner's own `afterEach`, a check that throws when `crashes` is not
|
|
196
|
+
* empty, listing each crash with its stack. The crashes it reports are
|
|
197
|
+
* forgotten, so the next test starts clean whether or not it resets. A
|
|
198
|
+
* test that provokes a crash on purpose asserts on `crashes` and ends
|
|
199
|
+
* with `reset()`.
|
|
200
|
+
*
|
|
201
|
+
* ```typescript
|
|
202
|
+
* const app = lambderTestApp(lambder);
|
|
203
|
+
* app.assertNoCrashesAfterEach(afterEach);
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
assertNoCrashesAfterEach(afterEach) {
|
|
207
|
+
afterEach(() => {
|
|
208
|
+
if (this.crashList.length === 0)
|
|
209
|
+
return;
|
|
210
|
+
const crashes = this.crashList.splice(0);
|
|
211
|
+
const listed = crashes.map((crash, index) => {
|
|
212
|
+
const [first, ...frames] = (crash.stack ?? `${crash.name}: ${crash.message}`).split("\n");
|
|
213
|
+
return [`${index + 1}) ${first}`, ...frames.map((frame) => ` ${frame.trim()}`)].join("\n");
|
|
214
|
+
});
|
|
215
|
+
throw new Error(`The app crashed answering ${crashes.length === 1 ? "a request" : `${crashes.length} requests`} in this test:\n\n${listed.join("\n\n")}`);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
memoryTwinOf(instance) {
|
|
219
|
+
const twin = this.memoryTwins.get(instance);
|
|
220
|
+
if (!twin) {
|
|
221
|
+
throw new Error("lambderTestApp: memoryTwinOf() was handed something this test app put no memory twin under. It takes a LambderDdbCache, " +
|
|
222
|
+
"LambderDdbOneShotSecretStore or LambderS3UploadBucket, and the test app created last puts its own twins under every one.");
|
|
223
|
+
}
|
|
224
|
+
return twin;
|
|
225
|
+
}
|
|
134
226
|
/** The session manager, for tests that inspect or manipulate sessions directly. Throws when the app has no sessions. */
|
|
135
227
|
get sessionManager() {
|
|
136
228
|
return this.lambder.getSessionManager();
|
|
@@ -156,8 +248,8 @@ export class LambderTestApp {
|
|
|
156
248
|
* no login endpoint has to exist or be called.
|
|
157
249
|
*
|
|
158
250
|
* ```typescript
|
|
159
|
-
* const
|
|
160
|
-
* expect(await
|
|
251
|
+
* const owner = await app.signIn("user:ada", { userId: "ada", role: "owner" });
|
|
252
|
+
* expect(await owner.api("store.rename", { name })).toEqual({ ok: true });
|
|
161
253
|
* ```
|
|
162
254
|
*/
|
|
163
255
|
async signIn(sessionKey, data, ...[options]) {
|
|
@@ -193,14 +285,17 @@ export class LambderTestApp {
|
|
|
193
285
|
}
|
|
194
286
|
/**
|
|
195
287
|
* Rewinds what accumulated: sessions, rate-limit counters and replay
|
|
196
|
-
* records in the stores this test app made, the
|
|
197
|
-
*
|
|
198
|
-
* (each empties its jar the next
|
|
199
|
-
* app's own data (its database)
|
|
288
|
+
* records in the stores this test app made, the memory twins under the
|
|
289
|
+
* app's own stores, the crashes and call summaries it recorded, and the
|
|
290
|
+
* cookies of every visitor it created (each empties its jar the next
|
|
291
|
+
* time it is used). For a beforeEach. The app's own data (its database)
|
|
292
|
+
* is the app's to rewind, and so are the invoke mocks.
|
|
200
293
|
*/
|
|
201
294
|
reset() {
|
|
202
295
|
for (const store of this.ownStores)
|
|
203
296
|
store.reset();
|
|
297
|
+
for (const { instance } of liveSwappableInstances())
|
|
298
|
+
this.memoryTwins.get(instance)?.reset();
|
|
204
299
|
this.crashList.length = 0;
|
|
205
300
|
this.callSummaryList.length = 0;
|
|
206
301
|
this.resetCount += 1;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import LambderCaller, { type
|
|
1
|
+
import LambderCaller, { type LambderCallerMembers, type LambderCallOptions } from "../client/LambderCaller.js";
|
|
2
|
+
import { type LambderContractActionOf, type LambderContractGroupsOf, type LambderContractNamesInGroup } from "../shared/wire/LambderApiGroupCalls.js";
|
|
3
|
+
import type { LambderApiOutcome } from "../shared/wire/LambderApiOutcome.js";
|
|
2
4
|
import type { LambderHttpEventFormat } from "../core/LambderContext.js";
|
|
3
5
|
import type { LambderHandler } from "../core/LambderCreateOptions.js";
|
|
4
6
|
import { type LambderLambdaHttpResult } from "../invoke/LambderLambdaEvent.js";
|
|
5
7
|
import type { LambderCreatedSession } from "../session/LambderSessionManager.js";
|
|
6
8
|
import { LambderCookieJar } from "../shared/transport/LambderCookieJar.js";
|
|
7
|
-
import type { LambderApiContractShape } from "../shared/wire/LambderApiContract.js";
|
|
9
|
+
import type { LambderApiContractShape, LambderContractRefusalMessage } from "../shared/wire/LambderApiContract.js";
|
|
8
10
|
import type { LambderApiSignatureMap } from "../shared/wire/LambderApiSignatureMap.js";
|
|
9
|
-
import type { LambderGuardInputsProviderOption } from "../shared/wire/LambderCallOptions.js";
|
|
11
|
+
import type { LambderCallArgs, LambderContractOutputOf, LambderGuardInputsProviderOption } from "../shared/wire/LambderCallOptions.js";
|
|
10
12
|
/**
|
|
11
13
|
* How one visitor differs from the next. Everything is optional: a visitor
|
|
12
14
|
* nobody described is a stranger on the app's host, at an address of its own.
|
|
@@ -73,12 +75,35 @@ export type LambderTestVisitorWiring<TSessionData> = {
|
|
|
73
75
|
export type LambderTestVisitorArgs<TOptions, TProvidedGuards extends string> = [
|
|
74
76
|
TProvidedGuards
|
|
75
77
|
] extends [never] ? [options?: TOptions] : [options: TOptions];
|
|
78
|
+
/**
|
|
79
|
+
* A visitor's `api`: the endpoint's output, typed as the output alone, or a
|
|
80
|
+
* thrown Error saying what came back instead. A test step that should
|
|
81
|
+
* succeed reads its output on the next line; a refused or crashed step stops
|
|
82
|
+
* the test there, rather than as an undefined read further on.
|
|
83
|
+
*/
|
|
84
|
+
export type LambderTestVisitorApi<TContract extends LambderApiContractShape, TProvidedGuards extends string> = <TApiName extends keyof TContract & string = string>(apiName: TApiName, ...rest: LambderCallArgs<TContract, TApiName, TProvidedGuards, LambderCallOptions<LambderContractRefusalMessage<TContract, TApiName>>>) => Promise<LambderContractOutputOf<TContract, TApiName>>;
|
|
85
|
+
/**
|
|
86
|
+
* One endpoint as a visitor hands it out on its group: called, it is the
|
|
87
|
+
* visitor's `api` for that endpoint (the output, or a thrown Error saying what
|
|
88
|
+
* came back instead); `.outcome` is its `apiOutcome`, which never throws.
|
|
89
|
+
*/
|
|
90
|
+
export type LambderTestVisitorEndpoint<TContract, TName extends keyof TContract & string, TProvidedGuards extends string> = {
|
|
91
|
+
(...args: LambderCallArgs<TContract, TName, TProvidedGuards, LambderCallOptions<LambderContractRefusalMessage<TContract, TName>>>): Promise<LambderContractOutputOf<TContract, TName>>;
|
|
92
|
+
outcome(...args: LambderCallArgs<TContract, TName, TProvidedGuards, LambderCallOptions<LambderContractRefusalMessage<TContract, TName>>>): Promise<LambderApiOutcome<LambderContractOutputOf<TContract, TName>, LambderContractRefusalMessage<TContract, TName>>>;
|
|
93
|
+
};
|
|
94
|
+
/** Every endpoint of a contract by group, as a visitor hands them out: `visitor.orders.place(input)`. */
|
|
95
|
+
export type LambderTestVisitorGroupCalls<TContract, TProvidedGuards extends string> = {
|
|
96
|
+
readonly [TGroup in LambderContractGroupsOf<TContract>]: {
|
|
97
|
+
readonly [TName in LambderContractNamesInGroup<TContract, TGroup> as LambderContractActionOf<TName>]: LambderTestVisitorEndpoint<TContract, TName, TProvidedGuards>;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
76
100
|
/**
|
|
77
101
|
* One simulated browser in front of a real Lambder app: a cookie jar, an
|
|
78
|
-
* address and a host of its own, and two ways in. `api` / `apiOutcome`
|
|
79
|
-
* typed LambderCaller
|
|
80
|
-
* runs the whole pipeline (rate limits, session, replay, guards,
|
|
81
|
-
* the way a browser's would
|
|
102
|
+
* address and a host of its own, and two ways in. `api` / `apiOutcome` go
|
|
103
|
+
* through a typed LambderCaller, over the real handler in this process, so a
|
|
104
|
+
* call runs the whole pipeline (rate limits, session, replay, guards,
|
|
105
|
+
* validation) the way a browser's would; `api` throws where the caller's
|
|
106
|
+
* would hand back undefined. `request` is everything else a browser sends:
|
|
82
107
|
* pages, redirects, session routes, file requests. Both carry the same jar,
|
|
83
108
|
* so a session started through one is the session the other presents.
|
|
84
109
|
*
|
|
@@ -95,8 +120,14 @@ declare class LambderTestVisitorCore<TContract extends LambderApiContractShape =
|
|
|
95
120
|
* An answer's logList is not printed; it is on the outcome.
|
|
96
121
|
*/
|
|
97
122
|
readonly caller: LambderCaller<TContract, TProvidedGuards>;
|
|
98
|
-
/**
|
|
99
|
-
|
|
123
|
+
/**
|
|
124
|
+
* The endpoint's output; throws on every failure, with an Error that
|
|
125
|
+
* names the endpoint and says what came back the way assertApiSuccess
|
|
126
|
+
* does: the reason, the status, the refusal's code and message, and for
|
|
127
|
+
* a crash the error the app threw, which is also the end of the Error's
|
|
128
|
+
* cause chain. `apiOutcome` is for a test that expects a failure.
|
|
129
|
+
*/
|
|
130
|
+
readonly api: LambderTestVisitorApi<TContract, TProvidedGuards>;
|
|
100
131
|
/**
|
|
101
132
|
* The full outcome, never throwing: LambderCaller.apiOutcome, through
|
|
102
133
|
* this visitor. Pair it with assertApiSuccess / assertApiFailure.
|
|
@@ -150,10 +181,10 @@ declare class LambderTestVisitorCore<TContract extends LambderApiContractShape =
|
|
|
150
181
|
}
|
|
151
182
|
/**
|
|
152
183
|
* One simulated browser in front of a real Lambder app, with the app's
|
|
153
|
-
* endpoints by group
|
|
154
|
-
* `visitor.orders.place.outcome(input)
|
|
184
|
+
* endpoints by group: `visitor.orders.place(input)` for the output (thrown on
|
|
185
|
+
* a failure), `visitor.orders.place.outcome(input)` for the outcome.
|
|
155
186
|
*/
|
|
156
|
-
export type LambderTestVisitor<TContract extends LambderApiContractShape = any, TSessionData = any, TProvidedGuards extends string = never> = LambderTestVisitorCore<TContract, TSessionData, TProvidedGuards> &
|
|
187
|
+
export type LambderTestVisitor<TContract extends LambderApiContractShape = any, TSessionData = any, TProvidedGuards extends string = never> = LambderTestVisitorCore<TContract, TSessionData, TProvidedGuards> & LambderTestVisitorGroupCalls<TContract, TProvidedGuards>;
|
|
157
188
|
export declare const LambderTestVisitor: {
|
|
158
189
|
new <TContract extends LambderApiContractShape = any, TSessionData = any, TProvidedGuards extends string = never>(...args: ConstructorParameters<typeof LambderTestVisitorCore<TContract, TSessionData, TProvidedGuards>>): LambderTestVisitor<TContract, TSessionData, TProvidedGuards>;
|
|
159
190
|
readonly prototype: LambderTestVisitorCore<any, any, any>;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import LambderCaller from "../client/LambderCaller.js";
|
|
2
|
-
import { withApiGroupCalls } from "../shared/wire/LambderApiGroupCalls.js";
|
|
2
|
+
import { withApiGroupCalls, } from "../shared/wire/LambderApiGroupCalls.js";
|
|
3
3
|
import { lambderHandlerTransport } from "../invoke/lambderHandlerTransport.js";
|
|
4
4
|
import { decodeLambdaHttpResult, localLambdaContext, synthesizeLambdaHttpEvent, } from "../invoke/LambderLambdaEvent.js";
|
|
5
5
|
import { LambderCookieJar } from "../shared/transport/LambderCookieJar.js";
|
|
6
6
|
import { lambderCookieJarTransport } from "../shared/transport/lambderCookieJarTransport.js";
|
|
7
|
+
import { assertApiSuccess } from "../shared/wire/LambderOutcomeAssertions.js";
|
|
7
8
|
import { DEFAULT_MAX_RESTORED_PAYLOAD_BYTES } from "../shared/wire/LambderRequestPayload.js";
|
|
8
9
|
/**
|
|
9
10
|
* One simulated browser in front of a real Lambder app: a cookie jar, an
|
|
10
|
-
* address and a host of its own, and two ways in. `api` / `apiOutcome`
|
|
11
|
-
* typed LambderCaller
|
|
12
|
-
* runs the whole pipeline (rate limits, session, replay, guards,
|
|
13
|
-
* the way a browser's would
|
|
11
|
+
* address and a host of its own, and two ways in. `api` / `apiOutcome` go
|
|
12
|
+
* through a typed LambderCaller, over the real handler in this process, so a
|
|
13
|
+
* call runs the whole pipeline (rate limits, session, replay, guards,
|
|
14
|
+
* validation) the way a browser's would; `api` throws where the caller's
|
|
15
|
+
* would hand back undefined. `request` is everything else a browser sends:
|
|
14
16
|
* pages, redirects, session routes, file requests. Both carry the same jar,
|
|
15
17
|
* so a session started through one is the session the other presents.
|
|
16
18
|
*
|
|
@@ -26,7 +28,13 @@ class LambderTestVisitorCore {
|
|
|
26
28
|
* An answer's logList is not printed; it is on the outcome.
|
|
27
29
|
*/
|
|
28
30
|
caller;
|
|
29
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The endpoint's output; throws on every failure, with an Error that
|
|
33
|
+
* names the endpoint and says what came back the way assertApiSuccess
|
|
34
|
+
* does: the reason, the status, the refusal's code and message, and for
|
|
35
|
+
* a crash the error the app threw, which is also the end of the Error's
|
|
36
|
+
* cause chain. `apiOutcome` is for a test that expects a failure.
|
|
37
|
+
*/
|
|
30
38
|
api;
|
|
31
39
|
/**
|
|
32
40
|
* The full outcome, never throwing: LambderCaller.apiOutcome, through
|
|
@@ -77,12 +85,28 @@ class LambderTestVisitorCore {
|
|
|
77
85
|
outcome.error.cause = crash;
|
|
78
86
|
return outcome;
|
|
79
87
|
};
|
|
80
|
-
|
|
88
|
+
// The plain call reads as the output, so a failure has nowhere to go
|
|
89
|
+
// but a throw. assertApiSuccess says what came back, as a test's own
|
|
90
|
+
// assertion would; the endpoint's name says which step of a setup it
|
|
91
|
+
// was.
|
|
92
|
+
const api = async (apiName, ...callArgs) => {
|
|
93
|
+
const outcome = await apiOutcome(apiName, ...callArgs);
|
|
94
|
+
try {
|
|
95
|
+
assertApiSuccess(outcome);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
const described = err;
|
|
99
|
+
throw new Error(`${apiName}: ${described.message}`, { cause: described.cause });
|
|
100
|
+
}
|
|
101
|
+
return outcome.payload;
|
|
102
|
+
};
|
|
103
|
+
this.api = api;
|
|
81
104
|
this.apiOutcome = apiOutcome;
|
|
82
|
-
// Each group of the contract
|
|
83
|
-
//
|
|
84
|
-
// cause as
|
|
85
|
-
|
|
105
|
+
// Each group of the contract through this visitor's own two calls:
|
|
106
|
+
// visitor.orders.place(input) throws as visitor.api does, and
|
|
107
|
+
// visitor.orders.place.outcome(input) carries the crash cause as
|
|
108
|
+
// visitor.apiOutcome does.
|
|
109
|
+
return withApiGroupCalls(this, (apiName, args) => api(apiName, ...args), (apiName, args) => apiOutcome(apiName, ...args));
|
|
86
110
|
}
|
|
87
111
|
/**
|
|
88
112
|
* This visitor's cookies, to inspect or clear; every call and request
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type LambderApiOutcome } from "../shared/wire/LambderApiOutcome.js";
|
|
2
|
+
/**
|
|
3
|
+
* One call the booted handler receives: a Lambder API call, which becomes
|
|
4
|
+
* the event a gateway (or, with `invoke`, another function) would deliver
|
|
5
|
+
* for it, or an event exactly as given (a schedule, a queue message, an
|
|
6
|
+
* HTTP request written out by hand).
|
|
7
|
+
*/
|
|
8
|
+
export type LambderPackageBootCall = {
|
|
9
|
+
/** The endpoint, as `group.action`. */
|
|
10
|
+
api: string;
|
|
11
|
+
/** The call's input. Default: none. */
|
|
12
|
+
payload?: unknown;
|
|
13
|
+
/** Headers the event carries beside the ones it owns. */
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
} | {
|
|
16
|
+
/** The event, handed to the handler as it is. */
|
|
17
|
+
event: unknown;
|
|
18
|
+
/** How the result names this call. Default: "event N", N its index. */
|
|
19
|
+
name?: string;
|
|
20
|
+
};
|
|
21
|
+
export type LambderPackageBootOptions = {
|
|
22
|
+
/** The assembled deployment package: the directory that becomes the zip, with its production dependencies installed, if it has any. */
|
|
23
|
+
packageDir: string;
|
|
24
|
+
/** The handler as the function's configuration names it: the module's path in the package without its extension, a dot, and the export. Default: "index.handler". The module is the first of `.js`, `.mjs` and `.cjs` that exists, as on Lambda. */
|
|
25
|
+
handler?: string;
|
|
26
|
+
/** What the handler receives, in order, in the one process, as a warm function receives it. Default: none, so the package is only imported. */
|
|
27
|
+
calls?: readonly LambderPackageBootCall[];
|
|
28
|
+
/** Where the server takes API calls, for the calls given as `api`. Default: "/api". */
|
|
29
|
+
apiPath?: string;
|
|
30
|
+
/** The Host an API call's event names. Default: "localhost". */
|
|
31
|
+
host?: string;
|
|
32
|
+
/** The API version an API call's envelope carries, for a server with a version floor. Default: none. */
|
|
33
|
+
apiVersion?: string;
|
|
34
|
+
/** Send API calls as another function's invoke rather than as a browser's request through a gateway. Default: false. */
|
|
35
|
+
invoke?: boolean;
|
|
36
|
+
/** Package names and scopes the Lambda runtime supplies, so the package leaves them out. Default: ["@aws-sdk"]. */
|
|
37
|
+
runtimeModules?: readonly string[];
|
|
38
|
+
/** The directory runtimeModules resolve from, as if a module there imported them: an install that has them. Default: the working directory, normally the project's root. */
|
|
39
|
+
runtimeModulesFrom?: string;
|
|
40
|
+
/** The process's environment, beside PATH: the variables the function's configuration sets. Nothing else of this process's environment reaches it. Default: none. */
|
|
41
|
+
env?: Record<string, string>;
|
|
42
|
+
/** Node's options for the process, such as the heap sizes Lambda starts the function with, so what is measured is what the function pays. Default: none. */
|
|
43
|
+
nodeFlags?: readonly string[];
|
|
44
|
+
/** How long starting the process and importing the handler module may take. Default: 30,000. */
|
|
45
|
+
importTimeoutMs?: number;
|
|
46
|
+
/** How long each call may take. Default: 30,000. */
|
|
47
|
+
callTimeoutMs?: number;
|
|
48
|
+
};
|
|
49
|
+
/** What loading the package cost, measured in the process that loaded it and nothing before it. */
|
|
50
|
+
export type LambderPackageBootMeasurements = {
|
|
51
|
+
/** Importing the handler module and everything it imports statically: what a cold start pays before its first request. */
|
|
52
|
+
importMs: number;
|
|
53
|
+
/** The process's resident memory once that import finished. */
|
|
54
|
+
rssBytes: number;
|
|
55
|
+
};
|
|
56
|
+
/** The handler's answer to one call. */
|
|
57
|
+
export type LambderPackageBootCallResult = {
|
|
58
|
+
/** The call's endpoint, or its event's name. */
|
|
59
|
+
name: string;
|
|
60
|
+
/** What the handler returned, as Lambda hands it back: through JSON, null for nothing. */
|
|
61
|
+
returned: unknown;
|
|
62
|
+
/** The HTTP status, when the handler answered with an HTTP response. */
|
|
63
|
+
status?: number;
|
|
64
|
+
/** For an API call, what a caller reads off the answer, for assertApiSuccess, assertApiFailure and assertApiRefusal. */
|
|
65
|
+
outcome?: LambderApiOutcome<unknown>;
|
|
66
|
+
};
|
|
67
|
+
export type LambderPackageBootResult = {
|
|
68
|
+
/** Whether the package imported and the handler answered every call. */
|
|
69
|
+
ok: boolean;
|
|
70
|
+
/** Where it failed: "import" (starting the process and importing the handler module) or "handler" (a call threw, ran out of time, or the process died during it). */
|
|
71
|
+
phase?: "import" | "handler";
|
|
72
|
+
/** The call that failed, as its result would have named it. */
|
|
73
|
+
failedCall?: string;
|
|
74
|
+
/** What went wrong, ready to print: the phase or the call, the error, and the end of the process's output when it died. */
|
|
75
|
+
error?: string;
|
|
76
|
+
/** The answer to each call the handler answered, in order: all of them when `ok`. */
|
|
77
|
+
calls: LambderPackageBootCallResult[];
|
|
78
|
+
/** Present once the import finished. */
|
|
79
|
+
measurements?: LambderPackageBootMeasurements;
|
|
80
|
+
/** What the process wrote to stdout and stderr. */
|
|
81
|
+
output: string;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Boots an assembled deployment package the way Lambda boots it, and hands
|
|
85
|
+
* its handler each call in turn: in a fresh node process, so nothing of the
|
|
86
|
+
* calling test's module graph or environment reaches it, with the package
|
|
87
|
+
* directory as the working directory, and with every import the package
|
|
88
|
+
* makes held to what the package carries. What the Lambda runtime supplies
|
|
89
|
+
* (`runtimeModules`, the AWS SDK by default) resolves from an install
|
|
90
|
+
* outside it instead, since the package rightly leaves it out.
|
|
91
|
+
*
|
|
92
|
+
* Makes no assertions: the result says whether the package imported and
|
|
93
|
+
* answered, what each call answered, what the import cost, and where it
|
|
94
|
+
* failed. Throws, before or after starting the process, for what is not
|
|
95
|
+
* the package's doing: a missing directory, a handler that names no module
|
|
96
|
+
* there or no function exported by it, an option out of range, or a Node
|
|
97
|
+
* without synchronous module hooks.
|
|
98
|
+
*
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const boot = await bootLambdaPackage({ packageDir: "build/server", calls: [{ api: "status.ping" }] });
|
|
101
|
+
* expect(boot.ok, boot.error).toBe(true);
|
|
102
|
+
* assertApiSuccess(boot.calls[0]!.outcome!);
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare const bootLambdaPackage: (options: LambderPackageBootOptions) => Promise<LambderPackageBootResult>;
|