lambder 6.0.1 → 7.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.
Files changed (195) hide show
  1. package/CHANGELOG.md +2316 -0
  2. package/README.md +60 -33
  3. package/dist/api/LambderApiAnswer.d.ts +40 -0
  4. package/dist/api/LambderApiAnswer.js +19 -0
  5. package/dist/api/LambderApiCallContext.d.ts +38 -0
  6. package/dist/api/LambderApiCallContext.js +13 -0
  7. package/dist/api/LambderApiDefinition.d.ts +18 -0
  8. package/dist/api/LambderApiDefinition.js +1 -0
  9. package/dist/api/LambderApiEnvelope.d.ts +67 -0
  10. package/dist/api/LambderApiEnvelope.js +180 -0
  11. package/dist/api/LambderApiGuards.d.ts +302 -0
  12. package/dist/api/LambderApiGuards.js +134 -0
  13. package/dist/api/LambderApiIdempotency.d.ts +122 -0
  14. package/dist/api/LambderApiIdempotency.js +330 -0
  15. package/dist/api/LambderApiPipeline.d.ts +134 -0
  16. package/dist/api/LambderApiPipeline.js +221 -0
  17. package/dist/api/LambderApiPolicyEngine.d.ts +36 -0
  18. package/dist/api/LambderApiPolicyEngine.js +77 -0
  19. package/dist/api/LambderApiRateLimits.d.ts +206 -0
  20. package/dist/api/LambderApiRateLimits.js +239 -0
  21. package/dist/api/LambderApiRequest.d.ts +101 -0
  22. package/dist/api/LambderApiRequest.js +129 -0
  23. package/dist/api/LambderApiValidationRefusal.d.ts +32 -0
  24. package/dist/api/LambderApiValidationRefusal.js +40 -0
  25. package/dist/client/LambderCaller.d.ts +62 -55
  26. package/dist/client/LambderCaller.js +147 -90
  27. package/dist/client/lambderFetchTransport.d.ts +9 -0
  28. package/dist/client/lambderFetchTransport.js +71 -0
  29. package/dist/client.d.ts +20 -10
  30. package/dist/client.js +11 -5
  31. package/dist/core/Lambder.d.ts +117 -253
  32. package/dist/core/Lambder.js +374 -341
  33. package/dist/core/LambderContext.d.ts +54 -44
  34. package/dist/core/LambderContext.js +41 -110
  35. package/dist/core/LambderCreateOptions.d.ts +285 -0
  36. package/dist/core/LambderCreateOptions.js +44 -0
  37. package/dist/core/LambderFiles.d.ts +1 -45
  38. package/dist/core/LambderFiles.js +18 -38
  39. package/dist/core/LambderIndexHtml.d.ts +37 -0
  40. package/dist/core/LambderIndexHtml.js +87 -0
  41. package/dist/core/LambderPolicyBuilders.d.ts +17 -0
  42. package/dist/core/LambderPolicyBuilders.js +16 -0
  43. package/dist/core/LambderPublicFiles.d.ts +5 -2
  44. package/dist/core/LambderPublicFiles.js +7 -2
  45. package/dist/core/LambderResolver.d.ts +8 -6
  46. package/dist/core/LambderResponse.d.ts +29 -11
  47. package/dist/core/LambderResponse.js +96 -49
  48. package/dist/core/LambderResponseBuilder.d.ts +18 -14
  49. package/dist/core/LambderResponseBuilder.js +19 -25
  50. package/dist/core/LambderRouting.d.ts +18 -7
  51. package/dist/core/LambderRouting.js +17 -7
  52. package/dist/core/LambderTemplatingEngine.d.ts +0 -62
  53. package/dist/core/LambderTemplatingEngine.js +7 -3
  54. package/dist/index.d.ts +85 -32
  55. package/dist/index.js +44 -16
  56. package/dist/invoke/LambderInvokeCaller.d.ts +46 -139
  57. package/dist/invoke/LambderInvokeCaller.js +140 -335
  58. package/dist/invoke/LambderInvokeOutcome.d.ts +165 -0
  59. package/dist/invoke/LambderInvokeOutcome.js +129 -0
  60. package/dist/invoke/LambderLambdaEvent.d.ts +81 -0
  61. package/dist/invoke/LambderLambdaEvent.js +187 -0
  62. package/dist/invoke/lambderHandlerTransport.d.ts +36 -0
  63. package/dist/invoke/lambderHandlerTransport.js +89 -0
  64. package/dist/mock/LambderMockApp.d.ts +352 -0
  65. package/dist/mock/LambderMockApp.js +815 -0
  66. package/dist/mock/LambderMockBrowserCookies.d.ts +55 -0
  67. package/dist/mock/LambderMockBrowserCookies.js +76 -0
  68. package/dist/mock/LambderMockCallRecorder.d.ts +85 -0
  69. package/dist/mock/LambderMockCallRecorder.js +183 -0
  70. package/dist/mock/LambderMockCreateOptions.d.ts +161 -0
  71. package/dist/mock/LambderMockCreateOptions.js +9 -0
  72. package/dist/mock/LambderMockEntryRegistry.d.ts +52 -0
  73. package/dist/mock/LambderMockEntryRegistry.js +126 -0
  74. package/dist/mock/LambderMockFailureInjector.d.ts +60 -0
  75. package/dist/mock/LambderMockFailureInjector.js +138 -0
  76. package/dist/mock/LambderMockTypes.d.ts +421 -0
  77. package/dist/mock/LambderMockTypes.js +8 -0
  78. package/dist/mock/lambderMockConsoleLogger.d.ts +16 -0
  79. package/dist/mock/lambderMockConsoleLogger.js +35 -0
  80. package/dist/mock/lambderMockInvokeTransport.d.ts +50 -0
  81. package/dist/mock/lambderMockInvokeTransport.js +52 -0
  82. package/dist/mock/lambderMockMswHandler.d.ts +99 -0
  83. package/dist/mock/lambderMockMswHandler.js +126 -0
  84. package/dist/mock.d.ts +34 -0
  85. package/dist/mock.js +27 -0
  86. package/dist/session/LambderSessionController.d.ts +199 -30
  87. package/dist/session/LambderSessionController.js +396 -82
  88. package/dist/session/LambderSessionCrypto.d.ts +66 -0
  89. package/dist/session/LambderSessionCrypto.js +101 -0
  90. package/dist/session/LambderSessionManager.d.ts +118 -80
  91. package/dist/session/LambderSessionManager.js +212 -184
  92. package/dist/shared/LambderI18n.d.ts +6 -6
  93. package/dist/shared/LambderI18n.js +1 -1
  94. package/dist/shared/contracts/LambderFileSource.d.ts +33 -0
  95. package/dist/shared/contracts/LambderFileSource.js +19 -0
  96. package/dist/shared/contracts/LambderIdempotencyStore.d.ts +66 -0
  97. package/dist/shared/contracts/LambderIdempotencyStore.js +12 -0
  98. package/dist/shared/contracts/LambderRateLimiter.d.ts +71 -0
  99. package/dist/shared/contracts/LambderRateLimiter.js +24 -0
  100. package/dist/shared/contracts/LambderSessionStore.d.ts +72 -0
  101. package/dist/shared/contracts/LambderSessionStore.js +13 -0
  102. package/dist/shared/transport/LambderApiTransport.d.ts +139 -0
  103. package/dist/shared/transport/LambderApiTransport.js +65 -0
  104. package/dist/shared/transport/LambderCookieJar.d.ts +121 -0
  105. package/dist/shared/transport/LambderCookieJar.js +246 -0
  106. package/dist/shared/transport/lambderCookieJarTransport.d.ts +30 -0
  107. package/dist/shared/transport/lambderCookieJarTransport.js +60 -0
  108. package/dist/shared/util/LambderBase64.d.ts +10 -0
  109. package/dist/shared/util/LambderBase64.js +27 -0
  110. package/dist/shared/util/LambderCallAbort.d.ts +62 -0
  111. package/dist/shared/util/LambderCallAbort.js +80 -0
  112. package/dist/shared/util/LambderClientIp.d.ts +32 -0
  113. package/dist/shared/util/LambderClientIp.js +56 -0
  114. package/dist/shared/util/LambderExpiringMap.d.ts +119 -0
  115. package/dist/shared/util/LambderExpiringMap.js +217 -0
  116. package/dist/shared/util/LambderKeyFields.d.ts +32 -0
  117. package/dist/shared/util/LambderKeyFields.js +34 -0
  118. package/dist/shared/util/LambderNodeModules.d.ts +9 -0
  119. package/dist/shared/util/LambderNodeModules.js +39 -0
  120. package/dist/shared/util/LambderOptionChecks.d.ts +17 -0
  121. package/dist/shared/util/LambderOptionChecks.js +33 -0
  122. package/dist/shared/util/LambderResponseBrand.d.ts +20 -0
  123. package/dist/shared/util/LambderResponseBrand.js +18 -0
  124. package/dist/shared/util/LambderTextDigest.d.ts +17 -0
  125. package/dist/shared/util/LambderTextDigest.js +34 -0
  126. package/dist/shared/util/LambderTypeUtilities.d.ts +33 -0
  127. package/dist/shared/util/LambderTypeUtilities.js +8 -0
  128. package/dist/shared/wire/LambderAnswerHeaders.d.ts +60 -0
  129. package/dist/shared/wire/LambderAnswerHeaders.js +94 -0
  130. package/dist/shared/wire/LambderApiContract.d.ts +129 -0
  131. package/dist/shared/wire/LambderApiOptionValues.d.ts +39 -0
  132. package/dist/shared/wire/LambderApiOptionValues.js +11 -0
  133. package/dist/shared/wire/LambderApiOutcome.d.ts +128 -0
  134. package/dist/shared/{LambderApiOutcome.js → wire/LambderApiOutcome.js} +16 -9
  135. package/dist/shared/{LambderApiError.d.ts → wire/LambderApiRefusal.d.ts} +48 -26
  136. package/dist/shared/{LambderApiError.js → wire/LambderApiRefusal.js} +13 -11
  137. package/dist/shared/wire/LambderCallOptions.d.ts +171 -0
  138. package/dist/shared/wire/LambderCallOptions.js +17 -0
  139. package/dist/shared/{LambderCompressionCodec.d.ts → wire/LambderCompressionCodec.d.ts} +10 -6
  140. package/dist/shared/{LambderCompressionCodec.js → wire/LambderCompressionCodec.js} +67 -23
  141. package/dist/shared/{LambderCompressionOption.d.ts → wire/LambderCompressionOption.d.ts} +1 -1
  142. package/dist/shared/{LambderCompressionOption.js → wire/LambderCompressionOption.js} +3 -4
  143. package/dist/shared/{LambderCrashDetail.d.ts → wire/LambderCrashDetail.d.ts} +10 -0
  144. package/dist/shared/{LambderCrashDetail.js → wire/LambderCrashDetail.js} +30 -0
  145. package/dist/shared/wire/LambderHttpStatus.d.ts +12 -0
  146. package/dist/shared/wire/LambderHttpStatus.js +1 -0
  147. package/dist/shared/{LambderRequestPayload.d.ts → wire/LambderRequestPayload.d.ts} +25 -17
  148. package/dist/shared/{LambderRequestPayload.js → wire/LambderRequestPayload.js} +29 -52
  149. package/dist/shared/wire/LambderSessionCookieNames.d.ts +9 -0
  150. package/dist/shared/wire/LambderSessionCookieNames.js +9 -0
  151. package/dist/stores/LambderDdbCache.d.ts +12 -9
  152. package/dist/stores/LambderDdbCache.js +56 -47
  153. package/dist/stores/{LambderDdbIdempotency.d.ts → LambderDdbIdempotencyStore.d.ts} +41 -31
  154. package/dist/stores/LambderDdbIdempotencyStore.js +319 -0
  155. package/dist/stores/LambderDdbRateLimiter.d.ts +30 -49
  156. package/dist/stores/LambderDdbRateLimiter.js +47 -45
  157. package/dist/stores/LambderDdbSdk.d.ts +83 -6
  158. package/dist/stores/LambderDdbSdk.js +83 -2
  159. package/dist/stores/LambderDdbSessionStore.d.ts +65 -0
  160. package/dist/stores/LambderDdbSessionStore.js +161 -0
  161. package/dist/stores/LambderHttpFileSource.d.ts +1 -1
  162. package/dist/stores/LambderHttpFileSource.js +10 -1
  163. package/dist/stores/LambderLocalFileSource.d.ts +15 -0
  164. package/dist/stores/LambderLocalFileSource.js +28 -0
  165. package/dist/stores/LambderMemoryIdempotencyStore.d.ts +63 -0
  166. package/dist/stores/LambderMemoryIdempotencyStore.js +113 -0
  167. package/dist/stores/LambderMemoryRateLimiter.d.ts +34 -0
  168. package/dist/stores/LambderMemoryRateLimiter.js +64 -0
  169. package/dist/stores/LambderMemorySessionStore.d.ts +48 -0
  170. package/dist/stores/LambderMemorySessionStore.js +74 -0
  171. package/dist/stores/LambderS3FileSource.d.ts +1 -1
  172. package/dist/stores/LambderS3FileSource.js +1 -1
  173. package/package.json +26 -24
  174. package/dist/client/LambderMSW.d.ts +0 -69
  175. package/dist/client/LambderMSW.js +0 -121
  176. package/dist/policies/LambderApiGuards.d.ts +0 -256
  177. package/dist/policies/LambderApiGuards.js +0 -94
  178. package/dist/policies/LambderApiIdempotency.d.ts +0 -58
  179. package/dist/policies/LambderApiIdempotency.js +0 -219
  180. package/dist/policies/LambderApiPolicies.d.ts +0 -42
  181. package/dist/policies/LambderApiPolicies.js +0 -52
  182. package/dist/policies/LambderApiRateLimits.d.ts +0 -132
  183. package/dist/policies/LambderApiRateLimits.js +0 -119
  184. package/dist/shared/LambderApiContract.d.ts +0 -57
  185. package/dist/shared/LambderApiOutcome.d.ts +0 -69
  186. package/dist/shared/LambderCallOptions.d.ts +0 -71
  187. package/dist/shared/LambderCallOptions.js +0 -16
  188. package/dist/shared/node-polyfills.d.ts +0 -4
  189. package/dist/shared/node-polyfills.js +0 -58
  190. package/dist/stores/LambderDdbIdempotency.js +0 -229
  191. package/dist/testing.d.ts +0 -9
  192. package/dist/testing.js +0 -8
  193. /package/dist/shared/{LambderApiContract.js → wire/LambderApiContract.js} +0 -0
  194. /package/dist/{core → shared/wire}/LambderCookie.d.ts +0 -0
  195. /package/dist/{core → shared/wire}/LambderCookie.js +0 -0
