lambder 11.0.1 → 12.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 +154 -0
- package/README.md +18 -13
- package/dist/api/LambderApiDeclarations.d.ts +194 -0
- package/dist/api/LambderApiDeclarations.js +52 -0
- package/dist/api/LambderApiDefinition.d.ts +1 -1
- package/dist/api/LambderApiGuards.d.ts +21 -22
- package/dist/api/LambderApiGuards.js +1 -1
- package/dist/api/LambderApiRateLimits.d.ts +19 -19
- package/dist/api/LambderApiRateLimits.js +1 -1
- package/dist/api/LambderApiRequest.d.ts +15 -6
- package/dist/api/LambderApiRequest.js +8 -9
- package/dist/build/generatedTables.d.ts +2 -1
- package/dist/build/generatedTables.js +1 -1
- package/dist/client/LambderCaller.d.ts +36 -23
- package/dist/client/LambderCaller.js +88 -67
- package/dist/client/lambderFetchTransport.js +2 -1
- package/dist/client.d.ts +3 -1
- package/dist/client.js +2 -0
- package/dist/core/Lambder.d.ts +203 -87
- package/dist/core/Lambder.js +265 -86
- package/dist/core/LambderContext.d.ts +1 -1
- package/dist/core/LambderContext.js +13 -5
- package/dist/core/LambderCreateOptions.d.ts +28 -39
- package/dist/core/LambderCreateOptions.js +2 -3
- package/dist/core/LambderPublicFiles.d.ts +8 -1
- package/dist/core/LambderPublicFiles.js +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +2 -0
- package/dist/invoke/LambderInvokeCaller.d.ts +48 -31
- package/dist/invoke/LambderInvokeCaller.js +110 -94
- package/dist/invoke/LambderLambdaEvent.d.ts +0 -1
- package/dist/invoke/LambderLambdaEvent.js +0 -1
- package/dist/invoke/lambderHandlerTransport.js +2 -1
- package/dist/mock/LambderMockApp.d.ts +31 -25
- package/dist/mock/LambderMockApp.js +52 -45
- package/dist/mock/LambderMockCreateOptions.d.ts +6 -7
- package/dist/mock/LambderMockTypes.d.ts +44 -7
- package/dist/mock/lambderMockInvokeTransport.d.ts +9 -1
- package/dist/mock/lambderMockInvokeTransport.js +15 -7
- package/dist/mock/lambderMockMswHandler.js +8 -4
- package/dist/mock.d.ts +2 -2
- package/dist/shared/transport/LambderApiTransport.d.ts +3 -2
- package/dist/shared/transport/LambderApiTransport.js +2 -3
- package/dist/shared/transport/lambderCookieJarTransport.js +2 -1
- package/dist/shared/util/LambderTypeUtilities.d.ts +1 -1
- package/dist/shared/wire/LambderApiContract.d.ts +4 -18
- package/dist/shared/wire/LambderApiContract.js +1 -1
- package/dist/shared/wire/LambderApiGroupCalls.d.ts +31 -0
- package/dist/shared/wire/LambderApiGroupCalls.js +68 -0
- package/dist/shared/wire/LambderApiNames.d.ts +55 -0
- package/dist/shared/wire/LambderApiNames.js +81 -0
- package/dist/shared/wire/LambderUploadSchemas.js +2 -1
- package/dist/testing/LambderTestApp.d.ts +5 -1
- package/dist/testing/LambderTestVisitor.d.ts +16 -8
- package/dist/testing/LambderTestVisitor.js +33 -25
- package/package.json +1 -1
|
@@ -93,6 +93,8 @@ export class LambderMockApp {
|
|
|
93
93
|
registry = new LambderMockEntryRegistry();
|
|
94
94
|
/** The server's declared options per API, when create() was given the generated table; the entries' declarations come from here. */
|
|
95
95
|
apiOptions;
|
|
96
|
+
/** 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. */
|
|
97
|
+
mockGuards;
|
|
96
98
|
/** The server's guard declarations, when create() was given the generated table: the refusal codes a declared guard adds to an entry. */
|
|
97
99
|
guardDeclarations;
|
|
98
100
|
/** The server's refusal vocabulary, when create() was given the generated table: whether each code an entry may refuse with carries data. */
|
|
@@ -106,6 +108,7 @@ export class LambderMockApp {
|
|
|
106
108
|
declared = null) {
|
|
107
109
|
this.apiVersion = options.apiVersion ?? null;
|
|
108
110
|
this.apiOptions = options.apiOptions ?? null;
|
|
111
|
+
this.mockGuards = options.guards ?? null;
|
|
109
112
|
this.guardDeclarations = options.guardDeclarations ?? null;
|
|
110
113
|
this.refusalVocabulary = declared?.vocabulary ?? null;
|
|
111
114
|
this.requireRefusalCodes = declared?.requireCodes ?? false;
|
|
@@ -220,23 +223,33 @@ export class LambderMockApp {
|
|
|
220
223
|
}
|
|
221
224
|
/**
|
|
222
225
|
* The table's entry for an endpoint, when create() was given one: null
|
|
223
|
-
* without a table, and a throw for a name the table does not hold
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* the compiler did not see.
|
|
226
|
+
* without a table, and a throw for a name the table does not hold. The
|
|
227
|
+
* compiler already refuses it against the contract; this is where a
|
|
228
|
+
* stale table meets a caller the compiler did not see.
|
|
227
229
|
*/
|
|
228
|
-
declaredOptionsOf(name
|
|
230
|
+
declaredOptionsOf(name) {
|
|
229
231
|
if (!this.apiOptions)
|
|
230
232
|
return null;
|
|
231
233
|
const declared = Object.prototype.hasOwnProperty.call(this.apiOptions, name) ? this.apiOptions[name] : undefined;
|
|
232
234
|
if (!declared) {
|
|
233
235
|
throw new Error(`LambderMockApp: "${name}" has no entry in the apiOptions table given to create(). The table predates this endpoint: regenerate it with writeApiOptions.`);
|
|
234
236
|
}
|
|
235
|
-
if (declared.mode !== mode) {
|
|
236
|
-
throw new Error(`LambderMockApp: "${name}" is a ${declared.mode} endpoint in the apiOptions table, registered here as a ${mode} one.`);
|
|
237
|
-
}
|
|
238
237
|
return declared;
|
|
239
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* An entry's mode, by the server's rule: a session endpoint when one of
|
|
241
|
+
* its guards needs a session. The guards are the apiOptions table's when
|
|
242
|
+
* create() was given one (which records the mode it came to as well), and
|
|
243
|
+
* otherwise the ones the entry restates, each read off the guardDeclarations
|
|
244
|
+
* table or the mock's own guards.
|
|
245
|
+
*/
|
|
246
|
+
modeOf(declared, restatedGuards) {
|
|
247
|
+
if (declared)
|
|
248
|
+
return declared.mode;
|
|
249
|
+
const needsSession = (guard) => (this.guardDeclarations && Object.prototype.hasOwnProperty.call(this.guardDeclarations, guard) && this.guardDeclarations[guard].session === true)
|
|
250
|
+
|| (this.mockGuards && Object.prototype.hasOwnProperty.call(this.mockGuards, guard) && this.mockGuards[guard].session === true);
|
|
251
|
+
return toGuardEntries(restatedGuards).some(({ name: guard }) => needsSession(guard)) ? "session" : "public";
|
|
252
|
+
}
|
|
240
253
|
/**
|
|
241
254
|
* The codes an entry may refuse with, read off the generated tables (its
|
|
242
255
|
* own from apiOptions, its guards' from guardDeclarations), each resolved
|
|
@@ -276,9 +289,10 @@ export class LambderMockApp {
|
|
|
276
289
|
return null;
|
|
277
290
|
return this.apiOptions[name].mode;
|
|
278
291
|
}
|
|
279
|
-
buildEntry(name,
|
|
292
|
+
buildEntry(name, input) {
|
|
280
293
|
const options = (typeof input === "function" ? { handler: input } : input);
|
|
281
|
-
const declared = this.declaredOptionsOf(name
|
|
294
|
+
const declared = this.declaredOptionsOf(name);
|
|
295
|
+
const mode = this.modeOf(declared, options.guards);
|
|
282
296
|
if (declared) {
|
|
283
297
|
// A caller the compiler did not see (a JavaScript slice, a cast)
|
|
284
298
|
// would otherwise have its restatement silently lose to the table.
|
|
@@ -306,32 +320,34 @@ export class LambderMockApp {
|
|
|
306
320
|
const handler = options.handler;
|
|
307
321
|
return { name, mode, definition, handler: async (ctx) => await handler(ctx), notMockedReason: null };
|
|
308
322
|
}
|
|
309
|
-
/** A mock for a public endpoint: a handler, or the handler with the endpoint's declarations restated. */
|
|
310
|
-
publicApi(name, entry) {
|
|
311
|
-
return this.buildEntry(name, "public", entry);
|
|
312
|
-
}
|
|
313
|
-
/** A mock for a session endpoint: the pipeline fetches the session before the handler runs, and refuses without one. */
|
|
314
|
-
sessionApi(name, entry) {
|
|
315
|
-
return this.buildEntry(name, "session", entry);
|
|
316
|
-
}
|
|
317
323
|
/**
|
|
318
|
-
* A
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
* do, because the refusal runs through the pipeline so the steps BEFORE
|
|
323
|
-
* dispatch still happen, and the session read is one of them. Were every
|
|
324
|
-
* not-mocked endpoint public, a session endpoint with no session would
|
|
325
|
-
* answer "not mocked" where the server answers sessionExpired, and its
|
|
326
|
-
* events and call log would carry the wrong mode. The contract is a type,
|
|
327
|
-
* so the mode cannot be recovered at runtime: the builder has to say it.
|
|
324
|
+
* A mock for an endpoint: a handler, or the handler with the endpoint's
|
|
325
|
+
* declarations restated. Its mode is the server's (see modeOf): a
|
|
326
|
+
* session endpoint's session is fetched before the handler runs, and a
|
|
327
|
+
* call without one is refused as on the server.
|
|
328
328
|
*/
|
|
329
|
-
|
|
330
|
-
return this.
|
|
329
|
+
api(name, entry) {
|
|
330
|
+
return this.buildEntry(name, entry);
|
|
331
331
|
}
|
|
332
|
-
/**
|
|
333
|
-
|
|
334
|
-
|
|
332
|
+
/**
|
|
333
|
+
* An endpoint deliberately left without a mock; a call answers the
|
|
334
|
+
* notMocked refusal carrying the reason. The refusal runs through the
|
|
335
|
+
* pipeline, so the steps before dispatch still happen, the session read
|
|
336
|
+
* among them, under the server's mode (see modeOf): a signed-out call to
|
|
337
|
+
* a session endpoint answers sessionExpired as on the server. Without the
|
|
338
|
+
* apiOptions table a session endpoint restates its guards to say so,
|
|
339
|
+
* `notMocked(name, { reason, guards })`; no guard runs.
|
|
340
|
+
*/
|
|
341
|
+
notMocked(name, input) {
|
|
342
|
+
const { reason, guards } = (typeof input === "string" ? { reason: input } : input);
|
|
343
|
+
const declared = this.declaredOptionsOf(name);
|
|
344
|
+
if (declared && guards !== undefined) {
|
|
345
|
+
throw new Error(`LambderMockApp: "${name}" restates its guards option, which the apiOptions table given to create() already declares. Leave it out of the entry.`);
|
|
346
|
+
}
|
|
347
|
+
const mode = this.modeOf(declared, guards);
|
|
348
|
+
const definition = { name, mode };
|
|
349
|
+
this.assertEntryRegistration(definition);
|
|
350
|
+
return { name, mode, definition, handler: null, notMockedReason: reason };
|
|
335
351
|
}
|
|
336
352
|
/**
|
|
337
353
|
* "Everything I did not register is not mocked, for this reason", as an
|
|
@@ -356,18 +372,12 @@ export class LambderMockApp {
|
|
|
356
372
|
* a type), and a call it answers is processed as public: the protocol's
|
|
357
373
|
* pre-pass still runs, so a stale client still hears versionExpired, but
|
|
358
374
|
* a signed-out call to an unmocked session endpoint answers "not mocked"
|
|
359
|
-
* where the server answers sessionExpired.
|
|
360
|
-
*
|
|
375
|
+
* where the server answers sessionExpired. Give create() the table where
|
|
376
|
+
* a test cares about that path.
|
|
361
377
|
*/
|
|
362
378
|
restNotMocked(reason) {
|
|
363
379
|
return { restNotMockedReason: reason };
|
|
364
380
|
}
|
|
365
|
-
buildNotMockedEntry(name, mode, reason) {
|
|
366
|
-
this.declaredOptionsOf(name, mode);
|
|
367
|
-
const definition = { name, mode };
|
|
368
|
-
this.assertEntryRegistration(definition);
|
|
369
|
-
return { name, mode, definition, handler: null, notMockedReason: reason };
|
|
370
|
-
}
|
|
371
381
|
/** The entries of one module as a slice, keyed by name. Two entries for one endpoint is an error here. */
|
|
372
382
|
apiSlice(...entries) {
|
|
373
383
|
const slice = {};
|
|
@@ -809,10 +819,7 @@ export class LambderMockApp {
|
|
|
809
819
|
// the "saved" record with no call), a Date would stay a Date and a
|
|
810
820
|
// key set to undefined keep its place, none of which a server sees.
|
|
811
821
|
const envelope = JSON.parse(JSON.stringify(buildTransportEnvelope(transportRequest)));
|
|
812
|
-
|
|
813
|
-
if (!request)
|
|
814
|
-
throw new Error("LambderMockApp: the transport request names no api.");
|
|
815
|
-
return request;
|
|
822
|
+
return readApiEnvelope(envelope, info, transportRequest.apiName);
|
|
816
823
|
}
|
|
817
824
|
/** One call from a transport request to its answer: what the mock transport and the adapters call. */
|
|
818
825
|
async handle(transportRequest) {
|
|
@@ -16,7 +16,7 @@ import type { LambderSessionStore } from "../shared/contracts/LambderSessionStor
|
|
|
16
16
|
import type { LambderSessionDataRefreshConfig } from "../session/LambderSessionManager.js";
|
|
17
17
|
import type { LambderSessionCookieOptions } from "../session/LambderSessionController.js";
|
|
18
18
|
import type { LambderSessionCrypto } from "../session/LambderSessionCrypto.js";
|
|
19
|
-
import type { LambderMockCallContext, LambderMockGuards, LambderMockLatency, LambderMockRateLimitPolicies, LambderMockSessionCallContext, LambderMockSurplusKeys } from "./LambderMockTypes.js";
|
|
19
|
+
import type { LambderMockCallContext, LambderMockGuards, LambderMockLatency, LambderMockRateLimitPolicies, LambderMockSessionCallContext, LambderMockSessionGuardsCheck, LambderMockSurplusKeys } from "./LambderMockTypes.js";
|
|
20
20
|
export type LambderMockSessionsOptions<S> = {
|
|
21
21
|
/** Where the mock's sessions rest. Default: a fresh LambderMemorySessionStore. */
|
|
22
22
|
store?: LambderSessionStore<S>;
|
|
@@ -158,9 +158,9 @@ type LambderMockGuardsAgree<G, D> = {
|
|
|
158
158
|
type LambderMockGuardsOption<C, S, G, D> = [
|
|
159
159
|
LambderContractGuardNames<C>
|
|
160
160
|
] extends [never] ? {
|
|
161
|
-
guards?: G & LambderMockGuards<C, S> & LambderMockGuardShapes<S, G> & LambderMockGuardsAgree<G, NoInfer<D
|
|
161
|
+
guards?: G & LambderMockGuards<C, S> & LambderMockGuardShapes<S, G> & LambderMockGuardsAgree<G, NoInfer<D>> & LambderMockSessionGuardsCheck<C, G>;
|
|
162
162
|
} : {
|
|
163
|
-
guards: G & LambderMockGuards<C, S> & LambderMockGuardShapes<S, G> & LambderMockGuardsAgree<G, NoInfer<D
|
|
163
|
+
guards: G & LambderMockGuards<C, S> & LambderMockGuardShapes<S, G> & LambderMockGuardsAgree<G, NoInfer<D>> & LambderMockSessionGuardsCheck<C, G>;
|
|
164
164
|
};
|
|
165
165
|
/**
|
|
166
166
|
* The sessions, idempotency and rateLimits options: each required whenever
|
|
@@ -225,10 +225,9 @@ export type LambderMockAppOptions<C, S, G, P extends LambderMockRateLimitPolicie
|
|
|
225
225
|
* exports them (`apiOptions`). Given, every entry's guards, rateLimit and
|
|
226
226
|
* idempotency are read off the table rather than restated: an entry is
|
|
227
227
|
* its handler (and an input schema, if it has one), a restated option is
|
|
228
|
-
* a compile error, and
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
* mocks still reads the session first. The table has to cover the
|
|
228
|
+
* a compile error, and the entry takes the table's mode. A notMocked
|
|
229
|
+
* entry and a restNotMocked answer read the endpoint's mode off the table
|
|
230
|
+
* too, so a session endpoint nothing mocks still reads the session first. The table has to cover the
|
|
232
231
|
* contract (see LambderMockApiOptionsCover).
|
|
233
232
|
*/
|
|
234
233
|
apiOptions?: A & LambderMockApiOptionsCover<C>;
|
|
@@ -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, LambderContractKeysWithMode, LambderContractMode, LambderContractRateLimitOf } from "../shared/wire/LambderApiContract.js";
|
|
9
|
+
import type { LambderApiMode, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractGuardInputsOf, LambderContractGuardNames, LambderContractGuardsOf, LambderContractIdempotencyOf, LambderContractKeysWithMode, 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";
|
|
@@ -105,8 +105,9 @@ export type LambderMockContext<C, K extends keyof C, S, G, TVocabulary = never,
|
|
|
105
105
|
* the contract declares, plus, for each guardInput-mode guard, a `guardInput`
|
|
106
106
|
* schema whose input is what the contract says a client sends. A guard a
|
|
107
107
|
* public endpoint names may not require a session. A missing name, a schema
|
|
108
|
-
* that parses to something else, or
|
|
109
|
-
* `guards` option
|
|
108
|
+
* that parses to something else, or a public endpoint's guard that requires
|
|
109
|
+
* a session fails at the `guards` option; LambderMockSessionGuardsCheck
|
|
110
|
+
* holds the session endpoints to the same rule from the other side.
|
|
110
111
|
*/
|
|
111
112
|
export type LambderMockGuards<C, S = any> = {
|
|
112
113
|
[N in LambderContractGuardNames<C>]: LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>;
|
|
@@ -119,6 +120,33 @@ export type LambderMockGuards<C, S = any> = {
|
|
|
119
120
|
session?: false;
|
|
120
121
|
};
|
|
121
122
|
};
|
|
123
|
+
/** The guards of a mock guard map that need a session. */
|
|
124
|
+
type LambderMockSessionGuardNames<G> = {
|
|
125
|
+
[N in keyof G]: G[N] extends {
|
|
126
|
+
session: true;
|
|
127
|
+
} ? N : never;
|
|
128
|
+
}[keyof G];
|
|
129
|
+
/** The contract's session endpoints none of whose guards the mock guard map declares `session: true`. */
|
|
130
|
+
type LambderMockSessionEndpointsWithoutSessionGuard<C, G> = {
|
|
131
|
+
[K in LambderContractKeysWithMode<C, "session">]: [
|
|
132
|
+
Extract<LambderGuardNamesIn<LambderContractGuardsOf<C, K>>, LambderMockSessionGuardNames<G>>
|
|
133
|
+
] extends [never] ? K : never;
|
|
134
|
+
}[LambderContractKeysWithMode<C, "session">];
|
|
135
|
+
/**
|
|
136
|
+
* The mock guard map held to the server's rule for session endpoints: each
|
|
137
|
+
* names at least one guard that needs a session. The guards are what make
|
|
138
|
+
* an endpoint a session one, on the server and in the mock alike, and
|
|
139
|
+
* without the apiOptions table the mock reads an entry's mode off the guards
|
|
140
|
+
* it restates, so a session endpoint none of whose mock guards says
|
|
141
|
+
* `session: true` would run as public: a signed-out call would reach a
|
|
142
|
+
* handler typed with its session present. The property name is the message
|
|
143
|
+
* and its value the endpoints at fault.
|
|
144
|
+
*/
|
|
145
|
+
export type LambderMockSessionGuardsCheck<C, G> = [
|
|
146
|
+
LambderMockSessionEndpointsWithoutSessionGuard<C, G>
|
|
147
|
+
] extends [never] ? unknown : {
|
|
148
|
+
readonly "lambder: these session endpoints name no mock guard declared session: true, so the mock would run them as public": LambderMockSessionEndpointsWithoutSessionGuard<C, G>;
|
|
149
|
+
};
|
|
122
150
|
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>>;
|
|
123
151
|
/**
|
|
124
152
|
* The guards field of an entry: required whenever the contract declares any
|
|
@@ -230,7 +258,7 @@ export type LambderMockEntryOptions<C, K extends keyof C, S, G, TInputSchema ext
|
|
|
230
258
|
handler: LambderMockHandler<C, K, S, G, TVocabulary, TCodesRequired>;
|
|
231
259
|
};
|
|
232
260
|
/**
|
|
233
|
-
* What
|
|
261
|
+
* What api() accepts. On a mock created with the generated
|
|
234
262
|
* `apiOptions` table (`TDerived`), a bare handler or the options without the
|
|
235
263
|
* declarations, for every endpoint. Otherwise a bare handler only for an
|
|
236
264
|
* endpoint the contract declares nothing for, the full options elsewhere, so
|
|
@@ -240,6 +268,18 @@ export type LambderMockEntryOptions<C, K extends keyof C, S, G, TInputSchema ext
|
|
|
240
268
|
* bare form.
|
|
241
269
|
*/
|
|
242
270
|
export type LambderMockEntryInput<C, K extends keyof C, S, G, TInputSchema extends z.ZodType = z.ZodType, TDerived extends boolean = false, TVocabulary = never, TCodesRequired extends boolean = false> = TDerived extends true ? LambderMockHandler<C, K, S, G, TVocabulary, TCodesRequired> | LambderMockEntryOptions<C, K, S, G, TInputSchema, true, TVocabulary, TCodesRequired> : [LambderContractGuardsOf<C, K> | LambderContractRateLimitOf<C, K> | LambderContractIdempotencyOf<C, K>] extends [never] ? LambderMockHandler<C, K, S, G, TVocabulary, TCodesRequired> | LambderMockEntryOptions<C, K, S, G, TInputSchema, false, TVocabulary, TCodesRequired> : LambderMockEntryOptions<C, K, S, G, TInputSchema, false, TVocabulary, TCodesRequired>;
|
|
271
|
+
/**
|
|
272
|
+
* What notMocked() accepts: the reason, or, for a session endpoint on a mock
|
|
273
|
+
* created without the generated `apiOptions` table, the reason with the
|
|
274
|
+
* endpoint's guards restated. The entry answers before any guard runs, but
|
|
275
|
+
* its mode is still the server's (the session read before the notMocked
|
|
276
|
+
* refusal), and without the table only the guards can say it, as they do
|
|
277
|
+
* for api().
|
|
278
|
+
*/
|
|
279
|
+
export type LambderMockNotMockedInput<C, K extends keyof C, TDerived extends boolean = false> = TDerived extends true ? string : LambderContractMode<C, K> extends "session" ? {
|
|
280
|
+
reason: string;
|
|
281
|
+
guards: LambderContractGuardsOf<C, K>;
|
|
282
|
+
} : string;
|
|
243
283
|
/** One registry entry: the endpoint's definition as the pipeline runs it, and its handler (null when registered as not mocked). */
|
|
244
284
|
export type LambderMockEntry<C, K extends keyof C & string> = {
|
|
245
285
|
readonly name: K;
|
|
@@ -248,9 +288,6 @@ export type LambderMockEntry<C, K extends keyof C & string> = {
|
|
|
248
288
|
readonly handler: ((ctx: any) => Promise<unknown>) | null;
|
|
249
289
|
readonly notMockedReason: string | null;
|
|
250
290
|
};
|
|
251
|
-
/** The endpoint names a mock app may register under each mode. */
|
|
252
|
-
export type LambderMockPublicNames<C> = LambderContractKeysWithMode<C, "public">;
|
|
253
|
-
export type LambderMockSessionNames<C> = LambderContractKeysWithMode<C, "session">;
|
|
254
291
|
/** A slice: the entries of one module, keyed by endpoint name. */
|
|
255
292
|
export type LambderMockSlice<C, K extends keyof C & string> = {
|
|
256
293
|
readonly [P in K]: LambderMockEntry<C, P>;
|
|
@@ -12,17 +12,25 @@ import { type LambderApiRequest } from "../api/LambderApiRequest.js";
|
|
|
12
12
|
* returned function still fits LambderInvokeTransport.
|
|
13
13
|
*/
|
|
14
14
|
export type LambderMockInvokeEvent = {
|
|
15
|
+
/** The path the call was made to, `{apiPath}/{group}/{action}`. */
|
|
16
|
+
rawPath?: string | undefined;
|
|
15
17
|
body?: string | undefined;
|
|
16
18
|
isBase64Encoded?: boolean | undefined;
|
|
17
19
|
headers?: Record<string, string | undefined> | undefined;
|
|
18
20
|
cookies?: string[] | undefined;
|
|
19
21
|
requestContext?: {
|
|
20
22
|
http?: {
|
|
23
|
+
method?: string;
|
|
21
24
|
sourceIp?: string;
|
|
22
25
|
} | undefined;
|
|
23
26
|
domainName?: string;
|
|
24
27
|
} | undefined;
|
|
25
28
|
};
|
|
29
|
+
/** The options of lambderMockInvokeTransport. */
|
|
30
|
+
export type LambderMockInvokeTransportOptions = {
|
|
31
|
+
/** The apiPath the mocked function serves its calls under, as its LambderInvokeCaller names it. Default: "/api", as there. */
|
|
32
|
+
apiPath?: string;
|
|
33
|
+
};
|
|
26
34
|
/** What the callee answers with: the Lambda response object LambderInvokeCaller decodes. */
|
|
27
35
|
export type LambderMockInvokeResult = {
|
|
28
36
|
functionError: string | null;
|
|
@@ -43,6 +51,6 @@ export type LambderMockInvokeResult = {
|
|
|
43
51
|
*/
|
|
44
52
|
export declare const lambderMockInvokeTransport: (mockApp: {
|
|
45
53
|
handleRequest(request: LambderApiRequest): Promise<LambderApiAnswer>;
|
|
46
|
-
}) => ((event: LambderMockInvokeEvent, options: {
|
|
54
|
+
}, options?: LambderMockInvokeTransportOptions) => ((event: LambderMockInvokeEvent, options: {
|
|
47
55
|
signal?: AbortSignal;
|
|
48
56
|
}) => Promise<LambderMockInvokeResult>);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { apiNameOfCallPath } from "../shared/wire/LambderApiNames.js";
|
|
2
|
+
import { DEFAULT_API_PATH } from "../shared/wire/LambderDefaultApiPath.js";
|
|
1
3
|
import { readApiEnvelope, cookieValuesByName, isApiCallContentType, lowercaseHeaderNames } from "../api/LambderApiRequest.js";
|
|
2
4
|
import { getAnswerHeader } from "../shared/wire/LambderAnswerHeaders.js";
|
|
3
5
|
import { base64ToText } from "../shared/util/LambderBase64.js";
|
|
@@ -9,7 +11,8 @@ import { normalizeClientIp } from "../shared/util/LambderClientIp.js";
|
|
|
9
11
|
* can then point its typed invoke caller at a mock of the function it
|
|
10
12
|
* depends on, with the same registry a browser test uses.
|
|
11
13
|
*/
|
|
12
|
-
export const lambderMockInvokeTransport = (mockApp) => async (event, { signal }) => {
|
|
14
|
+
export const lambderMockInvokeTransport = (mockApp, options = {}) => async (event, { signal }) => {
|
|
15
|
+
const apiPath = options.apiPath ?? DEFAULT_API_PATH;
|
|
13
16
|
const rawBody = event.body ?? "";
|
|
14
17
|
const body = event.isBase64Encoded ? base64ToText(rawBody) : rawBody;
|
|
15
18
|
let post = {};
|
|
@@ -21,16 +24,21 @@ export const lambderMockInvokeTransport = (mockApp) => async (event, { signal })
|
|
|
21
24
|
}
|
|
22
25
|
const headers = lowercaseHeaderNames(event.headers);
|
|
23
26
|
const cookies = cookieValuesByName(event.cookies ?? []);
|
|
24
|
-
// A POST
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
27
|
+
// A JSON POST to `{apiPath}/{group}/{action}` is a call, as the server's
|
|
28
|
+
// createContext reads it; anything else (another method or type, a path
|
|
29
|
+
// outside apiPath) is no API call on the server either, and a mock
|
|
30
|
+
// serves no routes. The address is the one the synthesized event carries
|
|
31
|
+
// in sourceIp, as createContext reads it; no forwarding header is trusted
|
|
32
|
+
// here either, so a per-IP limit keys the same address under both adapters.
|
|
33
|
+
const apiName = event.requestContext?.http?.method === "POST" && isApiCallContentType(headers)
|
|
34
|
+
? apiNameOfCallPath(apiPath, event.rawPath ?? "")
|
|
35
|
+
: null;
|
|
36
|
+
const request = apiName !== null && readApiEnvelope(post, {
|
|
29
37
|
headers, cookies,
|
|
30
38
|
ip: normalizeClientIp(event.requestContext?.http?.sourceIp ?? ""),
|
|
31
39
|
host: headers.host || event.requestContext?.domainName || "lambder-invoke",
|
|
32
40
|
...(signal ? { signal } : {}),
|
|
33
|
-
});
|
|
41
|
+
}, apiName);
|
|
34
42
|
if (!request) {
|
|
35
43
|
return { functionError: null, result: { statusCode: 404, headers: { "content-type": "text/plain; charset=utf-8" }, body: "Not found.", isBase64Encoded: false } };
|
|
36
44
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LambderMockTransportError } from "./LambderMockFailureInjector.js";
|
|
2
|
+
import { apiNameOfCallPath } from "../shared/wire/LambderApiNames.js";
|
|
2
3
|
import { readApiEnvelope, cookieValuesByName, isApiCallContentType, lowercaseHeaderNames } from "../api/LambderApiRequest.js";
|
|
3
4
|
import { getAnswerHeader } from "../shared/wire/LambderAnswerHeaders.js";
|
|
4
5
|
import { LambderCookieJar } from "../shared/transport/LambderCookieJar.js";
|
|
@@ -50,7 +51,9 @@ export const lambderMockMswHandler = (mockApp, options) => {
|
|
|
50
51
|
// adapter's is, so one address is one counter under a `per: "ip"` limit
|
|
51
52
|
// however it was spelled.
|
|
52
53
|
const clientIp = normalizeClientIp(options.clientIp ?? mockApp.defaultClientIp);
|
|
53
|
-
|
|
54
|
+
// Every call goes to `{apiPath}/{group}/{action}`; the endpoint is read
|
|
55
|
+
// off the path as the server reads it (apiNameOfCallPath).
|
|
56
|
+
const handler = msw.http.post(`${apiPath.replace(/\/+$/, "")}/:group/:action`, async ({ request }) => {
|
|
54
57
|
let post;
|
|
55
58
|
try {
|
|
56
59
|
post = await request.clone().json();
|
|
@@ -91,11 +94,12 @@ export const lambderMockMswHandler = (mockApp, options) => {
|
|
|
91
94
|
(cookies[name] ??= []).push(value);
|
|
92
95
|
}
|
|
93
96
|
}
|
|
97
|
+
const apiName = apiNameOfCallPath(new URL(apiPath, url).pathname, url.pathname);
|
|
98
|
+
if (apiName === null)
|
|
99
|
+
return undefined;
|
|
94
100
|
const parsed = readApiEnvelope(post, {
|
|
95
101
|
headers, cookies, ip: clientIp, host: url.host, signal: request.signal,
|
|
96
|
-
});
|
|
97
|
-
if (!parsed)
|
|
98
|
-
return undefined;
|
|
102
|
+
}, apiName);
|
|
99
103
|
// Returning undefined hands the request back to MSW, which tries its
|
|
100
104
|
// other handlers and then the network. Noted on the runtime first:
|
|
101
105
|
// a passthrough that leaves no event and no call-log row is a
|
package/dist/mock.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
export { LambderMockApp, initLambderMock } from "./mock/LambderMockApp.js";
|
|
10
10
|
export { LambderMockTransportError } from "./mock/LambderMockFailureInjector.js";
|
|
11
11
|
export type { LambderMockAppOptions, LambderMockSessionsOptions, LambderMockIdempotencyOptions, LambderMockInvalidInputAnswer, LambderMockTransport, LambderMockTransportOptions } from "./mock/LambderMockCreateOptions.js";
|
|
12
|
-
export type { LambderMockCallContext, LambderMockSessionCallContext, LambderMockContext, LambderMockRefusalsOf, LambderMockGuards, LambderMockHandler, LambderMockEntry, LambderMockEntryOptions, LambderMockEntryInput, LambderMockSlice, LambderMockRestEntry, LambderMockRegistryCheck, LambderMockMissingNames, LambderMockStrayNames, LambderMockDuplicateNames,
|
|
12
|
+
export type { LambderMockCallContext, LambderMockSessionCallContext, LambderMockContext, LambderMockRefusalsOf, LambderMockGuards, LambderMockHandler, LambderMockEntry, LambderMockEntryOptions, LambderMockEntryInput, LambderMockNotMockedInput, LambderMockSessionGuardsCheck, LambderMockSlice, LambderMockRestEntry, LambderMockRegistryCheck, LambderMockMissingNames, LambderMockStrayNames, LambderMockDuplicateNames, LambderMockLatency, LambderMockFailure, LambderMockFailureReason, LambderMockOutcome, LambderMockCallEvent, LambderMockRequestEvent, LambderMockResponseEvent, LambderMockCallRecord, LambderMockListener, LambderMockRateLimitPolicies, LambderMockInputOf, LambderMockOutputOf, LambderMockOverride, } from "./mock/LambderMockTypes.js";
|
|
13
13
|
export { lambderMockPoliciesFrom } from "./mock/lambderMockPoliciesFrom.js";
|
|
14
14
|
export type { LambderCustomKeyedPolicyNames, LambderMockPolicyKeys } from "./mock/lambderMockPoliciesFrom.js";
|
|
15
15
|
export type { LambderMockGuardShapeOf } from "./mock/LambderMockCreateOptions.js";
|
|
@@ -21,7 +21,7 @@ export { LambderMemoryUploadBucket } from "./stores/LambderMemoryUploadBucket.js
|
|
|
21
21
|
export type { LambderMemoryUploadBucketOptions, LambderMemoryUploadObject } from "./stores/LambderMemoryUploadBucket.js";
|
|
22
22
|
export { lambderMockUploadMswHandler } from "./mock/lambderMockUploadMswHandler.js";
|
|
23
23
|
export { lambderMockInvokeTransport } from "./mock/lambderMockInvokeTransport.js";
|
|
24
|
-
export type { LambderMockInvokeEvent, LambderMockInvokeResult } from "./mock/lambderMockInvokeTransport.js";
|
|
24
|
+
export type { LambderMockInvokeEvent, LambderMockInvokeResult, LambderMockInvokeTransportOptions } from "./mock/lambderMockInvokeTransport.js";
|
|
25
25
|
export { LambderCookieJar } from "./shared/transport/LambderCookieJar.js";
|
|
26
26
|
export { lambderCookieJarTransport } from "./shared/transport/lambderCookieJarTransport.js";
|
|
27
27
|
export type { LambderApiTransport, LambderApiTransportRequest } from "./shared/transport/LambderApiTransport.js";
|
|
@@ -8,6 +8,7 @@ import type { LambderCompressedGzipPayload, LambderCompressedBrotliPayload } fro
|
|
|
8
8
|
*/
|
|
9
9
|
export type LambderApiTransportRequest = {
|
|
10
10
|
apiPath: string;
|
|
11
|
+
/** The endpoint called, `group.action`: the call goes to `{apiPath}/{group}/{action}` (apiCallPath). */
|
|
11
12
|
apiName: string;
|
|
12
13
|
version?: string;
|
|
13
14
|
/** The caller's signature for this endpoint, out of its LambderApiSignatureMap; absent when it carries no map. */
|
|
@@ -90,7 +91,8 @@ export declare const isLambderTransportFailure: (err: unknown) => err is Lambder
|
|
|
90
91
|
export type LambderApiTransport = (request: LambderApiTransportRequest) => Promise<LambderApiHttpAnswer>;
|
|
91
92
|
/**
|
|
92
93
|
* The fields of the request envelope, in wire order: the one statement of
|
|
93
|
-
* what a call sends, for every sender.
|
|
94
|
+
* what a call sends, for every sender. The endpoint is not among them: it is
|
|
95
|
+
* the path the envelope is posted to (apiCallPath).
|
|
94
96
|
*
|
|
95
97
|
* Two senders write it. A transport hands the payload over as a value
|
|
96
98
|
* (buildTransportEnvelope, below); LambderInvokeCaller has already serialized
|
|
@@ -101,7 +103,6 @@ export type LambderApiTransport = (request: LambderApiTransportRequest) => Promi
|
|
|
101
103
|
* the wire would catch.
|
|
102
104
|
*/
|
|
103
105
|
export declare const buildEnvelopeFields: (fields: {
|
|
104
|
-
apiName: string;
|
|
105
106
|
version?: string;
|
|
106
107
|
signature?: string;
|
|
107
108
|
/** The CSRF token, as the envelope names it. */
|
|
@@ -16,7 +16,8 @@ export class LambderTransportFailure extends Error {
|
|
|
16
16
|
export const isLambderTransportFailure = (err) => err instanceof Error && err.isLambderTransportFailure === true;
|
|
17
17
|
/**
|
|
18
18
|
* The fields of the request envelope, in wire order: the one statement of
|
|
19
|
-
* what a call sends, for every sender.
|
|
19
|
+
* what a call sends, for every sender. The endpoint is not among them: it is
|
|
20
|
+
* the path the envelope is posted to (apiCallPath).
|
|
20
21
|
*
|
|
21
22
|
* Two senders write it. A transport hands the payload over as a value
|
|
22
23
|
* (buildTransportEnvelope, below); LambderInvokeCaller has already serialized
|
|
@@ -27,7 +28,6 @@ export const isLambderTransportFailure = (err) => err instanceof Error && err.is
|
|
|
27
28
|
* the wire would catch.
|
|
28
29
|
*/
|
|
29
30
|
export const buildEnvelopeFields = (fields) => ({
|
|
30
|
-
apiName: fields.apiName,
|
|
31
31
|
version: fields.version,
|
|
32
32
|
...(fields.signature !== undefined ? { signature: fields.signature } : {}),
|
|
33
33
|
token: fields.token,
|
|
@@ -42,7 +42,6 @@ export const buildEnvelopeFields = (fields) => ({
|
|
|
42
42
|
* runtime read one shape.
|
|
43
43
|
*/
|
|
44
44
|
export const buildTransportEnvelope = (request) => buildEnvelopeFields({
|
|
45
|
-
apiName: request.apiName,
|
|
46
45
|
version: request.version,
|
|
47
46
|
signature: request.signature,
|
|
48
47
|
token: request.token,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_SESSION_CSRF_COOKIE_KEY } from "../wire/LambderSessionCookieNames.js";
|
|
2
2
|
import { resolveApiPathTarget } from "./LambderApiTransport.js";
|
|
3
|
+
import { apiCallPath } from "../wire/LambderApiNames.js";
|
|
3
4
|
/**
|
|
4
5
|
* Makes any transport carry a cookie jar the way a browser carries its
|
|
5
6
|
* cookies: the jar's cookies ride on every request, the answer's Set-Cookie
|
|
@@ -36,7 +37,7 @@ export const lambderCookieJarTransport = (inner, options) => {
|
|
|
36
37
|
// absolute cross-origin apiPath. siteHost is "" outside a browser, and
|
|
37
38
|
// an empty host would scope every cookie to nothing while claiming to
|
|
38
39
|
// scope it.
|
|
39
|
-
const target = resolveApiPathTarget(request.apiPath);
|
|
40
|
+
const target = resolveApiPathTarget(apiCallPath(request.apiPath, request.apiName));
|
|
40
41
|
const cookieScope = {
|
|
41
42
|
host: target.host ?? options.host ?? (request.siteHost || undefined),
|
|
42
43
|
path: target.path,
|
|
@@ -35,7 +35,7 @@ export type LambderReadonlyDeep<T, TDepth extends unknown[] = []> = TDepth["leng
|
|
|
35
35
|
* name, of "this one required and the rest optional".
|
|
36
36
|
*
|
|
37
37
|
* An all-optional map is inhabited by `{}`, which would let `guards: {}`
|
|
38
|
-
* satisfy
|
|
38
|
+
* satisfy requireApiGuards at the type level
|
|
39
39
|
* while declaring no guard: the option is present, so the required-field
|
|
40
40
|
* check passes, and it normalizes to zero entries, so nothing runs. Requiring
|
|
41
41
|
* the chosen key also rejects `{ theGuard: undefined }`, which an optional
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Lambder API Contract System
|
|
3
3
|
*
|
|
4
|
-
* Contracts are
|
|
4
|
+
* Contracts are the endpoint declarations an instance registered, read with typeof lambder.ApiContract
|
|
5
5
|
*/
|
|
6
6
|
import type { LambderCrashDetail } from "./LambderCrashDetail.js";
|
|
7
7
|
import type { LambderUncheckedRefusalMessage, LambderPlainRefusalMessage, LambderRefusalMessage } from "./LambderApiRefusal.js";
|
|
8
8
|
import type { LambderApiIdempotencyOption, LambderGuardsOptionValue, LambderRateLimitOptionValue } from "./LambderApiOptionValues.js";
|
|
9
|
-
/** Whether an endpoint runs without a session or requires one
|
|
9
|
+
/** Whether an endpoint runs without a session or requires one: "session" exactly when one of its guards needs a session. */
|
|
10
10
|
export type LambderApiMode = "public" | "session";
|
|
11
11
|
/**
|
|
12
12
|
* Base shape for API contracts: what LambderCaller, LambderInvokeCaller and
|
|
@@ -15,7 +15,7 @@ export type LambderApiMode = "public" | "session";
|
|
|
15
15
|
export type LambderApiContractShape = Record<string, {
|
|
16
16
|
input: any;
|
|
17
17
|
output: any;
|
|
18
|
-
/** "public"
|
|
18
|
+
/** "public", or "session" when one of its guards needs a session. */
|
|
19
19
|
mode?: LambderApiMode;
|
|
20
20
|
/** Present when the API declares guardInput-mode guards: guard name -> value the client must send via options.guardInputs. */
|
|
21
21
|
guardInputs?: any;
|
|
@@ -149,7 +149,7 @@ export type LambderJsonOf<T> = unknown extends T ? T : T extends LambderJsonValu
|
|
|
149
149
|
[K in keyof TKeyed as K extends string | number ? (string extends K ? K : number extends K ? K : [LambderJsonOf<TKeyed[K]>] extends [never] ? never : K) : never]: LambderJsonOf<TKeyed[K]>;
|
|
150
150
|
} : never : never;
|
|
151
151
|
/**
|
|
152
|
-
* One contract entry as
|
|
152
|
+
* One contract entry as defineApi records it: the payload types,
|
|
153
153
|
* the mode, and every declarative option exactly as written. Options that
|
|
154
154
|
* were not written are absent rather than undefined, so `keyof` an entry
|
|
155
155
|
* lists only what the endpoint declared.
|
|
@@ -180,20 +180,6 @@ export type LambderContractEntry<In, Out, Mode extends LambderApiMode, GuardInpu
|
|
|
180
180
|
}) & ([Idempotency] extends [never] ? {} : {
|
|
181
181
|
idempotency: Idempotency;
|
|
182
182
|
});
|
|
183
|
-
/**
|
|
184
|
-
* Merges a new entry into the contract during chaining.
|
|
185
|
-
*
|
|
186
|
-
* The contract is therefore an intersection one member deep per endpoint, and
|
|
187
|
-
* every `C[K]` read generically (a typed caller, a mock registry, a test
|
|
188
|
-
* visitor) resolves the property across all of them. That costs nothing
|
|
189
|
-
* worth measuring in a small app and most of a large client's type check,
|
|
190
|
-
* which is what writeApiContract (lambder/build) is for: it writes the
|
|
191
|
-
* contract out as one object type with plain members, for clients to import
|
|
192
|
-
* instead of the server.
|
|
193
|
-
*/
|
|
194
|
-
export type LambderMergeContract<Old, Name extends string, Entry> = Old & {
|
|
195
|
-
[K in Name]: Entry;
|
|
196
|
-
};
|
|
197
183
|
/** Guard names referenced by a guards option, whichever of its three forms is used. */
|
|
198
184
|
export type LambderGuardNamesIn<TOpt> = TOpt extends string ? TOpt : TOpt extends readonly (infer N extends string)[] ? N : TOpt extends object ? keyof TOpt & string : never;
|
|
199
185
|
/** The endpoint's mode; a contract written without one admits either. */
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** The groups a contract's endpoint names hold: the part of each before its dot. */
|
|
2
|
+
export type LambderContractGroupsOf<TContract> = keyof TContract extends infer TName ? (TName extends `${infer TGroup}.${string}` ? TGroup : never) : never;
|
|
3
|
+
/** The endpoint names of one group. */
|
|
4
|
+
export type LambderContractNamesInGroup<TContract, TGroup extends string> = Extract<keyof TContract, `${TGroup}.${string}`> & string;
|
|
5
|
+
/** An endpoint name's action: the part after its dot. */
|
|
6
|
+
export type LambderContractActionOf<TName> = TName extends `${string}.${infer TAction}` ? TAction : never;
|
|
7
|
+
/** A call made through a group: the endpoint's name, and the arguments as the call site passed them. */
|
|
8
|
+
type LambderGroupCallRunner = (apiName: string, args: unknown[]) => unknown;
|
|
9
|
+
/**
|
|
10
|
+
* The caller, answering for each group a contract could name besides its
|
|
11
|
+
* own members. A member the caller has (a method, a field) is always the
|
|
12
|
+
* caller's; a name that could be a group (see isGroupName: an identifier no
|
|
13
|
+
* caller member has) is one; anything else a runtime or a framework asks of
|
|
14
|
+
* an object (then, toJSON, a symbol, `__v_isRef`) is what the caller itself
|
|
15
|
+
* answers, so a caller is never mistaken for a promise, a ref or a
|
|
16
|
+
* serializable record.
|
|
17
|
+
*
|
|
18
|
+
* A caller keeps its state in #private fields, which no property name
|
|
19
|
+
* reaches, so a group may take any name its public members leave free. Such
|
|
20
|
+
* a field is readable only on the caller itself, never through this proxy,
|
|
21
|
+
* so a member is read and a method run on the caller; a method that returns
|
|
22
|
+
* the caller (setTransport) hands back the proxy, groups and all.
|
|
23
|
+
*
|
|
24
|
+
* A group answers the same way one level down: an ordinary object's own
|
|
25
|
+
* members (toString, valueOf, hasOwnProperty) are an ordinary object's, so
|
|
26
|
+
* converting a group to a string or asking it what it holds calls no
|
|
27
|
+
* endpoint; any other name that could be an action (see isActionName) is
|
|
28
|
+
* one. A group is read-only, like the type that describes it.
|
|
29
|
+
*/
|
|
30
|
+
export declare const withApiGroupCalls: <TCaller extends object>(caller: TCaller, call: LambderGroupCallRunner, outcome: LambderGroupCallRunner) => TCaller;
|
|
31
|
+
export {};
|