lambder 15.0.2 → 16.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 +118 -0
- package/README.md +4 -4
- package/dist/api/LambderApiDeclarations.d.ts +21 -5
- package/dist/api/LambderApiDeclarations.js +21 -3
- package/dist/api/LambderApiGuards.d.ts +23 -3
- package/dist/api/LambderApiGuards.js +50 -25
- package/dist/api/LambderApiModules.d.ts +50 -0
- package/dist/api/LambderApiModules.js +36 -0
- package/dist/api/LambderApiPipeline.d.ts +27 -4
- package/dist/api/LambderApiPipeline.js +71 -5
- package/dist/api/LambderApiRateLimits.d.ts +35 -4
- package/dist/api/LambderApiRateLimits.js +74 -35
- package/dist/core/Lambder.d.ts +68 -93
- package/dist/core/Lambder.js +147 -71
- package/dist/core/LambderApiBuilders.d.ts +105 -0
- package/dist/core/LambderApiBuilders.js +12 -0
- package/dist/core/LambderApiModule.d.ts +106 -0
- package/dist/core/LambderApiModule.js +45 -0
- package/dist/core/LambderCallSummary.d.ts +11 -9
- package/dist/core/LambderCreateOptions.d.ts +29 -24
- package/dist/core/LambderInvocationScope.d.ts +9 -3
- package/dist/core/LambderRouting.d.ts +9 -0
- package/dist/core/LambderRouting.js +20 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.js +2 -0
- package/dist/mock/LambderMockApiModule.d.ts +70 -0
- package/dist/mock/LambderMockApiModule.js +34 -0
- package/dist/mock/LambderMockApp.d.ts +53 -8
- package/dist/mock/LambderMockApp.js +104 -28
- package/dist/mock/LambderMockCreateOptions.d.ts +18 -18
- package/dist/mock/LambderMockTypes.d.ts +46 -26
- package/dist/mock.d.ts +4 -1
- package/dist/mock.js +2 -0
- package/dist/shared/util/LambderTypeUtilities.d.ts +1 -1
- package/dist/testing/LambderTestApp.d.ts +4 -3
- package/dist/testing/LambderTestApp.js +4 -3
- package/package.json +1 -1
|
@@ -7,6 +7,8 @@ import { type LambderDeclaredVocabulary, type LambderHandlerRefusalsOf, type Lam
|
|
|
7
7
|
import { type LambderApiTransport, type LambderApiTransportRequest } from "../shared/transport/LambderApiTransport.js";
|
|
8
8
|
import { LambderCookieJar } from "../shared/transport/LambderCookieJar.js";
|
|
9
9
|
import { type LambderApiGuard } from "../api/LambderApiGuards.js";
|
|
10
|
+
import type { LambderNoExtraKeys } from "../shared/util/LambderTypeUtilities.js";
|
|
11
|
+
import { LambderMockApiModule, type LambderMockApiModuleChecks, type LambderMockApiModuleOptions } from "./LambderMockApiModule.js";
|
|
10
12
|
import { LAMBDER_MOCK_ADAPTER_DOOR, type LambderMockAdapterDoor } from "./LambderMockAdapterDoor.js";
|
|
11
13
|
import { LambderMemoryRateLimiter } from "../stores/LambderMemoryRateLimiter.js";
|
|
12
14
|
import { LambderMemoryIdempotencyStore } from "../stores/LambderMemoryIdempotencyStore.js";
|
|
@@ -14,7 +16,7 @@ import { LambderMemorySessionStore } from "../stores/LambderMemorySessionStore.j
|
|
|
14
16
|
import LambderSessionManager, { type LambderCreatedSession } from "../session/LambderSessionManager.js";
|
|
15
17
|
import type { LambderMockAppOptions, LambderMockIdempotencyOptions, LambderMockTransport, LambderMockTransportOptions } from "./LambderMockCreateOptions.js";
|
|
16
18
|
import type { LambderApiOptionEntry, LambderGuardDeclarationEntry } from "../shared/wire/LambderApiOptionEntries.js";
|
|
17
|
-
import type { LambderMockCallContext, LambderMockCallRecord, LambderMockEntry, LambderMockEntryInput, LambderMockFailure, LambderMockFailureReason, LambderMockHandler, LambderMockLatency, LambderMockListener, LambderMockNotMockedInput, LambderMockRateLimitPolicies, LambderMockRegistryCheck, LambderMockRestEntry, LambderMockSessionCallContext, LambderMockSlice, LambderMockOverride, LambderMockRefusalsOf } from "./LambderMockTypes.js";
|
|
19
|
+
import type { LambderMockCallContext, LambderMockCallRecord, LambderMockEntry, LambderMockEntryGuardsCheck, LambderMockEntryInput, LambderMockFailure, LambderMockFailureReason, LambderMockHandler, LambderMockLatency, LambderMockListener, LambderMockNotMockedGuardsCheck, LambderMockNotMockedInput, LambderMockRateLimitPolicies, LambderMockRegistryCheck, LambderMockRestEntry, LambderMockSessionCallContext, LambderMockSlice, LambderMockOverride, LambderMockRefusalsOf } from "./LambderMockTypes.js";
|
|
18
20
|
/**
|
|
19
21
|
* The mock runtime: the API core (LambderApiPipeline, the same class the
|
|
20
22
|
* Lambda server runs) over memory stores, with typed mock handlers and mock
|
|
@@ -31,8 +33,10 @@ import type { LambderMockCallContext, LambderMockCallRecord, LambderMockEntry, L
|
|
|
31
33
|
* generated `apiOptions` table, whose entries are their handlers alone.
|
|
32
34
|
* `TVocabulary` and `TCodesRequired` are the refusal vocabulary the init
|
|
33
35
|
* declared (declareRefusals), which types every handler's ctx.refuse.
|
|
36
|
+
* `D` is the generated guard declarations create() was given, which a
|
|
37
|
+
* module's mock guards are held to as create()'s are.
|
|
34
38
|
*/
|
|
35
|
-
export declare class LambderMockApp<C extends LambderApiContractShape, S = any, G extends Record<string, LambderApiGuard<any, any, any>> = {}, TDerived extends boolean = false, TVocabulary extends LambderRefusalVocabulary = never, TCodesRequired extends boolean = false> {
|
|
39
|
+
export declare class LambderMockApp<C extends LambderApiContractShape, S = any, G extends Record<string, LambderApiGuard<any, any, any>> = {}, TDerived extends boolean = false, TVocabulary extends LambderRefusalVocabulary = never, TCodesRequired extends boolean = false, D extends Record<string, LambderGuardDeclarationEntry> = {}> {
|
|
36
40
|
readonly apiVersion: string | null;
|
|
37
41
|
/** The memory stores, for assertions and reset; null for a subsystem that is off or backed by a store of yours. */
|
|
38
42
|
readonly sessionStore: LambderMemorySessionStore<S> | null;
|
|
@@ -87,8 +91,8 @@ export declare class LambderMockApp<C extends LambderApiContractShape, S = any,
|
|
|
87
91
|
private readonly apiOptions;
|
|
88
92
|
/** The server's schemas, when create() was given the generated table: every entry's input is validated, and every answer parsed, through them. */
|
|
89
93
|
private readonly apiSchemas;
|
|
90
|
-
/** The mock's own guards as create() was given them: which of them need a session, for an entry's mode without the apiOptions table. */
|
|
91
|
-
private
|
|
94
|
+
/** The mock's own guards as create() was given them, and every registered module's beside them: which of them need a session, for an entry's mode without the apiOptions table. */
|
|
95
|
+
private mockGuards;
|
|
92
96
|
/** The server's guard declarations, when create() was given the generated table: the refusal codes a declared guard adds to an entry. */
|
|
93
97
|
private readonly guardDeclarations;
|
|
94
98
|
/** The server's refusal vocabulary, when create() was given the generated table: whether each code an entry may refuse with carries data. */
|
|
@@ -100,6 +104,15 @@ export declare class LambderMockApp<C extends LambderApiContractShape, S = any,
|
|
|
100
104
|
constructor(options: LambderMockAppOptions<C, S, G, LambderMockRateLimitPolicies<S>, boolean | LambderMockIdempotencyOptions<S>, {}, Record<string, LambderApiOptionEntry> | undefined>,
|
|
101
105
|
/** The vocabulary the init declared, or null: what initLambderMock().declareRefusals() passes on. */
|
|
102
106
|
declared?: LambderDeclaredVocabulary<TVocabulary, TCodesRequired> | null);
|
|
107
|
+
/**
|
|
108
|
+
* A mock guard stands in for the server's guard of its name, so it
|
|
109
|
+
* declares the codes that guard declares: its own refusals type its
|
|
110
|
+
* ctx.refuse and are what checkApiRefusals holds it to. The compiler
|
|
111
|
+
* compares literal lists at the guards option and at a module's; this
|
|
112
|
+
* catches a list typed as any string and a caller the compiler did not
|
|
113
|
+
* see.
|
|
114
|
+
*/
|
|
115
|
+
private assertGuardRefusalsAgree;
|
|
103
116
|
/**
|
|
104
117
|
* The registration-time checks every entry goes through, mocked or not:
|
|
105
118
|
* the ones the server runs on a definition, and the mock's own "a session
|
|
@@ -145,6 +158,11 @@ export declare class LambderMockApp<C extends LambderApiContractShape, S = any,
|
|
|
145
158
|
* and defaulted as on the server). Either alone where only one is given.
|
|
146
159
|
*/
|
|
147
160
|
private entryInputOf;
|
|
161
|
+
/**
|
|
162
|
+
* An entry built and checked, by the app's api() or by a module's, whose
|
|
163
|
+
* declarations (`module`) its guards may name beside the app's own; the
|
|
164
|
+
* entry carries the module, which registering it adds.
|
|
165
|
+
*/
|
|
148
166
|
private buildEntry;
|
|
149
167
|
/**
|
|
150
168
|
* A mock for an endpoint: a handler, or the handler with the endpoint's
|
|
@@ -152,7 +170,7 @@ export declare class LambderMockApp<C extends LambderApiContractShape, S = any,
|
|
|
152
170
|
* session endpoint's session is fetched before the handler runs, and a
|
|
153
171
|
* call without one is refused as on the server.
|
|
154
172
|
*/
|
|
155
|
-
api<K extends keyof C & string, TInputSchema extends z.ZodType = z.ZodType>(name: K, entry: LambderMockEntryInput<C, K, S, G, TInputSchema, TDerived, TVocabulary, TCodesRequired>): LambderMockEntry<C, K>;
|
|
173
|
+
api<K extends keyof C & string, TInputSchema extends z.ZodType = z.ZodType>(name: K & LambderMockEntryGuardsCheck<C, K, G, TDerived>, entry: LambderMockEntryInput<C, K, S, G, TInputSchema, TDerived, TVocabulary, TCodesRequired>): LambderMockEntry<C, K>;
|
|
156
174
|
/**
|
|
157
175
|
* An endpoint deliberately left without a mock; a call answers the
|
|
158
176
|
* notMocked refusal carrying the reason. The refusal runs through the
|
|
@@ -162,7 +180,9 @@ export declare class LambderMockApp<C extends LambderApiContractShape, S = any,
|
|
|
162
180
|
* apiOptions table a session endpoint restates its guards to say so,
|
|
163
181
|
* `notMocked(name, { reason, guards })`; no guard runs.
|
|
164
182
|
*/
|
|
165
|
-
notMocked<K extends keyof C & string>(name: K, input: LambderMockNotMockedInput<C, K, TDerived>): LambderMockEntry<C, K>;
|
|
183
|
+
notMocked<K extends keyof C & string>(name: K & LambderMockNotMockedGuardsCheck<C, K, G, TDerived>, input: LambderMockNotMockedInput<C, K, TDerived>): LambderMockEntry<C, K>;
|
|
184
|
+
/** A notMocked entry, by the app's notMocked() or by a module's, whose guards give its mode beside the app's own. */
|
|
185
|
+
private buildNotMockedEntry;
|
|
166
186
|
/**
|
|
167
187
|
* "Everything I did not register is not mocked, for this reason", as an
|
|
168
188
|
* argument to the same register() call:
|
|
@@ -190,8 +210,33 @@ export declare class LambderMockApp<C extends LambderApiContractShape, S = any,
|
|
|
190
210
|
* a test cares about that path.
|
|
191
211
|
*/
|
|
192
212
|
restNotMocked(reason: string): LambderMockRestEntry;
|
|
213
|
+
/**
|
|
214
|
+
* Defines a module: the mock guards a part of the app declares with its
|
|
215
|
+
* own entries, standing in for the guards the server's module declares
|
|
216
|
+
* (its defineApiModule). Returns the module's entry builders, whose
|
|
217
|
+
* entries may name the app's guards and the module's own; registering
|
|
218
|
+
* them adds the module's guards to the app. So the app's create() need
|
|
219
|
+
* not import every module's mock guards.
|
|
220
|
+
*
|
|
221
|
+
* ```ts
|
|
222
|
+
* export const giftCardMocks = mockApp.defineApiModule({ guards: { cardHolder: mock.guard({ ... }) } });
|
|
223
|
+
* giftCardMocks.apiSlice(giftCardMocks.api("giftCards.redeem", async () => ({ redeemed: true })));
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* Each guard is held to what create()'s are held to (the contract, the
|
|
227
|
+
* generated guardDeclarations, surplus keys); a name the app's create()
|
|
228
|
+
* declares is refused here, at compile time and at the call, and a name
|
|
229
|
+
* two modules declare when the second is registered.
|
|
230
|
+
*/
|
|
231
|
+
defineApiModule<const TOptions extends LambderMockApiModuleOptions<S>>(options: LambderNoExtraKeys<TOptions, LambderMockApiModuleOptions<S>> & LambderMockApiModuleChecks<C, S, G, D, TOptions>): LambderMockApiModule<C, S, G & TOptions["guards"], TDerived, TVocabulary, TCodesRequired>;
|
|
193
232
|
/** The entries of one module as a slice, keyed by name. Two entries for one endpoint is an error here. */
|
|
194
233
|
apiSlice<const E extends readonly LambderMockEntry<C, keyof C & string>[]>(...entries: E): LambderMockSlice<C, E[number]["name"]>;
|
|
234
|
+
/**
|
|
235
|
+
* Registers slices, adding the modules their entries were built with: a
|
|
236
|
+
* module's mock guards are checked against the app's and every other
|
|
237
|
+
* module's first, and taken only once the registry took the slices, so a
|
|
238
|
+
* refused registration leaves the app as it was.
|
|
239
|
+
*/
|
|
195
240
|
private addSlices;
|
|
196
241
|
/**
|
|
197
242
|
* Registers the whole contract: every endpoint in exactly one slice, or
|
|
@@ -389,7 +434,7 @@ export declare const initLambderMock: <C extends LambderApiContractShape, S = an
|
|
|
389
434
|
* dropped. `I` exists for the same reason `P` does, and
|
|
390
435
|
* LambderMockSurplusKeys puts the error back on the key.
|
|
391
436
|
*/
|
|
392
|
-
create<const G extends Record<string, LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>> = {}, const P extends LambderMockRateLimitPolicies<S> = {}, I extends boolean | LambderMockIdempotencyOptions<S> = boolean | LambderMockIdempotencyOptions<S>, const D extends Record<string, LambderGuardDeclarationEntry> = {}, const A extends Record<string, LambderApiOptionEntry> | undefined = undefined>(options: LambderMockAppOptions<C, S, G, P, I, D, A>): LambderMockApp<C, S, G, [A] extends [undefined] ? false : true, LambderMergedRefusalVocabulary<TVocabulary>, TRequireCodes>;
|
|
437
|
+
create<const G extends Record<string, LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>> = {}, const P extends LambderMockRateLimitPolicies<S> = {}, I extends boolean | LambderMockIdempotencyOptions<S> = boolean | LambderMockIdempotencyOptions<S>, const D extends Record<string, LambderGuardDeclarationEntry> = {}, const A extends Record<string, LambderApiOptionEntry> | undefined = undefined>(options: LambderMockAppOptions<C, S, G, P, I, D, A>): LambderMockApp<C, S, G, [A] extends [undefined] ? false : true, LambderMergedRefusalVocabulary<TVocabulary>, TRequireCodes, D>;
|
|
393
438
|
};
|
|
394
439
|
/**
|
|
395
440
|
* Builds a mock guard: the server guard's shape, the handler seeing the
|
|
@@ -425,5 +470,5 @@ export declare const initLambderMock: <C extends LambderApiContractShape, S = an
|
|
|
425
470
|
* dropped. `I` exists for the same reason `P` does, and
|
|
426
471
|
* LambderMockSurplusKeys puts the error back on the key.
|
|
427
472
|
*/
|
|
428
|
-
create<const G extends Record<string, LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>> = {}, const P extends LambderMockRateLimitPolicies<S> = {}, I extends boolean | LambderMockIdempotencyOptions<S> = boolean | LambderMockIdempotencyOptions<S>, const D extends Record<string, LambderGuardDeclarationEntry> = {}, const A extends Record<string, LambderApiOptionEntry> | undefined = undefined>(options: LambderMockAppOptions<C, S, G, P, I, D, A>): LambderMockApp<C, S, G, [A] extends [undefined] ? false : true, never, false>;
|
|
473
|
+
create<const G extends Record<string, LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>> = {}, const P extends LambderMockRateLimitPolicies<S> = {}, I extends boolean | LambderMockIdempotencyOptions<S> = boolean | LambderMockIdempotencyOptions<S>, const D extends Record<string, LambderGuardDeclarationEntry> = {}, const A extends Record<string, LambderApiOptionEntry> | undefined = undefined>(options: LambderMockAppOptions<C, S, G, P, I, D, A>): LambderMockApp<C, S, G, [A] extends [undefined] ? false : true, never, false, D>;
|
|
429
474
|
};
|
|
@@ -15,6 +15,8 @@ import { LambderCookieJar } from "../shared/transport/LambderCookieJar.js";
|
|
|
15
15
|
import { serializeClearCookie } from "../shared/wire/LambderCookie.js";
|
|
16
16
|
import { LOOPBACK_CLIENT_IP, normalizeClientIp } from "../shared/util/LambderClientIp.js";
|
|
17
17
|
import { lambderGuardBuilder, toGuardEntries } from "../api/LambderApiGuards.js";
|
|
18
|
+
import { apiModuleDeclarationsOf } from "../api/LambderApiModules.js";
|
|
19
|
+
import { LambderMockApiModule } from "./LambderMockApiModule.js";
|
|
18
20
|
import { lambderRateLimitKeyBuilder } from "../api/LambderApiRateLimits.js";
|
|
19
21
|
import { LambderMockFailureInjector, LambderMockTransportError } from "./LambderMockFailureInjector.js";
|
|
20
22
|
import { LambderMockCallRecorder } from "./LambderMockCallRecorder.js";
|
|
@@ -58,6 +60,8 @@ const defaultCookieHost = () => globalThis.location?.host || "localhost";
|
|
|
58
60
|
* generated `apiOptions` table, whose entries are their handlers alone.
|
|
59
61
|
* `TVocabulary` and `TCodesRequired` are the refusal vocabulary the init
|
|
60
62
|
* declared (declareRefusals), which types every handler's ctx.refuse.
|
|
63
|
+
* `D` is the generated guard declarations create() was given, which a
|
|
64
|
+
* module's mock guards are held to as create()'s are.
|
|
61
65
|
*/
|
|
62
66
|
export class LambderMockApp {
|
|
63
67
|
apiVersion;
|
|
@@ -122,7 +126,7 @@ export class LambderMockApp {
|
|
|
122
126
|
apiOptions;
|
|
123
127
|
/** The server's schemas, when create() was given the generated table: every entry's input is validated, and every answer parsed, through them. */
|
|
124
128
|
apiSchemas;
|
|
125
|
-
/** The mock's own guards as create() was given them: which of them need a session, for an entry's mode without the apiOptions table. */
|
|
129
|
+
/** The mock's own guards as create() was given them, and every registered module's beside them: which of them need a session, for an entry's mode without the apiOptions table. */
|
|
126
130
|
mockGuards;
|
|
127
131
|
/** The server's guard declarations, when create() was given the generated table: the refusal codes a declared guard adds to an entry. */
|
|
128
132
|
guardDeclarations;
|
|
@@ -138,23 +142,9 @@ export class LambderMockApp {
|
|
|
138
142
|
this.apiVersion = options.apiVersion ?? null;
|
|
139
143
|
this.apiOptions = options.apiOptions ?? null;
|
|
140
144
|
this.apiSchemas = options.apiSchemas ? new LambderMockApiSchemas(options.apiSchemas) : null;
|
|
141
|
-
this.mockGuards = options.guards
|
|
145
|
+
this.mockGuards = { ...options.guards };
|
|
142
146
|
this.guardDeclarations = options.guardDeclarations ?? null;
|
|
143
|
-
|
|
144
|
-
// declares the codes that guard declares: its own refusals type its
|
|
145
|
-
// ctx.refuse and are what checkApiRefusals holds it to. The compiler
|
|
146
|
-
// compares literal lists at the guards option; this catches a list
|
|
147
|
-
// typed as any string and a caller the compiler did not see.
|
|
148
|
-
for (const [name, guard] of Object.entries(options.guards ?? {})) {
|
|
149
|
-
if (!this.guardDeclarations || !Object.prototype.hasOwnProperty.call(this.guardDeclarations, name))
|
|
150
|
-
continue;
|
|
151
|
-
const own = new Set(Array.isArray(guard?.refusals) ? guard.refusals : []);
|
|
152
|
-
const declared = new Set(this.guardDeclarations[name].refusals ?? []);
|
|
153
|
-
if (own.size !== declared.size || [...declared].some((code) => !own.has(code))) {
|
|
154
|
-
const listed = (codes) => codes.size ? [...codes].map((code) => `"${code}"`).join(", ") : "none";
|
|
155
|
-
throw new Error(`LambderMockApp: the mock guard "${name}" declares the refusals ${listed(own)}, and the server's guard of that name declares ${listed(declared)} in the guardDeclarations table. A mock guard declares exactly the server guard's codes: regenerate the table, or give the mock guard refusals: [${[...declared].map((code) => `"${code}"`).join(", ")}].`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
147
|
+
this.assertGuardRefusalsAgree(options.guards ?? {});
|
|
158
148
|
this.refusalVocabulary = declared?.vocabulary ?? null;
|
|
159
149
|
this.requireRefusalCodes = declared?.requireCodes ?? false;
|
|
160
150
|
if (this.requireRefusalCodes && !this.apiOptions) {
|
|
@@ -253,6 +243,26 @@ export class LambderMockApp {
|
|
|
253
243
|
atRestSecret: sessionOptions ? sessionOptions.sessionSalt ?? DEFAULT_MOCK_SESSION_SALT : undefined,
|
|
254
244
|
});
|
|
255
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* A mock guard stands in for the server's guard of its name, so it
|
|
248
|
+
* declares the codes that guard declares: its own refusals type its
|
|
249
|
+
* ctx.refuse and are what checkApiRefusals holds it to. The compiler
|
|
250
|
+
* compares literal lists at the guards option and at a module's; this
|
|
251
|
+
* catches a list typed as any string and a caller the compiler did not
|
|
252
|
+
* see.
|
|
253
|
+
*/
|
|
254
|
+
assertGuardRefusalsAgree(guards) {
|
|
255
|
+
for (const [name, guard] of Object.entries(guards)) {
|
|
256
|
+
if (!this.guardDeclarations || !Object.prototype.hasOwnProperty.call(this.guardDeclarations, name))
|
|
257
|
+
continue;
|
|
258
|
+
const own = new Set(Array.isArray(guard?.refusals) ? guard.refusals : []);
|
|
259
|
+
const declared = new Set(this.guardDeclarations[name].refusals ?? []);
|
|
260
|
+
if (own.size !== declared.size || [...declared].some((code) => !own.has(code))) {
|
|
261
|
+
const listed = (codes) => codes.size ? [...codes].map((code) => `"${code}"`).join(", ") : "none";
|
|
262
|
+
throw new Error(`LambderMockApp: the mock guard "${name}" declares the refusals ${listed(own)}, and the server's guard of that name declares ${listed(declared)} in the guardDeclarations table. A mock guard declares exactly the server guard's codes: regenerate the table, or give the mock guard refusals: [${[...declared].map((code) => `"${code}"`).join(", ")}].`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
256
266
|
// -----------------------------------------------------------------------
|
|
257
267
|
// Registry
|
|
258
268
|
// -----------------------------------------------------------------------
|
|
@@ -266,8 +276,8 @@ export class LambderMockApp {
|
|
|
266
276
|
* silently and answer its first call with a 500 naming the SERVER's
|
|
267
277
|
* option, for a mistake fixed by one option at create().
|
|
268
278
|
*/
|
|
269
|
-
assertEntryRegistration(definition) {
|
|
270
|
-
this.pipeline.assertRegistration(definition);
|
|
279
|
+
assertEntryRegistration(definition, module) {
|
|
280
|
+
this.pipeline.assertRegistration(definition, module ?? undefined);
|
|
271
281
|
if (definition.mode === "session" && !this.pipeline.hasSessions) {
|
|
272
282
|
throw new Error(`LambderMockApp: session endpoint "${definition.name}" needs the sessions option at creation.`);
|
|
273
283
|
}
|
|
@@ -294,11 +304,12 @@ export class LambderMockApp {
|
|
|
294
304
|
* otherwise the ones the entry restates, each read off the guardDeclarations
|
|
295
305
|
* table or the mock's own guards.
|
|
296
306
|
*/
|
|
297
|
-
modeOf(declared, restatedGuards) {
|
|
307
|
+
modeOf(declared, restatedGuards, module) {
|
|
298
308
|
if (declared)
|
|
299
309
|
return declared.mode;
|
|
300
310
|
const needsSession = (guard) => (this.guardDeclarations && Object.prototype.hasOwnProperty.call(this.guardDeclarations, guard) && this.guardDeclarations[guard].session === true)
|
|
301
|
-
|| (
|
|
311
|
+
|| (Object.prototype.hasOwnProperty.call(this.mockGuards, guard) && this.mockGuards[guard].session === true)
|
|
312
|
+
|| (module !== null && Object.prototype.hasOwnProperty.call(module.guards, guard) && module.guards[guard].session === true);
|
|
302
313
|
return toGuardEntries(restatedGuards).some(({ name: guard }) => needsSession(guard)) ? "session" : "public";
|
|
303
314
|
}
|
|
304
315
|
/**
|
|
@@ -352,10 +363,15 @@ export class LambderMockApp {
|
|
|
352
363
|
const server = this.apiSchemas.inputOf(name);
|
|
353
364
|
return own ? server.pipe(own) : server;
|
|
354
365
|
}
|
|
355
|
-
|
|
366
|
+
/**
|
|
367
|
+
* An entry built and checked, by the app's api() or by a module's, whose
|
|
368
|
+
* declarations (`module`) its guards may name beside the app's own; the
|
|
369
|
+
* entry carries the module, which registering it adds.
|
|
370
|
+
*/
|
|
371
|
+
buildEntry(name, input, module) {
|
|
356
372
|
const options = (typeof input === "function" ? { handler: input } : input);
|
|
357
373
|
const declared = this.declaredOptionsOf(name);
|
|
358
|
-
const mode = this.modeOf(declared, options.guards);
|
|
374
|
+
const mode = this.modeOf(declared, options.guards, module);
|
|
359
375
|
if (declared) {
|
|
360
376
|
// A caller the compiler did not see (a JavaScript slice, a cast)
|
|
361
377
|
// would otherwise have its restatement silently lose to the table.
|
|
@@ -379,9 +395,10 @@ export class LambderMockApp {
|
|
|
379
395
|
// undefined }`, a refusal a client cannot read.
|
|
380
396
|
input: this.entryInputOf(name, options.input),
|
|
381
397
|
};
|
|
382
|
-
this.assertEntryRegistration(definition);
|
|
398
|
+
this.assertEntryRegistration(definition, module);
|
|
383
399
|
const handler = options.handler;
|
|
384
|
-
|
|
400
|
+
const entry = { name, mode, definition, handler: async (ctx) => await handler(ctx), notMockedReason: null };
|
|
401
|
+
return module ? { ...entry, module } : entry;
|
|
385
402
|
}
|
|
386
403
|
/**
|
|
387
404
|
* A mock for an endpoint: a handler, or the handler with the endpoint's
|
|
@@ -390,7 +407,7 @@ export class LambderMockApp {
|
|
|
390
407
|
* call without one is refused as on the server.
|
|
391
408
|
*/
|
|
392
409
|
api(name, entry) {
|
|
393
|
-
return this.buildEntry(name, entry);
|
|
410
|
+
return this.buildEntry(name, entry, null);
|
|
394
411
|
}
|
|
395
412
|
/**
|
|
396
413
|
* An endpoint deliberately left without a mock; a call answers the
|
|
@@ -402,14 +419,18 @@ export class LambderMockApp {
|
|
|
402
419
|
* `notMocked(name, { reason, guards })`; no guard runs.
|
|
403
420
|
*/
|
|
404
421
|
notMocked(name, input) {
|
|
422
|
+
return this.buildNotMockedEntry(name, input, null);
|
|
423
|
+
}
|
|
424
|
+
/** A notMocked entry, by the app's notMocked() or by a module's, whose guards give its mode beside the app's own. */
|
|
425
|
+
buildNotMockedEntry(name, input, module) {
|
|
405
426
|
const { reason, guards } = (typeof input === "string" ? { reason: input } : input);
|
|
406
427
|
const declared = this.declaredOptionsOf(name);
|
|
407
428
|
if (declared && guards !== undefined) {
|
|
408
429
|
throw new Error(`LambderMockApp: "${name}" restates its guards option, which the apiOptions table given to create() already declares. Leave it out of the entry.`);
|
|
409
430
|
}
|
|
410
|
-
const mode = this.modeOf(declared, guards);
|
|
431
|
+
const mode = this.modeOf(declared, guards, module);
|
|
411
432
|
const definition = { name, mode };
|
|
412
|
-
this.assertEntryRegistration(definition);
|
|
433
|
+
this.assertEntryRegistration(definition, module);
|
|
413
434
|
return { name, mode, definition, handler: null, notMockedReason: reason };
|
|
414
435
|
}
|
|
415
436
|
/**
|
|
@@ -441,6 +462,42 @@ export class LambderMockApp {
|
|
|
441
462
|
restNotMocked(reason) {
|
|
442
463
|
return { restNotMockedReason: reason };
|
|
443
464
|
}
|
|
465
|
+
/**
|
|
466
|
+
* Defines a module: the mock guards a part of the app declares with its
|
|
467
|
+
* own entries, standing in for the guards the server's module declares
|
|
468
|
+
* (its defineApiModule). Returns the module's entry builders, whose
|
|
469
|
+
* entries may name the app's guards and the module's own; registering
|
|
470
|
+
* them adds the module's guards to the app. So the app's create() need
|
|
471
|
+
* not import every module's mock guards.
|
|
472
|
+
*
|
|
473
|
+
* ```ts
|
|
474
|
+
* export const giftCardMocks = mockApp.defineApiModule({ guards: { cardHolder: mock.guard({ ... }) } });
|
|
475
|
+
* giftCardMocks.apiSlice(giftCardMocks.api("giftCards.redeem", async () => ({ redeemed: true })));
|
|
476
|
+
* ```
|
|
477
|
+
*
|
|
478
|
+
* Each guard is held to what create()'s are held to (the contract, the
|
|
479
|
+
* generated guardDeclarations, surplus keys); a name the app's create()
|
|
480
|
+
* declares is refused here, at compile time and at the call, and a name
|
|
481
|
+
* two modules declare when the second is registered.
|
|
482
|
+
*/
|
|
483
|
+
defineApiModule(options) {
|
|
484
|
+
const given = options;
|
|
485
|
+
if (given.rateLimitPolicies !== undefined) {
|
|
486
|
+
throw new Error("LambderMockApp: defineApiModule() takes guards alone. A mock takes every rate-limit policy at create(), the modules' among them, from the generated table (lambderMockPoliciesFrom).");
|
|
487
|
+
}
|
|
488
|
+
const declarations = apiModuleDeclarationsOf({ guards: given.guards }, "LambderMockApp");
|
|
489
|
+
this.assertGuardRefusalsAgree(declarations.guards);
|
|
490
|
+
// Checked against the app now, so a name its create() declares fails
|
|
491
|
+
// where the module is defined; registration checks again, against
|
|
492
|
+
// the other modules.
|
|
493
|
+
this.pipeline.modulesToAdd([{ module: declarations, usedBy: "being defined" }]);
|
|
494
|
+
return new LambderMockApiModule({
|
|
495
|
+
api: (name, entry) => this.buildEntry(name, entry, declarations),
|
|
496
|
+
notMocked: (name, input) => this.buildNotMockedEntry(name, input, declarations),
|
|
497
|
+
apiSlice: (...entries) => this.apiSlice(...entries),
|
|
498
|
+
override: (name, handler) => this.override(name, handler),
|
|
499
|
+
});
|
|
500
|
+
}
|
|
444
501
|
/** The entries of one module as a slice, keyed by name. Two entries for one endpoint is an error here. */
|
|
445
502
|
apiSlice(...entries) {
|
|
446
503
|
const slice = {};
|
|
@@ -451,8 +508,27 @@ export class LambderMockApp {
|
|
|
451
508
|
}
|
|
452
509
|
return slice;
|
|
453
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* Registers slices, adding the modules their entries were built with: a
|
|
513
|
+
* module's mock guards are checked against the app's and every other
|
|
514
|
+
* module's first, and taken only once the registry took the slices, so a
|
|
515
|
+
* refused registration leaves the app as it was.
|
|
516
|
+
*/
|
|
454
517
|
addSlices(slices) {
|
|
518
|
+
const uses = [];
|
|
519
|
+
for (const slice of slices) {
|
|
520
|
+
if ("restNotMockedReason" in slice && typeof slice.restNotMockedReason === "string")
|
|
521
|
+
continue;
|
|
522
|
+
for (const entry of Object.values(slice)) {
|
|
523
|
+
const module = entry?.module;
|
|
524
|
+
if (module && !uses.some((use) => use.module === module))
|
|
525
|
+
uses.push({ module, usedBy: `building the entry of "${entry.name}"` });
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const added = this.pipeline.modulesToAdd(uses);
|
|
455
529
|
this.registry.addSlices(slices);
|
|
530
|
+
this.pipeline.addModules(added);
|
|
531
|
+
this.mockGuards = { ...this.mockGuards, ...added.guards };
|
|
456
532
|
}
|
|
457
533
|
/**
|
|
458
534
|
* Registers the whole contract: every endpoint in exactly one slice, or
|
|
@@ -2,8 +2,8 @@ import type { z } from "zod";
|
|
|
2
2
|
import type { LambderApiSignatureMap } from "../shared/wire/LambderApiSignatureMap.js";
|
|
3
3
|
import type { LambderApiRefusalConfig } from "../shared/wire/LambderApiContract.js";
|
|
4
4
|
import type { LambderHttpStatusCode } from "../shared/wire/LambderHttpStatus.js";
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
5
|
+
import type { LambderMaybePromise } from "../shared/util/LambderTypeUtilities.js";
|
|
6
|
+
import type { LambderContractIdempotencyOf, LambderContractKeysWithMode, LambderContractMode, LambderContractRateLimitNames, LambderContractRateLimitOf } from "../shared/wire/LambderApiContract.js";
|
|
7
7
|
import type { LambderApiGuard } from "../api/LambderApiGuards.js";
|
|
8
8
|
import type { LambderApiOptionEntry, LambderGuardDeclarationEntry } from "../shared/wire/LambderApiOptionEntries.js";
|
|
9
9
|
import type { LambderApiSchemaEntry } from "../shared/wire/LambderApiSchemaEntries.js";
|
|
@@ -17,7 +17,7 @@ import type { LambderSessionStore } from "../shared/contracts/LambderSessionStor
|
|
|
17
17
|
import type { LambderSessionDataOptions } from "../session/LambderSessionManager.js";
|
|
18
18
|
import type { LambderSessionCookieOptions } from "../session/LambderSessionController.js";
|
|
19
19
|
import type { LambderSessionCrypto } from "../session/LambderSessionCrypto.js";
|
|
20
|
-
import type { LambderMockCallContext,
|
|
20
|
+
import type { LambderMockCallContext, LambderMockGuardMapChecks, LambderMockLatency, LambderMockRateLimitPolicies, LambderMockSessionCallContext, LambderMockSurplusKeys } from "./LambderMockTypes.js";
|
|
21
21
|
export type LambderMockSessionsOptions<S> = {
|
|
22
22
|
/** Where the mock's sessions rest. Default: a fresh LambderMemorySessionStore. */
|
|
23
23
|
store?: LambderSessionStore<S>;
|
|
@@ -162,24 +162,24 @@ type LambderMockGuardRefusalsMismatch<TDeclaration> = {
|
|
|
162
162
|
} ? R : readonly [];
|
|
163
163
|
};
|
|
164
164
|
/** Each mock guard the declarations know held to its declared shape and refusal codes; a guard the table does not have is free. */
|
|
165
|
-
type LambderMockGuardsAgree<G, D> = {
|
|
165
|
+
export type LambderMockGuardsAgree<G, D> = {
|
|
166
166
|
[N in keyof G & keyof D]: (G[N] extends LambderMockGuardShapeOf<D[N]> ? unknown : LambderMockGuardShapeOf<D[N]>) & LambderMockGuardRefusalsAgree<G[N], D[N]>;
|
|
167
167
|
};
|
|
168
168
|
/**
|
|
169
|
-
* The guards option:
|
|
170
|
-
*
|
|
169
|
+
* The guards option: the app's own mock guards, each held to what the
|
|
170
|
+
* contract asks of a guard of its name (LambderMockGuardMapChecks), to the
|
|
171
|
+
* generated declarations when they are given, and to the mock contexts.
|
|
171
172
|
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* the
|
|
173
|
+
* It need not name every guard the contract declares, since a part of the
|
|
174
|
+
* app may declare its own with its entries (defineApiModule); what it may
|
|
175
|
+
* not do is leave one out that an entry needs. Every entry is held to having
|
|
176
|
+
* a mock guard for each guard its endpoint declares, in the map it is built
|
|
177
|
+
* with (LambderMockEntryGuardsCheck), because a guard the mock does not
|
|
178
|
+
* declare cannot run, and a call the server answers notAuthorized would
|
|
179
|
+
* answer 200 here.
|
|
176
180
|
*/
|
|
177
|
-
type LambderMockGuardsOption<C, S, G, D> =
|
|
178
|
-
|
|
179
|
-
] extends [never] ? {
|
|
180
|
-
guards?: G & LambderMockGuards<C, S> & LambderMockGuardShapes<S, G> & LambderMockGuardsAgree<G, NoInfer<D>> & LambderMockSessionGuardsCheck<C, G>;
|
|
181
|
-
} : {
|
|
182
|
-
guards: G & LambderMockGuards<C, S> & LambderMockGuardShapes<S, G> & LambderMockGuardsAgree<G, NoInfer<D>> & LambderMockSessionGuardsCheck<C, G>;
|
|
181
|
+
type LambderMockGuardsOption<C, S, G, D> = {
|
|
182
|
+
guards?: G & LambderMockGuardMapChecks<C, S, G> & LambderMockGuardShapes<S, G> & LambderMockGuardsAgree<G, NoInfer<D>>;
|
|
183
183
|
};
|
|
184
184
|
/**
|
|
185
185
|
* The sessions, idempotency and rateLimits options: each required whenever
|
|
@@ -216,7 +216,7 @@ type LambderMockRateLimitsOption<C, S, P extends LambderMockRateLimitPolicies<S>
|
|
|
216
216
|
rateLimits: LambderMockRateLimitsOptions<C, S, P>;
|
|
217
217
|
};
|
|
218
218
|
/** Each guard checked for surplus keys, so `sesion: true` on an inline guard is an error at the key rather than a guard that silently runs as public. */
|
|
219
|
-
type LambderMockGuardShapes<S, G> = {
|
|
219
|
+
export type LambderMockGuardShapes<S, G> = {
|
|
220
220
|
[N in keyof G]: LambderMockSurplusKeys<G[N], LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>>;
|
|
221
221
|
};
|
|
222
222
|
/**
|
|
@@ -323,7 +323,7 @@ export type LambderMockAppOptions<C, S, G, P extends LambderMockRateLimitPolicie
|
|
|
323
323
|
* data. Return null for the standard 422 `validation` refusal, which is
|
|
324
324
|
* also what an app that sets neither gets.
|
|
325
325
|
*/
|
|
326
|
-
onInvalidInput?: (zodError: z.ZodError, ctx: LambderMockCallContext<S>) =>
|
|
326
|
+
onInvalidInput?: (zodError: z.ZodError, ctx: LambderMockCallContext<S>) => LambderMaybePromise<LambderMockInvalidInputAnswer | null>;
|
|
327
327
|
};
|
|
328
328
|
/**
|
|
329
329
|
* What the server's input validation handler answers, as a mock states it:
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the contract stays a type-only import in the consuming app.
|
|
7
7
|
*/
|
|
8
8
|
import type { z } from "zod";
|
|
9
|
-
import type { LambderApiMode, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractGuardInputsOf, LambderContractGuardNames, LambderContractGuardsOf, LambderContractIdempotencyOf,
|
|
9
|
+
import type { LambderApiMode, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractGuardInputsOf, LambderContractGuardNames, LambderContractGuardsOf, LambderContractIdempotencyOf, LambderContractMode, LambderContractRateLimitOf, LambderGuardNamesIn } from "../shared/wire/LambderApiContract.js";
|
|
10
10
|
import type { LambderApiGuard, LambderGuardDataOf, LambderGuardMetaMap } from "../api/LambderApiGuards.js";
|
|
11
11
|
import type { LambderApiRateLimitPolicyConfig, LambderContextRateLimit, LambderContextRateLimitCheck } from "../api/LambderApiRateLimits.js";
|
|
12
12
|
import type { LambderApiCallContext, LambderResponseTools } from "../api/LambderApiCallContext.js";
|
|
@@ -20,6 +20,7 @@ import type { LambderContractRefusalsOf } from "../shared/wire/LambderApiContrac
|
|
|
20
20
|
import type { LambderHandlerRefusalsOf } from "../api/LambderApiRefusals.js";
|
|
21
21
|
import type { LambderSessionRecord } from "../shared/contracts/LambderSessionStore.js";
|
|
22
22
|
import type LambderSessionController from "../session/LambderSessionController.js";
|
|
23
|
+
import type { LambderApiModuleDeclarations } from "../api/LambderApiModules.js";
|
|
23
24
|
export type LambderMockInputOf<C, K extends keyof C> = C[K] extends {
|
|
24
25
|
input: infer I;
|
|
25
26
|
} ? I : never;
|
|
@@ -101,13 +102,13 @@ export type LambderMockContext<C, K extends keyof C, S, G, TVocabulary = never,
|
|
|
101
102
|
refuse: LambderDeclaredRefuse<LambderMockRefusalsOf<C, K, TVocabulary>, TCodesRequired>;
|
|
102
103
|
};
|
|
103
104
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* public endpoint names
|
|
108
|
-
*
|
|
109
|
-
* a
|
|
110
|
-
*
|
|
105
|
+
* What the contract asks of a mock guard of each name it declares: the mock
|
|
106
|
+
* call contexts, plus, for each guardInput-mode guard, a `guardInput` schema
|
|
107
|
+
* whose input is what the contract says a client sends, and, for a guard a
|
|
108
|
+
* public endpoint names, no session. LambderMockGuardMapChecks holds a mock
|
|
109
|
+
* guard map (create()'s, or a module's) to it, name by name; an entry is held
|
|
110
|
+
* to having a mock guard for every guard its endpoint declares
|
|
111
|
+
* (LambderMockEntryGuardsCheck).
|
|
111
112
|
*/
|
|
112
113
|
export type LambderMockGuards<C, S = any> = {
|
|
113
114
|
[N in LambderContractGuardNames<C>]: LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>;
|
|
@@ -120,33 +121,50 @@ export type LambderMockGuards<C, S = any> = {
|
|
|
120
121
|
session?: false;
|
|
121
122
|
};
|
|
122
123
|
};
|
|
124
|
+
/**
|
|
125
|
+
* A mock guard map held, guard by guard, to what the contract asks of the
|
|
126
|
+
* guards it names (LambderMockGuards): a schema that parses to something
|
|
127
|
+
* else, or a public endpoint's guard that requires a session, fails at the
|
|
128
|
+
* map. Guards the contract does not declare are free.
|
|
129
|
+
*/
|
|
130
|
+
export type LambderMockGuardMapChecks<C, S, G> = {
|
|
131
|
+
[N in keyof G & keyof LambderMockGuards<C, S>]: LambderMockGuards<C, S>[N];
|
|
132
|
+
};
|
|
123
133
|
/** The guards of a mock guard map that need a session. */
|
|
124
134
|
type LambderMockSessionGuardNames<G> = {
|
|
125
135
|
[N in keyof G]: G[N] extends {
|
|
126
136
|
session: true;
|
|
127
137
|
} ? N : never;
|
|
128
138
|
}[keyof G];
|
|
129
|
-
/** The
|
|
130
|
-
type
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
/** The guards endpoint K declares that the guard map an entry for it is built with has no mock guard for. */
|
|
140
|
+
type LambderMockGuardsMissing<C, K extends keyof C, G> = Exclude<LambderGuardNamesIn<LambderContractGuardsOf<C, K>>, keyof G>;
|
|
141
|
+
/**
|
|
142
|
+
* Whether an entry for session endpoint K would run as public: without the
|
|
143
|
+
* apiOptions table (`TDerived`) the mock reads an entry's mode off the guards
|
|
144
|
+
* it restates, and none of those the map declares says `session: true`.
|
|
145
|
+
*/
|
|
146
|
+
type LambderMockRunsAsPublic<C, K extends keyof C, G, TDerived extends boolean> = TDerived extends true ? false : LambderContractMode<C, K> extends "session" ? ([Extract<LambderGuardNamesIn<LambderContractGuardsOf<C, K>>, LambderMockSessionGuardNames<G>>] extends [never] ? true : false) : false;
|
|
147
|
+
/** The error an entry that would run as public meets on its name. */
|
|
148
|
+
type LambderMockRunsAsPublicError<K> = {
|
|
149
|
+
readonly "lambder: this session endpoint names no mock guard declared session: true, so the mock would run it as public": K;
|
|
150
|
+
};
|
|
135
151
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
152
|
+
* An entry held to the guard map it is built with (create()'s, or a
|
|
153
|
+
* module's beside it): each guard its endpoint declares has a mock guard
|
|
154
|
+
* there, since a guard the mock does not declare cannot run and the mock
|
|
155
|
+
* would answer 200 where the server answers notAuthorized; and, where the
|
|
156
|
+
* mock reads the entry's mode off its guards, a session endpoint names one
|
|
157
|
+
* that needs a session, or a signed-out call would reach a handler typed
|
|
158
|
+
* with its session present. Intersected onto the entry's name: the property
|
|
159
|
+
* name is the message, its value the guards or the endpoint at fault.
|
|
144
160
|
*/
|
|
145
|
-
export type
|
|
146
|
-
|
|
147
|
-
] extends [never] ? unknown : {
|
|
148
|
-
readonly "lambder: these
|
|
161
|
+
export type LambderMockEntryGuardsCheck<C, K extends keyof C, G, TDerived extends boolean> = [
|
|
162
|
+
LambderMockGuardsMissing<C, K, G>
|
|
163
|
+
] extends [never] ? (LambderMockRunsAsPublic<C, K, G, TDerived> extends true ? LambderMockRunsAsPublicError<K> : unknown) : {
|
|
164
|
+
readonly "lambder: these guards of this endpoint have no mock guard where its entry is built; declare them at create(), or build the entry with the defineApiModule() that declares them": LambderMockGuardsMissing<C, K, G>;
|
|
149
165
|
};
|
|
166
|
+
/** A notMocked entry runs no guard, so it is held only to the mode its restated guards give it (see LambderMockEntryGuardsCheck). */
|
|
167
|
+
export type LambderMockNotMockedGuardsCheck<C, K extends keyof C, G, TDerived extends boolean> = LambderMockRunsAsPublic<C, K, G, TDerived> extends true ? LambderMockRunsAsPublicError<K> : unknown;
|
|
150
168
|
export type LambderMockHandler<C, K extends keyof C, S, G, TVocabulary = never, TCodesRequired extends boolean = false> = (ctx: LambderMockContext<C, K, S, G, TVocabulary, TCodesRequired>) => LambderMockOutputOf<C, K> | Promise<LambderMockOutputOf<C, K>>;
|
|
151
169
|
/**
|
|
152
170
|
* The guards field of an entry: required whenever the contract declares any
|
|
@@ -288,6 +306,8 @@ export type LambderMockEntry<C, K extends keyof C & string> = {
|
|
|
288
306
|
readonly definition: LambderApiDefinition;
|
|
289
307
|
readonly handler: ((ctx: any) => Promise<unknown>) | null;
|
|
290
308
|
readonly notMockedReason: string | null;
|
|
309
|
+
/** The module whose builders made the entry (defineApiModule), whose mock guards registering it adds; absent on the app's own. */
|
|
310
|
+
readonly module?: LambderApiModuleDeclarations;
|
|
291
311
|
};
|
|
292
312
|
/** A slice: the entries of one module, keyed by endpoint name. */
|
|
293
313
|
export type LambderMockSlice<C, K extends keyof C & string> = {
|
package/dist/mock.d.ts
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* production bundle by construction: nothing else imports it.
|
|
8
8
|
*/
|
|
9
9
|
export { LambderMockApp, initLambderMock } from "./mock/LambderMockApp.js";
|
|
10
|
+
export { LambderMockApiModule } from "./mock/LambderMockApiModule.js";
|
|
11
|
+
export type { LambderMockApiModuleOptions, LambderMockApiModuleChecks, LambderMockApiModuleDoor } from "./mock/LambderMockApiModule.js";
|
|
10
12
|
export { LambderMockTransportError } from "./mock/LambderMockFailureInjector.js";
|
|
11
13
|
export type { LambderMockAppOptions, LambderMockSessionsOptions, LambderMockIdempotencyOptions, LambderMockInvalidInputAnswer, LambderMockTransport, LambderMockTransportOptions } from "./mock/LambderMockCreateOptions.js";
|
|
12
|
-
export type { LambderMockCallContext, LambderMockSessionCallContext, LambderMockContext, LambderMockRefusalsOf, LambderMockGuards, LambderMockHandler, LambderMockEntry, LambderMockEntryOptions, LambderMockEntryInput, LambderMockNotMockedInput,
|
|
14
|
+
export type { LambderMockCallContext, LambderMockSessionCallContext, LambderMockContext, LambderMockRefusalsOf, LambderMockGuards, LambderMockHandler, LambderMockEntry, LambderMockEntryOptions, LambderMockEntryInput, LambderMockNotMockedInput, LambderMockEntryGuardsCheck, LambderMockNotMockedGuardsCheck, LambderMockSlice, LambderMockRestEntry, LambderMockRegistryCheck, LambderMockMissingNames, LambderMockStrayNames, LambderMockDuplicateNames, LambderMockLatency, LambderMockFailure, LambderMockFailureReason, LambderMockOutcome, LambderMockCallEvent, LambderMockRequestEvent, LambderMockResponseEvent, LambderMockCallRecord, LambderMockListener, LambderMockRateLimitPolicies, LambderMockInputOf, LambderMockOutputOf, LambderMockOverride, } from "./mock/LambderMockTypes.js";
|
|
13
15
|
export { lambderMockPoliciesFrom } from "./mock/lambderMockPoliciesFrom.js";
|
|
14
16
|
export type { LambderCustomKeyedPolicyNames, LambderMockPolicyKeys } from "./mock/lambderMockPoliciesFrom.js";
|
|
15
17
|
export type { LambderMockGuardShapeOf } from "./mock/LambderMockCreateOptions.js";
|
|
@@ -34,6 +36,7 @@ export type { LambderSessionCrypto } from "./session/LambderSessionCrypto.js";
|
|
|
34
36
|
export { LambderApiRefusal, refuse, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
35
37
|
export type { LambderRefusalMessage, LambderDeclaredRefuse, LambderDeclaredRefuseOptions } from "./shared/wire/LambderApiRefusal.js";
|
|
36
38
|
export { LambderApiRefusalValidationError } from "./api/LambderApiRefusals.js";
|
|
39
|
+
export type { LambderRefusalVocabularyOption, LambderRefusalVocabularyOptionChecks } from "./api/LambderApiRefusals.js";
|
|
37
40
|
export { assertApiSuccess, assertApiFailure, assertApiRefusal } from "./shared/wire/LambderOutcomeAssertions.js";
|
|
38
41
|
export type { LambderExpectedFailure } from "./shared/wire/LambderOutcomeAssertions.js";
|
|
39
42
|
export type { LambderApiRequest } from "./api/LambderApiRequest.js";
|
package/dist/mock.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* production bundle by construction: nothing else imports it.
|
|
8
8
|
*/
|
|
9
9
|
export { LambderMockApp, initLambderMock } from "./mock/LambderMockApp.js";
|
|
10
|
+
// A part of the mock app with mock guards of its own, declared with its entries.
|
|
11
|
+
export { LambderMockApiModule } from "./mock/LambderMockApiModule.js";
|
|
10
12
|
// The four collaborators behind LambderMockApp (the entry registry, the call
|
|
11
13
|
// recorder, the failure injector, the browser cookies) are deliberately not
|
|
12
14
|
// exported: the runtime is reached through the app. Only the error a
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* writes the synchronous form when it has nothing to await, and the framework
|
|
12
12
|
* awaits either.
|
|
13
13
|
*/
|
|
14
|
-
export type
|
|
14
|
+
export type LambderMaybePromise<T> = T | Promise<T>;
|
|
15
15
|
/**
|
|
16
16
|
* T with every object and array readonly, all the way down: what an API
|
|
17
17
|
* handler's returned answer is checked against.
|