@@ -1,19 +1,21 @@
1
- import { type LambderRequestCompressionOption } from '../shared/LambderRequestPayload.js';
2
- import type { ApiContractShape } from '../shared/LambderApiContract.js';
3
- import { type LambderApiOutcome, type LambderValidationError } from '../shared/LambderApiOutcome.js';
4
- import { type LambderCallOptionsArg, type LambderGuardInputsProviderOption } from '../shared/LambderCallOptions.js';
5
- export type { LambderApiOutcome, LambderApiFailureReason, LambderValidationError } from '../shared/LambderApiOutcome.js';
6
- export type { LambderProvidedGuardInputs, LambderGuardInputsProvider } from '../shared/LambderCallOptions.js';
7
- type VoidFunction = () => void | Promise<void>;
1
+ import type { LambderAppRefusalMessage } from '../shared/wire/LambderApiRefusal.js';
2
+ import { type LambderRequestCompressionOption } from '../shared/wire/LambderRequestPayload.js';
3
+ import type { LambderApiContractShape } from '../shared/wire/LambderApiContract.js';
4
+ import { type LambderApiOutcome, type LambderValidationError } from '../shared/wire/LambderApiOutcome.js';
5
+ import { type LambderCallArgs, type LambderContractOutputOf, type LambderGuardInputsProviderOption, type LambderSharedCallOptions } from '../shared/wire/LambderCallOptions.js';
6
+ import { type LambderApiTransport } from '../shared/transport/LambderApiTransport.js';
7
+ export type { LambderApiOutcome, LambderApiFailureReason, LambderValidationError } from '../shared/wire/LambderApiOutcome.js';
8
+ export type { LambderProvidedGuardInputs, LambderGuardInputsProvider } from '../shared/wire/LambderCallOptions.js';
9
+ /** A handler told that something happened, with nothing to hand it. */
10
+ type NotifyHandler = () => void | Promise<void>;
11
+ /** One call in flight: pushed when it starts, removed when it settles, so the list is the in-flight list rather than a log of every call ever made. */
8
12
  type FetchTracker = {
9
13
  apiName: string;
10
- done: boolean;
11
- fetchEndCalled: boolean;
12
14
  };
13
15
  type EventHandlerFetchParams = {
14
16
  apiName: string;
15
17
  payload?: any;
16
- headers?: Record<string, any>;
18
+ headers?: Record<string, string>;
17
19
  };
18
20
  type FetchStartEventHandler = (params: {
19
21
  fetchParams: EventHandlerFetchParams;
@@ -26,7 +28,9 @@ type FetchEndEventHandler = (params: {
26
28
  }) => void | Promise<void>;
27
29
  type ErrorHandler = (err: Error) => void | Promise<void>;
28
30
  type ValidationErrorHandler = (zodError: LambderValidationError) => (void | false) | Promise<(void | false)>;
29
- type MessageHandler = (message: any) => void | Promise<void>;
31
+ type MessageHandler = (message: LambderAppRefusalMessage | string) => void | Promise<void>;
32
+ /** The logListHandler option: an answer's logList, success or failure, when it has entries. The invoke caller's onLogList, for a browser. */
33
+ export type LambderLogListHandler = (apiName: string, logList: unknown[]) => void | Promise<void>;
30
34
  /** One logical operation's rotating idempotency key: see LambderCaller.createIdempotencyKeyScope(). */
31
35
  export type LambderIdempotencyKeyScope = {
32
36
  /** The key for the operation currently in progress. */
@@ -34,45 +38,19 @@ export type LambderIdempotencyKeyScope = {
34
38
  /** Call after a confirmed success: the next operation is a new intent. Returns the new key. */
35
39
  rotate(): string;
36
40
  };
37
- /** Per-call options: request extras plus overrides for every constructor handler. */
38
- export type LambderCallOptions = {
39
- headers?: Record<string, any>;
40
- /** Abort the request after this many ms; overrides the constructor default. */
41
- timeoutMs?: number;
42
- /** External abort signal, combined with the timeout when both are set. */
43
- signal?: AbortSignal;
44
- /**
45
- * Overrides the constructor's requestCompression for this call: `false`
46
- * sends the payload plainly (a hot path where the CPU matters more than
47
- * the bytes), `true` compresses it regardless of the size threshold.
48
- * Either way a payload is only sent compressed when that is smaller.
49
- */
50
- compressRequest?: boolean;
51
- /**
52
- * Values for the API's guardInput-mode guards, keyed by guard name; sent
53
- * beside the payload and consumed by the guards before validation. The
54
- * typed contract makes this REQUIRED for APIs that declare such guards,
55
- * except the guards a guardInputsProvider covers (these merge on top of
56
- * the provider's values).
57
- */
58
- guardInputs?: Record<string, unknown>;
59
- /**
60
- * Replay-protection key for APIs declared idempotent on the server.
61
- * Generate once per logical operation with createIdempotencyKey() and
62
- * send the same key on retries: duplicates of an in-flight request
63
- * refuse, and repeats of a completed one replay its stored response
64
- * instead of re-executing. Must be UNGUESSABLE random (it scopes the
65
- * replay record for logged-out clients) and at least 16 characters; the
66
- * server refuses shorter keys with a 400.
67
- */
68
- idempotencyKey?: string;
69
- versionExpiredHandler?: VoidFunction;
70
- sessionExpiredHandler?: VoidFunction;
41
+ /**
42
+ * Per-call options: the request extras both callers share (see
43
+ * LambderSharedCallOptions) plus an override for every constructor handler.
44
+ */
45
+ export type LambderCallOptions = LambderSharedCallOptions & {
46
+ versionExpiredHandler?: NotifyHandler;
47
+ sessionExpiredHandler?: NotifyHandler;
71
48
  messageHandler?: MessageHandler;
72
49
  errorMessageHandler?: MessageHandler;
73
50
  apiInputValidationErrorHandler?: ValidationErrorHandler;
74
- notAuthorizedHandler?: VoidFunction;
51
+ notAuthorizedHandler?: NotifyHandler;
75
52
  errorHandler?: ErrorHandler;
53
+ logListHandler?: LambderLogListHandler;
76
54
  fetchStartedHandler?: FetchStartEventHandler;
77
55
  fetchEndedHandler?: FetchEndEventHandler;
78
56
  };
@@ -82,12 +60,14 @@ type LambderCallerBaseOptions = {
82
60
  isCorsEnabled: boolean;
83
61
  /** Default per-request timeout in ms (none unless set; API Gateway caps around 29s, so ~30000 is a sensible value). Overridable per call. */
84
62
  timeoutMs?: number;
85
- versionExpiredHandler?: VoidFunction;
86
- sessionExpiredHandler?: VoidFunction;
63
+ versionExpiredHandler?: NotifyHandler;
64
+ sessionExpiredHandler?: NotifyHandler;
87
65
  messageHandler?: MessageHandler;
88
66
  errorMessageHandler?: MessageHandler;
89
- notAuthorizedHandler?: VoidFunction;
67
+ notAuthorizedHandler?: NotifyHandler;
90
68
  errorHandler?: ErrorHandler;
69
+ /** Receives each answer's logList, with the API name. Default: console.log with a `[lambder]` prefix, one line per entry. */
70
+ logListHandler?: LambderLogListHandler;
91
71
  fetchStartedHandler?: FetchStartEventHandler;
92
72
  fetchEndedHandler?: FetchEndEventHandler;
93
73
  apiInputValidationErrorHandler?: ValidationErrorHandler;
@@ -103,6 +83,13 @@ type LambderCallerBaseOptions = {
103
83
  * Lambda's ~6MB invoke cap, which applies to the compressed bytes.
104
84
  */
105
85
  requestCompression?: LambderRequestCompressionOption;
86
+ /**
87
+ * How a call reaches the server. Default: fetch to apiPath
88
+ * (lambderFetchTransport, with CORS per isCorsEnabled). A mock runtime,
89
+ * an in-process Lambder handler, or a cookie-jar decorator over either
90
+ * are the other transports that ship; see LambderApiTransport.
91
+ */
92
+ transport?: LambderApiTransport;
106
93
  };
107
94
  /** Constructor options: the base options plus guardInputsProvider, mandatory once TProvided names guards. */
108
95
  export type LambderCallerOptions<TContract, TProvided extends string = never> = LambderCallerBaseOptions & LambderGuardInputsProviderOption<TContract, TProvided>;
@@ -110,13 +97,15 @@ export type LambderCallerOptions<TContract, TProvided extends string = never> =
110
97
  * @typeParam TContract - The API contract, for typed names, payloads and guard inputs.
111
98
  * @typeParam TProvidedGuards - Guard names guardInputsProvider covers; those APIs' options argument becomes optional.
112
99
  */
113
- export default class LambderCaller<TContract extends ApiContractShape = any, TProvidedGuards extends string = never> {
100
+ export default class LambderCaller<TContract extends LambderApiContractShape = any, TProvidedGuards extends string = never> {
114
101
  private isCorsEnabled;
115
102
  private apiPath;
116
103
  private apiVersion?;
117
104
  private timeoutMs?;
105
+ /** The calls currently in flight, in the order they started. */
118
106
  fetchTrackerList: FetchTracker[];
119
- isLoading: boolean;
107
+ /** Whether any call is in flight. Derived, so it cannot drift from the list the way a separate flag did. */
108
+ get isLoading(): boolean;
120
109
  private versionExpiredHandler?;
121
110
  private sessionExpiredHandler?;
122
111
  private messageHandler?;
@@ -124,6 +113,7 @@ export default class LambderCaller<TContract extends ApiContractShape = any, TPr
124
113
  private notAuthorizedHandler?;
125
114
  private errorHandler?;
126
115
  private apiInputValidationErrorHandler?;
116
+ private logListHandler?;
127
117
  private fetchStartedHandler?;
128
118
  private fetchEndedHandler?;
129
119
  private guardInputsProvider?;
@@ -131,8 +121,11 @@ export default class LambderCaller<TContract extends ApiContractShape = any, TPr
131
121
  private sessionCsrfCookieKey;
132
122
  private sessionCookieDomain?;
133
123
  private requestCompression;
124
+ private transport;
134
125
  constructor(options: LambderCallerOptions<TContract, TProvidedGuards>);
135
126
  setSessionCookieKey(sessionTokenCookieKey: string, sessionCsrfCookieKey: string): void;
127
+ /** Replaces how calls reach the server: a mock runtime, an in-process handler, a decorated transport. */
128
+ setTransport(transport: LambderApiTransport): this;
136
129
  /**
137
130
  * A self-rotating idempotency key for a component or form that performs
138
131
  * the same logical operation repeatedly. `current` is the key for the
@@ -155,6 +148,11 @@ export default class LambderCaller<TContract extends ApiContractShape = any, TPr
155
148
  * confirmed success. Uses crypto.randomUUID when available and falls back
156
149
  * to a v4 UUID from getRandomValues, because randomUUID only exists in
157
150
  * secure contexts (plain-http LAN device testing lacks it).
151
+ *
152
+ * A runtime with neither throws rather than reaching for Math.random: the
153
+ * key must be UNGUESSABLE, since it is what scopes the replay record for a
154
+ * logged-out client, and a guessable one hands that client's stored
155
+ * response to whoever guesses it.
158
156
  */
159
157
  static createIdempotencyKey(): string;
160
158
  private clearSessionCookies;
@@ -162,9 +160,18 @@ export default class LambderCaller<TContract extends ApiContractShape = any, TPr
162
160
  private dispatch;
163
161
  /**
164
162
  * Full-fidelity call: resolves to a discriminated LambderApiOutcome
165
- * instead of collapsing every failure to null. Never throws.
163
+ * instead of collapsing every failure to undefined. Never throws.
164
+ *
165
+ * The output is computed from the contract in the return type rather than
166
+ * taken as a type parameter, so a call site cannot replace it by
167
+ * annotating what it assigns to.
168
+ */
169
+ apiOutcome<TApiName extends keyof TContract & string = string>(apiName: TApiName, ...rest: LambderCallArgs<TContract, TApiName, TProvidedGuards, LambderCallOptions>): Promise<LambderApiOutcome<LambderContractOutputOf<TContract, TApiName>>>;
170
+ /**
171
+ * The payload on success, `undefined` on every failure except a
172
+ * structured refusal, which hands back whatever payload the envelope
173
+ * carried (usually null). Neither is distinguishable from a legitimately
174
+ * null or undefined payload: use apiOutcome() when that matters.
166
175
  */
167
- apiOutcome<TApiName extends keyof TContract & string = string, TOutput = TApiName extends keyof TContract ? TContract[TApiName]['output'] : any>(apiName: TApiName, payload?: TApiName extends keyof TContract ? TContract[TApiName]['input'] : any, ...rest: LambderCallOptionsArg<TContract, TApiName, TProvidedGuards, LambderCallOptions>): Promise<LambderApiOutcome<TOutput>>;
168
- /** Payload on success, null/undefined otherwise (indistinguishable from a null payload; prefer apiOutcome() when that matters). */
169
- api<TApiName extends keyof TContract & string = string, TOutput = TApiName extends keyof TContract ? TContract[TApiName]['output'] : any>(apiName: TApiName, payload?: TApiName extends keyof TContract ? TContract[TApiName]['input'] : any, ...rest: LambderCallOptionsArg<TContract, TApiName, TProvidedGuards, LambderCallOptions>): Promise<TOutput | null | undefined>;
176
+ api<TApiName extends keyof TContract & string = string>(apiName: TApiName, ...rest: LambderCallArgs<TContract, TApiName, TProvidedGuards, LambderCallOptions>): Promise<LambderContractOutputOf<TContract, TApiName> | null | undefined>;
170
177
  }
@@ -1,8 +1,13 @@
1
1
  import Cookies from 'js-cookie';
2
- import { compressPayloadGzip, isRequestCompressionAvailable, DEFAULT_REQUEST_COMPRESSION_SETTINGS, } from '../shared/LambderRequestPayload.js';
3
- import { resolveCompressionOption } from '../shared/LambderCompressionOption.js';
4
- import { resolveApiOutcome } from '../shared/LambderApiOutcome.js';
5
- import { mergeGuardInputs, } from '../shared/LambderCallOptions.js';
2
+ import { compressPayloadGzip, isRequestCompressionAvailable, resolveRequestCompressionMinBytes, DEFAULT_REQUEST_COMPRESSION_SETTINGS, } from '../shared/wire/LambderRequestPayload.js';
3
+ import { resolveCompressionOption } from '../shared/wire/LambderCompressionOption.js';
4
+ import { resolveApiOutcome } from '../shared/wire/LambderApiOutcome.js';
5
+ import { mergeGuardInputs, } from '../shared/wire/LambderCallOptions.js';
6
+ import { createCallAbort } from '../shared/util/LambderCallAbort.js';
7
+ import { coerceToError } from '../shared/wire/LambderCrashDetail.js';
8
+ import { isLambderTransportFailure } from '../shared/transport/LambderApiTransport.js';
9
+ import { DEFAULT_SESSION_TOKEN_COOKIE_KEY, DEFAULT_SESSION_CSRF_COOKIE_KEY } from '../shared/wire/LambderSessionCookieNames.js';
10
+ import { lambderFetchTransport } from './lambderFetchTransport.js';
6
11
  /**
7
12
  * @typeParam TContract - The API contract, for typed names, payloads and guard inputs.
8
13
  * @typeParam TProvidedGuards - Guard names guardInputsProvider covers; those APIs' options argument becomes optional.
@@ -12,8 +17,10 @@ export default class LambderCaller {
12
17
  apiPath;
13
18
  apiVersion;
14
19
  timeoutMs;
20
+ /** The calls currently in flight, in the order they started. */
15
21
  fetchTrackerList = [];
16
- isLoading = false;
22
+ /** Whether any call is in flight. Derived, so it cannot drift from the list the way a separate flag did. */
23
+ get isLoading() { return this.fetchTrackerList.length > 0; }
17
24
  versionExpiredHandler;
18
25
  sessionExpiredHandler;
19
26
  messageHandler;
@@ -21,24 +28,27 @@ export default class LambderCaller {
21
28
  notAuthorizedHandler;
22
29
  errorHandler;
23
30
  apiInputValidationErrorHandler;
31
+ logListHandler;
24
32
  fetchStartedHandler;
25
33
  fetchEndedHandler;
26
34
  guardInputsProvider;
27
- sessionTokenCookieKey = "LMDRSESSIONTKID";
28
- sessionCsrfCookieKey = "LMDRSESSIONCSTK";
35
+ sessionTokenCookieKey = DEFAULT_SESSION_TOKEN_COOKIE_KEY;
36
+ sessionCsrfCookieKey = DEFAULT_SESSION_CSRF_COOKIE_KEY;
29
37
  sessionCookieDomain;
30
38
  requestCompression;
39
+ transport;
31
40
  constructor(options) {
32
41
  // The conditional provider option is resolved per instantiation;
33
42
  // inside the class it is read through the plain shape.
34
- const { apiPath, apiVersion, isCorsEnabled = false, timeoutMs, versionExpiredHandler, sessionExpiredHandler, messageHandler, errorMessageHandler, notAuthorizedHandler, errorHandler, fetchStartedHandler, fetchEndedHandler, apiInputValidationErrorHandler, sessionCookieDomain, requestCompression, guardInputsProvider, } = options;
35
- this.apiPath = apiPath ?? "/api";
43
+ const { apiPath, apiVersion, isCorsEnabled, timeoutMs, versionExpiredHandler, sessionExpiredHandler, messageHandler, errorMessageHandler, notAuthorizedHandler, errorHandler, logListHandler, fetchStartedHandler, fetchEndedHandler, apiInputValidationErrorHandler, sessionCookieDomain, requestCompression, guardInputsProvider, transport, } = options;
44
+ this.apiPath = apiPath;
36
45
  this.apiVersion = apiVersion;
37
46
  this.isCorsEnabled = isCorsEnabled;
38
47
  this.timeoutMs = timeoutMs;
39
48
  this.sessionCookieDomain = sessionCookieDomain;
40
49
  // `?? false`: unlike the at-rest stores, this one is off unless asked for.
41
50
  this.requestCompression = resolveCompressionOption(requestCompression ?? false, DEFAULT_REQUEST_COMPRESSION_SETTINGS);
51
+ this.transport = transport ?? lambderFetchTransport({ cors: this.isCorsEnabled });
42
52
  this.versionExpiredHandler = versionExpiredHandler;
43
53
  this.sessionExpiredHandler = sessionExpiredHandler;
44
54
  this.messageHandler = messageHandler;
@@ -46,6 +56,7 @@ export default class LambderCaller {
46
56
  this.notAuthorizedHandler = notAuthorizedHandler;
47
57
  this.errorHandler = errorHandler;
48
58
  this.apiInputValidationErrorHandler = apiInputValidationErrorHandler;
59
+ this.logListHandler = logListHandler;
49
60
  this.fetchStartedHandler = fetchStartedHandler;
50
61
  this.fetchEndedHandler = fetchEndedHandler;
51
62
  this.guardInputsProvider = guardInputsProvider;
@@ -55,6 +66,11 @@ export default class LambderCaller {
55
66
  this.sessionTokenCookieKey = sessionTokenCookieKey;
56
67
  this.sessionCsrfCookieKey = sessionCsrfCookieKey;
57
68
  }
69
+ /** Replaces how calls reach the server: a mock runtime, an in-process handler, a decorated transport. */
70
+ setTransport(transport) {
71
+ this.transport = transport;
72
+ return this;
73
+ }
58
74
  /**
59
75
  * A self-rotating idempotency key for a component or form that performs
60
76
  * the same logical operation repeatedly. `current` is the key for the
@@ -83,20 +99,20 @@ export default class LambderCaller {
83
99
  * confirmed success. Uses crypto.randomUUID when available and falls back
84
100
  * to a v4 UUID from getRandomValues, because randomUUID only exists in
85
101
  * secure contexts (plain-http LAN device testing lacks it).
102
+ *
103
+ * A runtime with neither throws rather than reaching for Math.random: the
104
+ * key must be UNGUESSABLE, since it is what scopes the replay record for a
105
+ * logged-out client, and a guessable one hands that client's stored
106
+ * response to whoever guesses it.
86
107
  */
87
108
  static createIdempotencyKey() {
88
109
  const cryptoObj = globalThis.crypto;
89
110
  if (cryptoObj?.randomUUID)
90
111
  return cryptoObj.randomUUID();
112
+ if (!cryptoObj?.getRandomValues)
113
+ throw new Error("LambderCaller.createIdempotencyKey needs crypto.getRandomValues: an idempotency key must be unguessable, and this runtime offers no random source that is.");
91
114
  const bytes = new Uint8Array(16);
92
- if (cryptoObj?.getRandomValues) {
93
- cryptoObj.getRandomValues(bytes);
94
- }
95
- else {
96
- for (let i = 0; i < 16; i += 1) {
97
- bytes[i] = Math.floor(Math.random() * 256);
98
- }
99
- }
115
+ cryptoObj.getRandomValues(bytes);
100
116
  bytes[6] = (bytes[6] & 0x0f) | 0x40;
101
117
  bytes[8] = (bytes[8] & 0x3f) | 0x80;
102
118
  const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
@@ -104,7 +120,7 @@ export default class LambderCaller {
104
120
  }
105
121
  clearSessionCookies() {
106
122
  const domainOption = this.sessionCookieDomain;
107
- const hostname = typeof window !== "undefined" ? window.location.hostname : "";
123
+ const hostname = globalThis.location?.hostname ?? "";
108
124
  const resolvedDomain = typeof domainOption === "function" ? domainOption(hostname) : domainOption;
109
125
  for (const key of [this.sessionTokenCookieKey, this.sessionCsrfCookieKey]) {
110
126
  // Host-only and domain-scoped cookies are distinct entries; clear both.
@@ -126,19 +142,31 @@ export default class LambderCaller {
126
142
  const notAuthorizedHandler = options?.notAuthorizedHandler ?? this.notAuthorizedHandler;
127
143
  const errorHandler = options?.errorHandler ?? this.errorHandler;
128
144
  const apiInputValidationErrorHandler = options?.apiInputValidationErrorHandler ?? this.apiInputValidationErrorHandler;
145
+ const logListHandler = options?.logListHandler ?? this.logListHandler;
129
146
  const fetchStartedHandler = options?.fetchStartedHandler ?? this.fetchStartedHandler;
130
147
  const fetchEndedHandler = options?.fetchEndedHandler ?? this.fetchEndedHandler;
131
148
  const headers = options?.headers;
132
- const fetchTracker = { apiName, done: false, fetchEndCalled: false };
149
+ const fetchTracker = { apiName };
150
+ // Dropped the moment the call settles, and idempotently, since the
151
+ // finally block below runs for the paths fetchEnded never reaches.
152
+ // Left in, the list grew by one per call forever, and every handler
153
+ // call scanned all of it: a long-lived page paid more per call the
154
+ // longer it had been open.
155
+ const dropFetchTracker = () => {
156
+ const at = this.fetchTrackerList.indexOf(fetchTracker);
157
+ if (at !== -1)
158
+ this.fetchTrackerList.splice(at, 1);
159
+ };
160
+ let fetchEndCalled = false;
133
161
  const fetchEnded = async (fetchResult) => {
134
- fetchTracker.done = true;
135
- if (fetchTracker.fetchEndCalled || !fetchEndedHandler)
162
+ dropFetchTracker();
163
+ if (fetchEndCalled || !fetchEndedHandler)
136
164
  return;
137
- fetchTracker.fetchEndCalled = true;
165
+ fetchEndCalled = true;
138
166
  await fetchEndedHandler({
139
167
  fetchParams: { apiName, payload, headers },
140
168
  fetchResult,
141
- activeFetchList: this.fetchTrackerList.filter(v => !v.done),
169
+ activeFetchList: [...this.fetchTrackerList],
142
170
  });
143
171
  };
144
172
  let errorHandlerCalled = false;
@@ -148,36 +176,33 @@ export default class LambderCaller {
148
176
  errorHandlerCalled = true;
149
177
  await errorHandler(err);
150
178
  };
151
- // Timeout / abort wiring: the timeout gets its own controller chained
152
- // to any external signal, so either source aborts the fetch.
153
- const timeoutMs = options?.timeoutMs ?? this.timeoutMs;
154
- const externalSignal = options?.signal;
155
- let timedOut = false;
156
- let signal = externalSignal;
157
- let timeoutId;
158
- if (timeoutMs !== undefined) {
159
- const controller = new AbortController();
160
- if (externalSignal) {
161
- if (externalSignal.aborted) {
162
- controller.abort(externalSignal.reason);
163
- }
164
- else {
165
- externalSignal.addEventListener("abort", () => controller.abort(externalSignal.reason), { once: true });
166
- }
167
- }
168
- timeoutId = setTimeout(() => { timedOut = true; controller.abort(); }, timeoutMs);
169
- signal = controller.signal;
170
- }
179
+ // Timeout and abort wiring, shared with LambderInvokeCaller so the two
180
+ // cannot drift on what a late or abandoned call means.
181
+ const abort = createCallAbort({ timeoutMs: options?.timeoutMs ?? this.timeoutMs, signal: options?.signal });
182
+ const signal = abort.signal;
183
+ /** Reports a call that was given up on, or null while it still stands. */
184
+ const abandonedOutcome = async (stage) => {
185
+ const failure = abort.abortFailure(stage);
186
+ if (!failure)
187
+ return null;
188
+ await fetchEnded(failure.error);
189
+ await reportError(failure.error);
190
+ const outcome = { ok: false, reason: failure.reason, error: failure.error };
191
+ return outcome;
192
+ };
171
193
  try {
172
194
  this.fetchTrackerList.push(fetchTracker);
173
195
  if (fetchStartedHandler)
174
196
  await fetchStartedHandler({
175
197
  fetchParams: { apiName, payload, headers, },
176
- activeFetchList: this.fetchTrackerList.filter(v => !v.done)
198
+ activeFetchList: [...this.fetchTrackerList],
177
199
  });
178
200
  const version = this.apiVersion;
201
+ // js-cookie reads nothing without a document, and there is no
202
+ // location outside a page: both are "" then, and a transport that
203
+ // carries a cookie jar fills the token in from it.
179
204
  const token = Cookies.get(this.sessionCsrfCookieKey) || "";
180
- const siteHost = window.location.hostname;
205
+ const siteHost = globalThis.location?.hostname ?? "";
181
206
  // Provider values underneath, per-call values on top.
182
207
  const providedGuardInputs = this.guardInputsProvider
183
208
  ? await this.guardInputsProvider(apiName)
@@ -188,45 +213,65 @@ export default class LambderCaller {
188
213
  // without CompressionStream always sends the payload plainly.
189
214
  // Nothing here runs (the extra stringify included) unless
190
215
  // compression is actually a possibility for this call.
191
- const compressionMinBytes = options?.compressRequest === true ? 0
192
- : options?.compressRequest === false ? null
193
- : this.requestCompression?.minBytes ?? null;
216
+ const compressionMinBytes = resolveRequestCompressionMinBytes(options?.compressRequest, this.requestCompression);
194
217
  const compressedPayload = compressionMinBytes !== null && payload !== undefined && isRequestCompressionAvailable()
195
218
  ? await compressPayloadGzip(JSON.stringify(payload), compressionMinBytes)
196
219
  : null;
197
- let res;
220
+ // A call the site has already given up on does not reach the
221
+ // transport at all: honouring request.signal is the transport's
222
+ // obligation, and not every transport does.
223
+ const refused = await abandonedOutcome("beforeSending");
224
+ if (refused)
225
+ return refused;
226
+ let answer;
198
227
  try {
199
- res = await fetch(this.apiPath, {
200
- method: 'POST', cache: 'no-cache',
201
- // Cross-origin API hosts need CORS mode and included credentials.
202
- mode: this.isCorsEnabled ? 'cors' : 'same-origin',
203
- credentials: this.isCorsEnabled ? 'include' : 'same-origin',
204
- redirect: 'follow', referrerPolicy: 'origin',
205
- headers: { 'Content-Type': 'application/json', ...(headers || {}) },
206
- body: JSON.stringify({
207
- apiName, version, token, siteHost,
208
- ...(compressedPayload ?? { payload }),
209
- ...(guardInputs !== undefined ? { guardInputs } : {}),
210
- ...(options?.idempotencyKey !== undefined ? { idempotencyKey: options.idempotencyKey } : {}),
211
- }),
228
+ answer = await this.transport({
229
+ apiPath: this.apiPath,
230
+ apiName, version, token, siteHost,
231
+ csrfCookieKey: this.sessionCsrfCookieKey,
232
+ ...(compressedPayload ? { compressed: compressedPayload } : { payload }),
233
+ ...(guardInputs !== undefined ? { guardInputs } : {}),
234
+ ...(options?.idempotencyKey !== undefined ? { idempotencyKey: options.idempotencyKey } : {}),
235
+ ...(headers ? { headers } : {}),
212
236
  ...(signal ? { signal } : {}),
213
237
  });
214
238
  }
215
239
  catch (err) {
216
- const wrappedError = err instanceof Error ? err : new Error("Request failed", { cause: err });
240
+ const wrappedError = coerceToError(err, "Request failed");
217
241
  await fetchEnded(wrappedError);
218
242
  await reportError(wrappedError);
219
- return { ok: false, reason: timedOut ? 'timeout' : 'network', error: wrappedError };
243
+ // The caller's own abort wins, since only it knows about that.
244
+ // Otherwise a transport that named its reason is believed:
245
+ // "protocol" means something came back and was not an answer,
246
+ // which is what this caller already calls `server`.
247
+ const reason = abort.timedOut() ? 'timeout'
248
+ : isLambderTransportFailure(err) && err.reason === 'protocol' ? 'server'
249
+ : 'network';
250
+ return { ok: false, reason, error: wrappedError };
220
251
  }
252
+ // An answer that arrives after the call was given up on is not a
253
+ // success. A transport that ignores request.signal resolves late,
254
+ // and believing it would report ok on a 20ms timeoutMs 300ms in,
255
+ // handing the call site data it had already abandoned.
256
+ const late = await abandonedOutcome("afterAnswering");
257
+ if (late)
258
+ return late;
221
259
  // The reading of the answer is shared with LambderInvokeCaller;
222
260
  // only what to do about each outcome is this caller's.
223
- const outcome = await resolveApiOutcome({
224
- status: res.status,
225
- statusText: res.statusText,
226
- header: (name) => res.headers?.get?.(name) ?? null,
227
- json: () => res.json(),
228
- text: () => res.text(),
229
- });
261
+ const outcome = await resolveApiOutcome(answer);
262
+ // Every answer's logs, surfaced once and before any branch that
263
+ // returns: the 500 whose global error handler attached a crash and
264
+ // a logList is the answer whose log trail is worth the most, and
265
+ // surfacing them under the envelope reads meant it was the one
266
+ // answer that never reached logListHandler at all.
267
+ const logList = outcome.logList;
268
+ if (logList?.length) {
269
+ if (logListHandler)
270
+ await logListHandler(apiName, logList);
271
+ else
272
+ for (const record of logList)
273
+ console.log("[lambder]", record);
274
+ }
230
275
  if (!outcome.ok && outcome.reason === 'server') {
231
276
  await fetchEnded(outcome.error);
232
277
  await reportError(outcome.error);
@@ -242,13 +287,10 @@ export default class LambderCaller {
242
287
  }
243
288
  return outcome;
244
289
  }
290
+ // Whatever is left carries the envelope: a success or one of the
291
+ // envelope's own refusals, which is why no assertion is needed.
245
292
  const data = outcome.response;
246
293
  await fetchEnded(data);
247
- if (data.logList?.length) {
248
- for (const record of data.logList) {
249
- console.log("[lambder]", record);
250
- }
251
- }
252
294
  if (!outcome.ok && outcome.reason === 'versionExpired') {
253
295
  if (versionExpiredHandler) {
254
296
  await versionExpiredHandler();
@@ -277,11 +319,13 @@ export default class LambderCaller {
277
319
  }
278
320
  return outcome;
279
321
  }
280
- if (data.message && messageHandler) {
322
+ // Presence, not truthiness: the envelope keeps a message an app
323
+ // spelled out as the empty string, so the handler runs for it.
324
+ if (data.message !== undefined && messageHandler) {
281
325
  await messageHandler(data.message);
282
326
  }
283
327
  if (!outcome.ok && outcome.reason === 'errorMessage') {
284
- if (errorMessageHandler) {
328
+ if (errorMessageHandler && data.errorMessage !== undefined) {
285
329
  await errorMessageHandler(data.errorMessage);
286
330
  }
287
331
  return outcome;
@@ -291,7 +335,7 @@ export default class LambderCaller {
291
335
  catch (err) {
292
336
  // Escape hatch for anything above (typically an app handler throwing):
293
337
  // dispatch never throws, so api()/apiOutcome() call sites never do.
294
- const wrappedError = err instanceof Error ? err : new Error("Error: ", { cause: err });
338
+ const wrappedError = coerceToError(err, "The call failed before it produced an outcome");
295
339
  try {
296
340
  await fetchEnded(wrappedError);
297
341
  await reportError(wrappedError);
@@ -300,25 +344,38 @@ export default class LambderCaller {
300
344
  return { ok: false, reason: 'unknown', error: wrappedError };
301
345
  }
302
346
  finally {
303
- fetchTracker.done = true;
304
- if (timeoutId !== undefined)
305
- clearTimeout(timeoutId);
347
+ dropFetchTracker();
348
+ abort.detach();
306
349
  }
307
350
  }
308
351
  ;
309
352
  /**
310
353
  * Full-fidelity call: resolves to a discriminated LambderApiOutcome
311
- * instead of collapsing every failure to null. Never throws.
354
+ * instead of collapsing every failure to undefined. Never throws.
355
+ *
356
+ * The output is computed from the contract in the return type rather than
357
+ * taken as a type parameter, so a call site cannot replace it by
358
+ * annotating what it assigns to.
312
359
  */
313
- async apiOutcome(apiName, payload, ...rest) {
314
- return await this.dispatch(apiName, payload, rest[0]);
360
+ async apiOutcome(apiName, ...rest) {
361
+ // The tuple is a conditional type on an unresolved TApiName, so its
362
+ // elements read as unknown from inside; the contract shaped them on
363
+ // the way in, which is where the guarantee belongs.
364
+ const [payload, options] = rest;
365
+ return await this.dispatch(apiName, payload, options);
315
366
  }
316
367
  ;
317
- /** Payload on success, null/undefined otherwise (indistinguishable from a null payload; prefer apiOutcome() when that matters). */
318
- async api(apiName, payload, ...rest) {
319
- const outcome = await this.dispatch(apiName, payload, rest[0]);
368
+ /**
369
+ * The payload on success, `undefined` on every failure except a
370
+ * structured refusal, which hands back whatever payload the envelope
371
+ * carried (usually null). Neither is distinguishable from a legitimately
372
+ * null or undefined payload: use apiOutcome() when that matters.
373
+ */
374
+ async api(apiName, ...rest) {
375
+ const [payload, options] = rest;
376
+ const outcome = await this.dispatch(apiName, payload, options);
320
377
  if (outcome.ok)
321
- return outcome.response?.payload;
322
- return outcome.reason === 'errorMessage' ? outcome.response?.payload : undefined;
378
+ return outcome.payload;
379
+ return outcome.reason === 'errorMessage' ? outcome.response.payload : undefined;
323
380
  }
324
381
  }
@@ -0,0 +1,9 @@
1
+ import type { LambderApiTransport } from "../shared/transport/LambderApiTransport.js";
2
+ /**
3
+ * The production transport: one POST of the envelope to the API path over
4
+ * fetch, with the cookie and CORS behaviour a browser call needs. The
5
+ * caller's default, built from its isCorsEnabled option.
6
+ */
7
+ export declare const lambderFetchTransport: (options?: {
8
+ cors?: boolean;
9
+ }) => LambderApiTransport;