lambder 9.0.2 → 10.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 (84) hide show
  1. package/CHANGELOG.md +195 -0
  2. package/README.md +29 -14
  3. package/dist/api/LambderApiDefinition.d.ts +9 -0
  4. package/dist/api/LambderApiEnvelope.d.ts +22 -10
  5. package/dist/api/LambderApiEnvelope.js +57 -22
  6. package/dist/api/LambderApiGuards.d.ts +77 -22
  7. package/dist/api/LambderApiGuards.js +13 -1
  8. package/dist/api/LambderApiIdempotency.js +4 -4
  9. package/dist/api/LambderApiOutputValidationError.d.ts +10 -4
  10. package/dist/api/LambderApiOutputValidationError.js +14 -6
  11. package/dist/api/LambderApiPipeline.d.ts +2 -1
  12. package/dist/api/LambderApiPipeline.js +10 -2
  13. package/dist/api/LambderApiRateLimits.d.ts +15 -11
  14. package/dist/api/LambderApiRateLimits.js +31 -7
  15. package/dist/api/LambderApiRefusals.d.ts +204 -0
  16. package/dist/api/LambderApiRefusals.js +223 -0
  17. package/dist/api/LambderApiSignature.d.ts +13 -4
  18. package/dist/api/LambderApiSignature.js +21 -4
  19. package/dist/build/writeApiOptions.js +7 -5
  20. package/dist/client/LambderCaller.d.ts +28 -17
  21. package/dist/client/LambderCaller.js +19 -15
  22. package/dist/client/LambderUploadRunner.d.ts +5 -5
  23. package/dist/client/LambderUploadRunner.js +25 -16
  24. package/dist/client.d.ts +6 -5
  25. package/dist/client.js +2 -1
  26. package/dist/core/Lambder.d.ts +121 -23
  27. package/dist/core/Lambder.js +105 -51
  28. package/dist/core/LambderContext.d.ts +1 -1
  29. package/dist/core/LambderContext.js +5 -0
  30. package/dist/core/LambderCreateOptions.d.ts +58 -15
  31. package/dist/core/LambderCreateOptions.js +10 -0
  32. package/dist/core/LambderPolicyBuilders.d.ts +11 -8
  33. package/dist/core/LambderPolicyBuilders.js +10 -8
  34. package/dist/core/LambderResolver.d.ts +1 -1
  35. package/dist/core/LambderResolver.js +1 -1
  36. package/dist/core/LambderResponseBuilder.d.ts +11 -9
  37. package/dist/core/LambderResponseBuilder.js +12 -11
  38. package/dist/index.d.ts +13 -9
  39. package/dist/index.js +6 -2
  40. package/dist/invoke/LambderInvokeCaller.d.ts +5 -6
  41. package/dist/invoke/LambderInvokeCaller.js +13 -12
  42. package/dist/invoke/LambderInvokeOutcome.d.ts +32 -23
  43. package/dist/invoke/LambderInvokeOutcome.js +4 -4
  44. package/dist/mock/LambderMockApp.d.ts +86 -24
  45. package/dist/mock/LambderMockApp.js +108 -16
  46. package/dist/mock/LambderMockCallRecorder.js +6 -3
  47. package/dist/mock/LambderMockCreateOptions.d.ts +4 -5
  48. package/dist/mock/LambderMockFailureInjector.d.ts +9 -2
  49. package/dist/mock/LambderMockFailureInjector.js +18 -4
  50. package/dist/mock/LambderMockTypes.d.ts +34 -11
  51. package/dist/mock.d.ts +4 -3
  52. package/dist/mock.js +4 -3
  53. package/dist/shared/contracts/LambderUploadBucket.d.ts +18 -3
  54. package/dist/shared/contracts/LambderUploadBucket.js +2 -2
  55. package/dist/shared/util/LambderTypeUtilities.d.ts +11 -0
  56. package/dist/shared/wire/LambderApiContract.d.ts +96 -31
  57. package/dist/shared/wire/LambderApiOptionEntries.d.ts +19 -12
  58. package/dist/shared/wire/LambderApiOptionValues.d.ts +15 -6
  59. package/dist/shared/wire/LambderApiOptionValues.js +3 -3
  60. package/dist/shared/wire/LambderApiOutcome.d.ts +49 -27
  61. package/dist/shared/wire/LambderApiOutcome.js +28 -11
  62. package/dist/shared/wire/LambderApiRefusal.d.ts +127 -42
  63. package/dist/shared/wire/LambderApiRefusal.js +15 -7
  64. package/dist/shared/wire/LambderCrashDetail.d.ts +1 -1
  65. package/dist/shared/wire/LambderCrashDetail.js +1 -1
  66. package/dist/shared/wire/LambderHttpStatus.d.ts +6 -0
  67. package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +3 -2
  68. package/dist/shared/wire/LambderIdempotencyKeyScope.js +5 -5
  69. package/dist/shared/wire/LambderObjectPayload.d.ts +15 -0
  70. package/dist/shared/wire/LambderObjectPayload.js +24 -0
  71. package/dist/shared/wire/LambderOutcomeAssertions.d.ts +52 -7
  72. package/dist/shared/wire/LambderOutcomeAssertions.js +34 -8
  73. package/dist/shared/wire/LambderUploadObjectFields.d.ts +10 -1
  74. package/dist/shared/wire/LambderUploadObjectFields.js +22 -1
  75. package/dist/shared/wire/LambderUploadSchemas.d.ts +8 -2
  76. package/dist/shared/wire/LambderUploadSchemas.js +18 -7
  77. package/dist/stores/LambderMemoryUploadBucket.d.ts +21 -7
  78. package/dist/stores/LambderMemoryUploadBucket.js +60 -12
  79. package/dist/stores/LambderS3UploadBucket.d.ts +22 -3
  80. package/dist/stores/LambderS3UploadBucket.js +51 -4
  81. package/dist/testing/LambderTestApp.d.ts +1 -1
  82. package/dist/testing.d.ts +1 -1
  83. package/dist/testing.js +1 -1
  84. package/package.json +1 -1
@@ -33,7 +33,10 @@ export default class LambderCaller {
33
33
  get isLoading() { return this.fetchTrackerList.length > 0; }
34
34
  versionExpiredHandler;
35
35
  sessionExpiredHandler;
36
- errorMessageHandler;
36
+ // Hears every endpoint's refusals, so held at the widest message: the
37
+ // constructor option types it to the contract's codes, and dispatch,
38
+ // generic over one endpoint's message, hands it one of those.
39
+ refusalHandler;
37
40
  notAuthorizedHandler;
38
41
  errorHandler;
39
42
  apiInputValidationErrorHandler;
@@ -49,7 +52,7 @@ export default class LambderCaller {
49
52
  constructor(options) {
50
53
  // The conditional provider option is resolved per instantiation;
51
54
  // inside the class it is read through the plain shape.
52
- const { apiPath, apiVersion, apiSignatures, isCorsEnabled, timeoutMs, versionExpiredHandler, sessionExpiredHandler, errorMessageHandler, notAuthorizedHandler, errorHandler, logListHandler, fetchStartedHandler, fetchEndedHandler, apiInputValidationErrorHandler, sessionCookieDomain, requestCompression, guardInputsProvider, transport, } = options;
55
+ const { apiPath, apiVersion, apiSignatures, isCorsEnabled, timeoutMs, versionExpiredHandler, sessionExpiredHandler, refusalHandler, notAuthorizedHandler, errorHandler, logListHandler, fetchStartedHandler, fetchEndedHandler, apiInputValidationErrorHandler, sessionCookieDomain, requestCompression, guardInputsProvider, transport, } = options;
53
56
  this.apiPath = apiPath;
54
57
  this.apiVersion = apiVersion;
55
58
  this.apiSignatures = apiSignatures;
@@ -60,7 +63,7 @@ export default class LambderCaller {
60
63
  this.transport = transport ?? lambderFetchTransport({ cors: isCorsEnabled });
61
64
  this.versionExpiredHandler = versionExpiredHandler;
62
65
  this.sessionExpiredHandler = sessionExpiredHandler;
63
- this.errorMessageHandler = errorMessageHandler;
66
+ this.refusalHandler = refusalHandler;
64
67
  this.notAuthorizedHandler = notAuthorizedHandler;
65
68
  this.errorHandler = errorHandler;
66
69
  this.apiInputValidationErrorHandler = apiInputValidationErrorHandler;
@@ -98,7 +101,7 @@ export default class LambderCaller {
98
101
  // Per-call overrides win over the constructor handlers.
99
102
  const versionExpiredHandler = options?.versionExpiredHandler ?? this.versionExpiredHandler;
100
103
  const sessionExpiredHandler = options?.sessionExpiredHandler ?? this.sessionExpiredHandler;
101
- const errorMessageHandler = options?.errorMessageHandler ?? this.errorMessageHandler;
104
+ const refusalHandler = options?.refusalHandler ?? this.refusalHandler;
102
105
  const notAuthorizedHandler = options?.notAuthorizedHandler ?? this.notAuthorizedHandler;
103
106
  const errorHandler = options?.errorHandler ?? this.errorHandler;
104
107
  const apiInputValidationErrorHandler = options?.apiInputValidationErrorHandler ?? this.apiInputValidationErrorHandler;
@@ -325,9 +328,9 @@ export default class LambderCaller {
325
328
  }
326
329
  return outcome;
327
330
  }
328
- if (!outcome.ok && outcome.reason === 'errorMessage') {
329
- if (errorMessageHandler && outcome.errorMessage !== undefined) {
330
- await errorMessageHandler(outcome.errorMessage);
331
+ if (!outcome.ok && outcome.reason === 'refusal') {
332
+ if (refusalHandler && outcome.refusal !== undefined) {
333
+ await refusalHandler(outcome.refusal);
331
334
  }
332
335
  return outcome;
333
336
  }
@@ -356,7 +359,9 @@ export default class LambderCaller {
356
359
  ;
357
360
  /**
358
361
  * Full-fidelity call: resolves to a discriminated LambderApiOutcome
359
- * instead of collapsing every failure to undefined. Never throws.
362
+ * instead of collapsing every failure to undefined. Never throws. A
363
+ * success's payload is the endpoint's output, and a refusal's
364
+ * refusal narrows on the codes the endpoint declares.
360
365
  *
361
366
  * The output is computed from the contract in the return type rather than
362
367
  * taken as a type parameter, so a call site cannot replace it by
@@ -371,16 +376,15 @@ export default class LambderCaller {
371
376
  }
372
377
  ;
373
378
  /**
374
- * The payload on success, `undefined` on every failure except a
375
- * structured refusal, which hands back whatever payload the envelope
376
- * carried (usually null). Neither is distinguishable from a legitimately
377
- * null or undefined payload: use apiOutcome() when that matters.
379
+ * The endpoint's output on success, `undefined` on every failure. An
380
+ * output is always an object or an array, so the result is truthy exactly
381
+ * when the call succeeded; the handlers configured on the caller have
382
+ * already been told why it did not. Use apiOutcome() to branch on the
383
+ * reason at the call site.
378
384
  */
379
385
  async api(apiName, ...rest) {
380
386
  const [payload, options] = rest;
381
387
  const outcome = await this.dispatch(apiName, payload, options);
382
- if (outcome.ok)
383
- return outcome.payload;
384
- return outcome.reason === 'errorMessage' ? outcome.response.payload : undefined;
388
+ return outcome.ok ? outcome.payload : undefined;
385
389
  }
386
390
  }
@@ -60,9 +60,9 @@ export type LambderUploadRunnerOptions<Reference, Receipt> = {
60
60
  maxDelayMs?: number;
61
61
  };
62
62
  /**
63
- * How long a post may be open and move nothing before it counts as
64
- * dropped. Default: 60 seconds. Watched where XMLHttpRequest exists,
65
- * which reports a body's progress; a runtime with only fetch posts
63
+ * How long an upload to storage may be open and move nothing before it
64
+ * counts as dropped. Default: 60 seconds. Watched where XMLHttpRequest exists,
65
+ * which reports a body's progress; a runtime with only fetch uploads
66
66
  * unwatched.
67
67
  */
68
68
  stallTimeoutMs?: number;
@@ -91,6 +91,6 @@ export declare class LambderUploadRunner<Reference, Receipt> {
91
91
  }): Promise<Receipt>;
92
92
  /** Forgets a confirmed upload through the app's endpoint, when it declared one. */
93
93
  discard(receipt: Receipt): Promise<void>;
94
- /** One post of the file to storage. Never throws: every ending is an outcome. */
95
- private post;
94
+ /** One upload of the file to storage, in the ticket's form. Never throws: every ending is an outcome. */
95
+ private send;
96
96
  }
@@ -90,7 +90,7 @@ export class LambderUploadRunner {
90
90
  for (;;) {
91
91
  stopIfCancelled();
92
92
  report("uploading");
93
- const outcome = await this.post(issued.ticket, file, signal, (sentBytes) => report("uploading", sentBytes));
93
+ const outcome = await this.send(issued.ticket, file, signal, (sentBytes) => report("uploading", sentBytes));
94
94
  if (outcome.kind === "stored")
95
95
  break;
96
96
  if (outcome.kind === "cancelled")
@@ -124,22 +124,29 @@ export class LambderUploadRunner {
124
124
  async discard(receipt) {
125
125
  await this.options.discardUpload?.(receipt);
126
126
  }
127
- /** One post of the file to storage. Never throws: every ending is an outcome. */
128
- post(ticket, file, signal, onSent) {
127
+ /** One upload of the file to storage, in the ticket's form. Never throws: every ending is an outcome. */
128
+ send(ticket, file, signal, onSent) {
129
129
  if (signal?.aborted)
130
130
  return Promise.resolve({ kind: "cancelled" });
131
- const form = new FormData();
132
- for (const [name, value] of Object.entries(ticket.formFields))
133
- form.append(name, value);
134
- // Storage ignores every field that comes after the file.
135
- form.append("file", file);
131
+ let request;
132
+ if (ticket.method === "PUT") {
133
+ request = { method: "PUT", url: ticket.uploadUrl, body: file, headers: ticket.headers };
134
+ }
135
+ else {
136
+ const form = new FormData();
137
+ for (const [name, value] of Object.entries(ticket.formFields))
138
+ form.append(name, value);
139
+ // Storage ignores every field that comes after the file.
140
+ form.append("file", file);
141
+ request = { method: "POST", url: ticket.uploadUrl, body: form, headers: {} };
142
+ }
136
143
  return typeof XMLHttpRequest === "function"
137
- ? postWithXhr(ticket.uploadUrl, form, file.size, this.stallTimeoutMs, signal, onSent)
138
- : postWithFetch(ticket.uploadUrl, form, file.size, signal, onSent);
144
+ ? sendWithXhr(request, file.size, this.stallTimeoutMs, signal, onSent)
145
+ : sendWithFetch(request, file.size, signal, onSent);
139
146
  }
140
147
  }
141
148
  /** XMLHttpRequest rather than fetch where it exists: fetch cannot report how much of a request body has been sent. */
142
- const postWithXhr = (url, form, fileBytes, stallTimeoutMs, signal, onSent) => new Promise((resolve) => {
149
+ const sendWithXhr = ({ method, url, body, headers }, fileBytes, stallTimeoutMs, signal, onSent) => new Promise((resolve) => {
143
150
  const request = new XMLHttpRequest();
144
151
  let stalled = false;
145
152
  let stallTimer;
@@ -158,7 +165,7 @@ const postWithXhr = (url, form, fileBytes, stallTimeoutMs, signal, onSent) => ne
158
165
  };
159
166
  request.upload.onprogress = (event) => {
160
167
  watchForStall();
161
- // `loaded` counts the form's own framing too, a little over the file.
168
+ // A form's `loaded` counts its own framing too, a little over the file.
162
169
  onSent(Math.min(event.loaded, fileBytes));
163
170
  };
164
171
  request.onload = () => {
@@ -173,18 +180,20 @@ const postWithXhr = (url, form, fileBytes, stallTimeoutMs, signal, onSent) => ne
173
180
  signal?.addEventListener("abort", cancel, { once: true });
174
181
  watchForStall();
175
182
  try {
176
- request.open("POST", url);
177
- request.send(form);
183
+ request.open(method, url);
184
+ for (const [name, value] of Object.entries(headers))
185
+ request.setRequestHeader(name, value);
186
+ request.send(body);
178
187
  }
179
188
  catch {
180
189
  // A URL the browser will not open, or a request it will not send, answers nothing.
181
190
  settle({ kind: "unreachable" });
182
191
  }
183
192
  });
184
- const postWithFetch = async (url, form, fileBytes, signal, onSent) => {
193
+ const sendWithFetch = async ({ method, url, body, headers }, fileBytes, signal, onSent) => {
185
194
  let response;
186
195
  try {
187
- response = await fetch(url, { method: "POST", body: form, signal });
196
+ response = await fetch(url, { method, body, headers, signal });
188
197
  }
189
198
  catch {
190
199
  return signal?.aborted ? { kind: "cancelled" } : { kind: "unreachable" };
package/dist/client.d.ts CHANGED
@@ -23,9 +23,10 @@ export type { LambderApiAnswerOutcome, LambderApiSuccessOutcome, LambderApiCallF
23
23
  export type { LambderApiOutcome, LambderApiFailureReason, LambderValidationError, LambderCallOptions, LambderCallerOptions, LambderGuardInputsProvider, LambderProvidedGuardInputs, LambderLogListHandler, } from "./client/LambderCaller.js";
24
24
  export { createIdempotencyKey, createIdempotencyKeyScope } from "./shared/wire/LambderIdempotencyKeyScope.js";
25
25
  export type { LambderIdempotencyKeyScope } from "./shared/wire/LambderIdempotencyKeyScope.js";
26
- export { LambderApiRefusal, isLambderApiRefusal, refuse, refusalMessageOf, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
27
- export type { LambderApiRefusalOptions, LambderRefusalMessage, LambderAppRefusalMessage, LambderRefusalCode, LambderRefuseOptions } from "./shared/wire/LambderApiRefusal.js";
28
- export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiResponseConfig, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractKeysWithGuard, LambderJsonOf, LambderJsonOutputOf, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractRateLimitNames, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
26
+ export { LambderApiRefusal, isLambderApiRefusal, isLambderRefusalCode, refuse, refusalMessageOf, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
27
+ export type { LambderApiRefusalOptions, LambderRefusalMessage, LambderPlainRefusalMessage, LambderUncheckedRefusalMessage, LambderRefusalCode, LambderRefuseOptions, LambderDeclaredRefuseOptions, LambderDeclaredRefuse, } from "./shared/wire/LambderApiRefusal.js";
28
+ export { isObjectPayload } from "./shared/wire/LambderObjectPayload.js";
29
+ export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiSuccessEnvelope, LambderApiRefusalEnvelope, LambderRefusalEnvelopeFields, LambderApiRefusalConfig, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractKeysWithGuard, LambderJsonOf, LambderContractRefusalsOf, LambderContractRefusalMessage, LambderContractRefusals, LambderContractAnyRefusalMessage, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractRateLimitNames, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
29
30
  export { apiGuardParam } from "./shared/wire/LambderApiOptionEntries.js";
30
31
  export type { LambderApiOptionEntries, LambderApiOptionEntry, LambderRateLimitPolicyEntry, LambderGuardDeclarationEntry, LambderApisWithGuard, LambderApisGuardedBy, LambderApisWithMode, LambderGuardParamOf, } from "./shared/wire/LambderApiOptionEntries.js";
31
32
  export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
@@ -37,7 +38,7 @@ export type { LambderCompressionOption, LambderCompressionSettingsBase } from ".
37
38
  export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml, type LambderHtmlValue } from "./shared/LambderHtml.js";
38
39
  export { createLambderI18n } from "./shared/LambderI18n.js";
39
40
  export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nDictionaryLoader, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./shared/LambderI18n.js";
40
- export type { LambderHttpStatusCode } from "./shared/wire/LambderHttpStatus.js";
41
+ export type { LambderHttpStatusCode, LambderRefusalStatusCode } from "./shared/wire/LambderHttpStatus.js";
41
42
  export { LambderBackoffTimer } from "./shared/util/LambderBackoffTimer.js";
42
43
  export type { LambderBackoffTimerOptions } from "./shared/util/LambderBackoffTimer.js";
43
44
  export { LambderSignedClaims, keyedDigest, randomSecret } from "./shared/util/LambderSignedClaims.js";
@@ -46,4 +47,4 @@ export { constantTimeEquals } from "./shared/util/LambderTextDigest.js";
46
47
  export { LambderUploadRunner, LambderUploadError } from "./client/LambderUploadRunner.js";
47
48
  export type { LambderUploadRunnerOptions, LambderUploadProgress, LambderUploadPhase, LambderUploadFailureReason } from "./client/LambderUploadRunner.js";
48
49
  export { checkUploadRule } from "./shared/contracts/LambderUploadBucket.js";
49
- export type { LambderUploadRule, LambderUploadFileFacts, LambderUploadTicket, LambderUploadRuleVerdict } from "./shared/contracts/LambderUploadBucket.js";
50
+ export type { LambderUploadRule, LambderUploadFileFacts, LambderUploadTicket, LambderUploadMethod, LambderUploadRuleVerdict } from "./shared/contracts/LambderUploadBucket.js";
package/dist/client.js CHANGED
@@ -22,7 +22,8 @@ export { compareDottedVersions, isDottedVersion } from "./shared/wire/LambderVer
22
22
  export { createIdempotencyKey, createIdempotencyKeyScope } from "./shared/wire/LambderIdempotencyKeyScope.js";
23
23
  // Typed API refusals (isomorphic: shared code may throw them from anywhere;
24
24
  // in the browser they are plain Errors).
25
- export { LambderApiRefusal, isLambderApiRefusal, refuse, refusalMessageOf, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
25
+ export { LambderApiRefusal, isLambderApiRefusal, isLambderRefusalCode, refuse, refusalMessageOf, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
26
+ export { isObjectPayload } from "./shared/wire/LambderObjectPayload.js";
26
27
  // The server's declared options as plain data (the generated options module's
27
28
  // entry types) and the readers a client derives its own facts from.
28
29
  export { apiGuardParam } from "./shared/wire/LambderApiOptionEntries.js";
@@ -12,17 +12,19 @@ import { LambderFiles } from "./LambderFiles.js";
12
12
  import { type LambderPipelineBackends, type LambderPipelineBackendSwap } from "../api/LambderApiPipeline.js";
13
13
  import type { LambderFileSource } from "../shared/contracts/LambderFileSource.js";
14
14
  import { LAMBDER_BACKEND_SWAP, LAMBDER_CRASH_WATCH } from "../shared/util/LambderTestingDoors.js";
15
+ import { type LambderWireRefusalsOf, type LambderHandlerRefusalsOf, type LambderRefusalNamesIn, type LambderRefusalsOption, type LambderRefusalVocabulary, type LambderRefusalVocabularyChecks } from "../api/LambderApiRefusals.js";
16
+ import { type LambderDeclaredRefuse } from "../shared/wire/LambderApiRefusal.js";
15
17
  import { type LambderApiSignatureEntry } from "../api/LambderApiSignature.js";
16
18
  import { type LambderApiSignatureMap } from "../shared/wire/LambderApiSignatureMap.js";
17
19
  import type { LambderApiOptionEntries } from "../shared/wire/LambderApiOptionEntries.js";
18
20
  import type { LambderApiIdempotencyOption } from "../shared/wire/LambderApiOptionValues.js";
19
- import type { LambderApiGuard, LambderGuardMetaMap, LambderGuardsOption, LambderGuardDataOf, LambderGuardInputsOf } from "../api/LambderApiGuards.js";
21
+ import type { LambderApiGuard, LambderGuardMetaMap, LambderGuardsOption, LambderGuardDataOf, LambderGuardInputsOf, LambderGuardRefusalNamesOf } from "../api/LambderApiGuards.js";
20
22
  import type { LambderApiRateLimitPolicyConfig, LambderRateLimitOption } from "../api/LambderApiRateLimits.js";
21
23
  import type { LambderApiIdempotencyConfig } from "../api/LambderApiIdempotency.js";
22
- import type { LambderContractEntry, LambderJsonOutputOf, LambderMergeContract } from "../shared/wire/LambderApiContract.js";
24
+ import type { LambderContractEntry, LambderJsonOf, LambderMergeContract } from "../shared/wire/LambderApiContract.js";
23
25
  import { type LambderHttpEvent, type LambderRenderContext, type LambderSessionRenderContext } from "./LambderContext.js";
24
26
  import type { LambderReadonlyDeep, MaybePromise } from "../shared/util/LambderTypeUtilities.js";
25
- import { type LambderRouteHandler, type LambderInputValidationHandler, type LambderFallbackHandler, type LambderGlobalErrorHandler, type LambderAfterRenderHook, type LambderBeforeRenderHook, type LambderFallbackHook, type LambderActionTools, type LambderCreateOptions, type LambderGivenOption, type LambderHandler, type LambderNestedOptionChecks, type LambderNoExtraKeys, type LambderRequirableGuardsField, type LambderSessionEnabledInstance, type LambderSessionRouteHandler } from "./LambderCreateOptions.js";
27
+ import { type LambderRouteHandler, type LambderInputValidationHandler, type LambderFallbackHandler, type LambderGlobalErrorHandler, type LambderAfterRenderHook, type LambderBeforeRenderHook, type LambderFallbackHook, type LambderActionTools, type LambderCreateOptions, type LambderGivenOption, type LambderHandler, type LambderNestedOptionChecks, type LambderObjectOutputCheck, type LambderRequirableGuardsField, type LambderSessionEnabledInstance, type LambderSessionRouteHandler } from "./LambderCreateOptions.js";
26
28
  /** Everything `lambder/testing` may put under a built instance: the pipeline's stores, and the source its files are read from. */
27
29
  export type LambderInstanceBackends = LambderPipelineBackends & {
28
30
  fileSource?: LambderFileSource;
@@ -37,7 +39,9 @@ export type LambderInstanceBackendSwap = LambderPipelineBackendSwap & {
37
39
  * because its parameter is the class, and an options module that names the
38
40
  * class cannot be read without it.
39
41
  */
40
- export type LambderCreatedHook = (lambderInstance: Lambder<any, any, any, any, any, any, any, any>) => void | Promise<void>;
42
+ export type LambderCreatedHook = (lambderInstance: Lambder<any, any, any, any, any, any, any, any, any, any>) => void | Promise<void>;
43
+ /** The codes one API may refuse with, typed: its own refusals option's and its declared guards', as the vocabulary holds them. */
44
+ type LambderApiRefusalCodes<TVocabulary, TGuards, TRefusalsOpt, TGuardsOpt> = (LambderRefusalNamesIn<TRefusalsOpt> | LambderGuardRefusalNamesOf<TGuards, TGuardsOpt>) & keyof TVocabulary & string;
41
45
  /**
42
46
  * Main Lambder class for building type-safe serverless APIs. Create
43
47
  * instances with initLambder<SessionData>().create({...}) (see below): the
@@ -52,6 +56,8 @@ export type LambderCreatedHook = (lambderInstance: Lambder<any, any, any, any, a
52
56
  * @typeParam _TSessionGuardsRequired - @internal True when create() received requireSessionApiGuards (do not pass manually)
53
57
  * @typeParam _TPublicGuardsRequired - @internal True when create() received requirePublicApiGuards (do not pass manually)
54
58
  * @typeParam _TSessionsEnabled - @internal True when create() received the session option (do not pass manually). Defaults to true, unlike its siblings, so a plugin annotating its parameter as the bare Lambder<SessionData> can still register session APIs. create() knows the option and supplies the false; `new Lambder(...)` relies on the registration-time throw alone.
59
+ * @typeParam _TRefusals - @internal The refusal vocabulary declareRefusals() gave the init (do not pass manually)
60
+ * @typeParam _TRefusalCodesRequired - @internal True when declareRefusals() received requireCodes (do not pass manually)
55
61
  *
56
62
  * @example
57
63
  * ```typescript
@@ -62,7 +68,7 @@ export type LambderCreatedHook = (lambderInstance: Lambder<any, any, any, any, a
62
68
  * .addApi('createUser', { input: z.object({...}), output: z.object({...}) }, handler);
63
69
  * ```
64
70
  */
65
- export default class Lambder<TSessionData = any, _TContract extends Record<string, any> = {}, _TRateLimitPolicies extends Record<string, LambderApiRateLimitPolicyConfig> = {}, _TGuards extends Record<string, any> = {}, _TIdempotencyEnabled extends boolean = false, _TSessionGuardsRequired extends boolean = false, _TPublicGuardsRequired extends boolean = false, _TSessionsEnabled extends boolean = true> {
71
+ export default class Lambder<TSessionData = any, _TContract extends Record<string, any> = {}, _TRateLimitPolicies extends Record<string, LambderApiRateLimitPolicyConfig> = {}, _TGuards extends Record<string, any> = {}, _TIdempotencyEnabled extends boolean = false, _TSessionGuardsRequired extends boolean = false, _TPublicGuardsRequired extends boolean = false, _TSessionsEnabled extends boolean = true, _TRefusals extends LambderRefusalVocabulary = {}, _TRefusalCodesRequired extends boolean = false> {
66
72
  apiPath: string;
67
73
  /** Stamped on every API answer's envelope as apiVersion. Informational: a client's staleness is judged per endpoint by its signature, see apiSignatures(). */
68
74
  apiVersion: null | string;
@@ -94,6 +100,10 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
94
100
  private readonly guards;
95
101
  /** The rate-limit policies given at creation, kept for apiOptionEntries(), which records each one less its key handler. */
96
102
  private readonly rateLimitPolicies;
103
+ /** The refusal vocabulary given at creation: what each API's and each guard's refusal codes resolve against. Null without the option. */
104
+ private readonly refusalVocabulary;
105
+ /** Every API's refusals option as written, for apiOptionEntries(); its definition holds the resolved set. */
106
+ private readonly refusalOptions;
97
107
  private hookList;
98
108
  private createdHooks;
99
109
  private initPromise;
@@ -108,6 +118,8 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
108
118
  private corsConfig;
109
119
  private finalizeOptions;
110
120
  private requireSessionApiGuards;
121
+ /** Whether every refusal an API answers with has to name a code (declareRefusals's requireCodes). */
122
+ private readonly requireRefusalCodes;
111
123
  /** Told what a request threw, beside whatever answers it; null outside a test. See LAMBDER_CRASH_WATCH. */
112
124
  private crashWatcher;
113
125
  /** The crashes option applied: reporting, and the framework's own 500. */
@@ -155,14 +167,20 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
155
167
  addRoute(condition: RegExp | LambderRouteConditionFn | LambderRouteMatcher, actionFn: LambderRouteHandler): this;
156
168
  addSessionRoute<TPath extends LambderRoutePath>(condition: TPath, actionFn: ((ctx: LambderSessionRenderContext<any, TSessionData, LambderPathParamsOf<TPath>, {}, _TRateLimitPolicies>, resolver: LambderResolver) => MaybePromise<LambderResponse>) & LambderSessionEnabledInstance<_TSessionsEnabled>): this;
157
169
  addSessionRoute(condition: RegExp | LambderRouteConditionFn | LambderRouteMatcher, actionFn: LambderSessionRouteHandler<TSessionData> & LambderSessionEnabledInstance<_TSessionsEnabled>): this;
158
- addApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TAnswer extends LambderReadonlyDeep<z.input<TOutput>>, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.input<TInput>, false> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.input<TInput>, false> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never>(name: TName, schema: {
170
+ addApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TAnswer extends LambderReadonlyDeep<z.input<TOutput>>, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.input<TInput>, false> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.input<TInput>, false> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never, const TRefusalsOpt extends LambderRefusalsOption<_TRefusals> = never>(name: TName, schema: {
159
171
  input: TInput;
160
172
  output: TOutput;
161
173
  } & {
162
- /** Named rate limits, checked in declared order within their phase (per ip before the session read, per session before the guards, a custom key after the guards and input validation): a name, a list of names, or a { name: true | override } map (windows overridable on perApi budgets, errorMessage on any). The first exceeded one refuses (429 envelope + Retry-After); attempts count on every counter checked before it. */
174
+ /** Named rate limits, checked in declared order within their phase (per ip before the session read, per session before the guards, a custom key after the guards and input validation): a name, a list of names, or a { name: true | override } map (windows overridable on perApi budgets, refusal on any). The first exceeded one refuses (429 envelope + Retry-After); attempts count on every counter checked before it. */
163
175
  rateLimit?: TRateOpt;
164
176
  /** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
165
177
  idempotency?: _TIdempotencyEnabled extends true ? TIdempotencyOpt : never;
178
+ /**
179
+ * The refusal codes this API may refuse with, from the vocabulary given at creation: one code or a
180
+ * non-empty list. Its guards' codes join them. The handler raises one with `ctx.refuse(content, { code, data })`,
181
+ * its callers narrow on them, and a refusal with any other code is a crash rather than an answer.
182
+ */
183
+ refusals?: TRefusalsOpt;
166
184
  /**
167
185
  * Whether this API's answers are compressed for a caller that accepts it: "auto" (the default) when the
168
186
  * body is large enough to gain, false never, true always. false suits an answer of base64 bytes: once
@@ -171,17 +189,25 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
171
189
  * both ends. A transport setting of this server's, not part of the API's contract.
172
190
  */
173
191
  compress?: boolean | "auto";
174
- } & LambderRequirableGuardsField<_TPublicGuardsRequired, TGuardsOpt>,
175
- /** Answers the call by returning its output (parsed through `output` before it is sent), or refuses it with refuse(). */
176
- handler: (ctx: LambderRenderContext<z.infer<TInput>, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>, TSessionData, _TRateLimitPolicies>) => MaybePromise<TAnswer>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.input<TInput>, LambderJsonOutputOf<z.output<TOutput>>, "public", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
177
- addSessionApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TAnswer extends LambderReadonlyDeep<z.input<TOutput>>, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.input<TInput>, true> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.input<TInput>, true> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never>(name: TName, schema: {
192
+ } & LambderRequirableGuardsField<_TPublicGuardsRequired, TGuardsOpt> & LambderObjectOutputCheck<TOutput>,
193
+ /** Answers the call by returning its output (parsed through `output` before it is sent), or refuses it with ctx.refuse() or refuse(). */
194
+ handler: (ctx: LambderRenderContext<z.infer<TInput>, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>, TSessionData, _TRateLimitPolicies> & {
195
+ refuse: LambderDeclaredRefuse<LambderHandlerRefusalsOf<_TRefusals, LambderApiRefusalCodes<_TRefusals, _TGuards, TRefusalsOpt, TGuardsOpt>>, _TRefusalCodesRequired>;
196
+ }) => MaybePromise<TAnswer>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.input<TInput>, LambderJsonOf<z.output<TOutput>>, "public", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt, LambderWireRefusalsOf<_TRefusals, LambderApiRefusalCodes<_TRefusals, _TGuards, TRefusalsOpt, TGuardsOpt>>>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled, _TRefusals, _TRefusalCodesRequired>;
197
+ addSessionApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TAnswer extends LambderReadonlyDeep<z.input<TOutput>>, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.input<TInput>, true> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.input<TInput>, true> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never, const TRefusalsOpt extends LambderRefusalsOption<_TRefusals> = never>(name: TName, schema: {
178
198
  input: TInput;
179
199
  output: TOutput;
180
200
  } & {
181
- /** Named rate limits, checked in declared order within their phase (per ip before the session read, per session before the guards, a custom key after the guards and input validation): a name, a list of names, or a { name: true | override } map (windows overridable on perApi budgets, errorMessage on any). The first exceeded one refuses (429 envelope + Retry-After); attempts count on every counter checked before it. */
201
+ /** Named rate limits, checked in declared order within their phase (per ip before the session read, per session before the guards, a custom key after the guards and input validation): a name, a list of names, or a { name: true | override } map (windows overridable on perApi budgets, refusal on any). The first exceeded one refuses (429 envelope + Retry-After); attempts count on every counter checked before it. */
182
202
  rateLimit?: TRateOpt;
183
203
  /** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
184
204
  idempotency?: _TIdempotencyEnabled extends true ? TIdempotencyOpt : never;
205
+ /**
206
+ * The refusal codes this API may refuse with, from the vocabulary given at creation: one code or a
207
+ * non-empty list. Its guards' codes join them. The handler raises one with `ctx.refuse(content, { code, data })`,
208
+ * its callers narrow on them, and a refusal with any other code is a crash rather than an answer.
209
+ */
210
+ refusals?: TRefusalsOpt;
185
211
  /**
186
212
  * Whether this API's answers are compressed for a caller that accepts it: "auto" (the default) when the
187
213
  * body is large enough to gain, false never, true always. false suits an answer of base64 bytes: once
@@ -190,9 +216,11 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
190
216
  * both ends. A transport setting of this server's, not part of the API's contract.
191
217
  */
192
218
  compress?: boolean | "auto";
193
- } & LambderRequirableGuardsField<_TSessionGuardsRequired, TGuardsOpt> & LambderSessionEnabledInstance<_TSessionsEnabled>,
194
- /** Answers the call by returning its output (parsed through `output` before it is sent), or refuses it with refuse(). */
195
- handler: (ctx: LambderSessionRenderContext<z.infer<TInput>, TSessionData, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>, _TRateLimitPolicies>) => MaybePromise<TAnswer>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.input<TInput>, LambderJsonOutputOf<z.output<TOutput>>, "session", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
219
+ } & LambderRequirableGuardsField<_TSessionGuardsRequired, TGuardsOpt> & LambderSessionEnabledInstance<_TSessionsEnabled> & LambderObjectOutputCheck<TOutput>,
220
+ /** Answers the call by returning its output (parsed through `output` before it is sent), or refuses it with ctx.refuse() or refuse(). */
221
+ handler: (ctx: LambderSessionRenderContext<z.infer<TInput>, TSessionData, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>, _TRateLimitPolicies> & {
222
+ refuse: LambderDeclaredRefuse<LambderHandlerRefusalsOf<_TRefusals, LambderApiRefusalCodes<_TRefusals, _TGuards, TRefusalsOpt, TGuardsOpt>>, _TRefusalCodesRequired>;
223
+ }) => MaybePromise<TAnswer>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.input<TInput>, LambderJsonOf<z.output<TOutput>>, "session", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt, LambderWireRefusalsOf<_TRefusals, LambderApiRefusalCodes<_TRefusals, _TGuards, TRefusalsOpt, TGuardsOpt>>>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled, _TRefusals, _TRefusalCodesRequired>;
196
224
  /**
197
225
  * What registering an API is, for addApi and addSessionApi alike: the
198
226
  * checks that can refuse it, then its definition recorded (what
@@ -223,7 +251,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
223
251
  */
224
252
  addAction<TEvent>(filter: (event: unknown, ctx: LambderRenderContext | null) => event is TEvent, actionFn: (event: TEvent, tools: LambderActionTools) => MaybePromise<unknown>): this;
225
253
  addAction(filter: (event: unknown, ctx: LambderRenderContext | null) => boolean, actionFn: (event: unknown, tools: LambderActionTools) => MaybePromise<unknown>): this;
226
- use<_TNewContract extends Record<string, any>>(plugin: (lambder: Lambder<TSessionData, _TContract, any, any, any, any, any, any>) => Lambder<TSessionData, _TNewContract, any, any, any, any, any, any>): Lambder<TSessionData, _TNewContract extends _TContract ? _TNewContract : (_TContract & _TNewContract), _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
254
+ use<_TNewContract extends Record<string, any>>(plugin: (lambder: Lambder<TSessionData, _TContract, any, any, any, any, any, any, any, any>) => Lambder<TSessionData, _TNewContract, any, any, any, any, any, any, any, any>): Lambder<TSessionData, _TNewContract extends _TContract ? _TNewContract : (_TContract & _TNewContract), _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled, _TRefusals, _TRefusalCodesRequired>;
227
255
  /**
228
256
  * A session controller for a context: what creates, rotates, refreshes
229
257
  * and ends sessions. An API call presents its posted CSRF token; a route
@@ -283,8 +311,10 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
283
311
  * and guard name. A policy's key handler is never written: its `per`
284
312
  * says "custom" and no more. A guard's input schema is never written
285
313
  * either; its declaration says only which of the three input modes it
286
- * has. Every table is sorted by name, so the module diffs by endpoint
287
- * and never moves when registrations are reordered.
314
+ * has. The refusal vocabulary is not written at all: it is shared code
315
+ * (codes, zod schemas, statuses and flags), and the mock declares the
316
+ * same object. Every table is sorted by name, so the module diffs by
317
+ * endpoint and never moves when registrations are reordered.
288
318
  */
289
319
  apiOptionEntries(): LambderApiOptionEntries;
290
320
  getResponseBuilder(ctx?: LambderRenderContext): LambderResponseBuilder;
@@ -399,7 +429,9 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
399
429
  * see the declared shape. The handler returns the schema's input form, so
400
430
  * a transform runs exactly once.
401
431
  *
402
- * An output the schema rejects is the handler breaking its contract,
432
+ * An output the schema rejects, or one that is not an object or an array
433
+ * (isObjectPayload: what makes a caller's success never falsy), is the
434
+ * handler breaking its contract,
403
435
  * answered as a crash rather than sent (LambderApiOutputValidationError,
404
436
  * which an idempotency key records as its answer, since the handler has
405
437
  * already run). The parse is synchronous, so an output schema cannot be
@@ -411,7 +443,13 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
411
443
  * and every retry would run the operation again.
412
444
  */
413
445
  private runApi;
414
- /** A thrown LambderApiRefusal (from a hook, say) as the structured API envelope: the core's one mapping. */
446
+ /**
447
+ * A thrown LambderApiRefusal (from a hook, say) as the structured API
448
+ * envelope: the core's one mapping, after the same check the pipeline
449
+ * applies, against the endpoint the call names. A name no API is
450
+ * registered under declares no code, so only an uncoded or a framework
451
+ * refusal goes out for it.
452
+ */
415
453
  private apiErrorResponse;
416
454
  }
417
455
  /**
@@ -446,10 +484,70 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
446
484
  * session type in the first call lets the second infer everything else.
447
485
  * `new Lambder(options)` serves untyped or session-data-free instances.
448
486
  */
487
+ /** The options create() takes: the constructor's, less the two declareRefusals() supplies. */
488
+ export type LambderInitCreateOptions<TSessionData> = Omit<LambderCreateOptions<TSessionData>, "refusals" | "requireRefusalCodes">;
489
+ /**
490
+ * The entry point of an app: binds the session data type, and hands out the
491
+ * builders and create() that share it. `declareRefusals()` binds the app's
492
+ * refusal vocabulary too, so a guard's ctx.refuse and the init's own refuse
493
+ * are typed to it before any instance exists, and create() gives it to the
494
+ * instance for every API's refusals option to name codes from.
495
+ */
449
496
  export declare const initLambder: <TSessionData = any>() => {
450
- create<const TOptions extends LambderCreateOptions<TSessionData>>(options: LambderNoExtraKeys<TOptions, LambderCreateOptions<TSessionData>> & LambderNestedOptionChecks<TSessionData, TOptions>): Lambder<TSessionData, {}, TOptions["rateLimits"] extends {
497
+ /**
498
+ * Declares the app's refusal vocabulary: every code once, with the schema
499
+ * of its data (`{ data: schema }`) or none (`{}`), the status every
500
+ * refusal with it leaves with (`status`, 200 by default) and whether it
501
+ * sets the notAuthorized flag (`notAuthorized: true`). Returns the init
502
+ * bound to it: its guard() types ctx.refuse to a guard's refusals and
503
+ * refuses a guard naming a code outside the vocabulary, its refuse is
504
+ * typed to the whole vocabulary, and its create() hands the vocabulary to
505
+ * the instance. With `requireCodes`, an uncoded refusal from an API
506
+ * handler, a guard or a helper is a crash rather than an answer, so every
507
+ * "no" a client reads names a code.
508
+ */
509
+ declareRefusals<const TRefusals extends LambderRefusalVocabulary, const TRequireCodes extends boolean = false>(refusals: TRefusals & LambderRefusalVocabularyChecks<TRefusals>, options?: {
510
+ requireCodes?: TRequireCodes;
511
+ }): {
512
+ /**
513
+ * refuse() typed to the app's whole vocabulary, for a shared helper or a
514
+ * hook that raises a declared code with no endpoint in hand: `code` is
515
+ * one of the vocabulary's and `data` follows it. Which endpoint may send
516
+ * the code is checked where the refusal is rendered, as for the free
517
+ * refuse(). Inside an API handler ctx.refuse is narrower, that
518
+ * endpoint's codes alone.
519
+ */
520
+ refuse: LambderDeclaredRefuse<LambderHandlerRefusalsOf<TRefusals, keyof TRefusals & string>, TRequireCodes>;
521
+ create<const TOptions extends LambderInitCreateOptions<TSessionData>>(options: TOptions & Record<Exclude<keyof TOptions, "session" | "guards" | "idempotency" | "apiVersion" | "cors" | "apiPath" | "apiSignatures" | "trustedClientIpHeaders" | "trustedHostHeaders" | "files" | "etag" | "minApiVersion" | "compression" | "maxResponseBytes" | "maxRequestPayloadBytes" | "rateLimits" | "requireSessionApiGuards" | "requirePublicApiGuards" | "crashes">, never> & LambderNestedOptionChecks<TSessionData, TOptions, TRefusals>): Lambder<TSessionData, {}, TOptions["rateLimits"] extends {
522
+ policies: infer TPolicies extends Record<string, LambderApiRateLimitPolicyConfig>;
523
+ } ? TPolicies : {}, TOptions["guards"] extends Record<string, LambderApiGuard<any, any, any>> ? LambderGuardMetaMap<TOptions["guards"]> : {}, TOptions["idempotency"] extends LambderApiIdempotencyConfig ? true : false, [LambderGivenOption<TOptions, "requireSessionApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "requirePublicApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "session">] extends [undefined] ? false : true, TRefusals, TRequireCodes>;
524
+ guard: import("../api/LambderApiGuards.js").LambderGuardBuilder<LambderRenderContext<any, Record<string, string>, {}, TSessionData>, LambderSessionRenderContext<any, TSessionData>, TRefusals, TRequireCodes>;
525
+ rateLimitKey: import("../api/LambderApiRateLimits.js").LambderRateLimitKeyBuilder<LambderRenderContext<any, Record<string, string>, {}, TSessionData>>;
526
+ };
527
+ /**
528
+ * refuse() typed to the app's whole vocabulary, for a shared helper or a
529
+ * hook that raises a declared code with no endpoint in hand: `code` is
530
+ * one of the vocabulary's and `data` follows it. Which endpoint may send
531
+ * the code is checked where the refusal is rendered, as for the free
532
+ * refuse(). Inside an API handler ctx.refuse is narrower, that
533
+ * endpoint's codes alone.
534
+ */
535
+ refuse: (content: string, options?: ({
536
+ cause?: unknown;
537
+ type?: import("../shared/wire/LambderApiRefusal.js").LambderRefusalMessage["type"] | undefined;
538
+ title?: string | undefined;
539
+ notAuthorized?: boolean | undefined;
540
+ sessionExpired?: boolean | undefined;
541
+ statusCode?: import("../client.js").LambderHttpStatusCode | undefined;
542
+ headers?: Record<string, string> | undefined;
543
+ } & {
544
+ code?: undefined;
545
+ data?: undefined;
546
+ }) | undefined) => never;
547
+ create<const TOptions extends LambderInitCreateOptions<TSessionData>>(options: TOptions & Record<Exclude<keyof TOptions, "session" | "guards" | "idempotency" | "apiVersion" | "cors" | "apiPath" | "apiSignatures" | "trustedClientIpHeaders" | "trustedHostHeaders" | "files" | "etag" | "minApiVersion" | "compression" | "maxResponseBytes" | "maxRequestPayloadBytes" | "rateLimits" | "requireSessionApiGuards" | "requirePublicApiGuards" | "crashes">, never> & LambderNestedOptionChecks<TSessionData, TOptions, {}>): Lambder<TSessionData, {}, TOptions["rateLimits"] extends {
451
548
  policies: infer TPolicies extends Record<string, LambderApiRateLimitPolicyConfig>;
452
- } ? TPolicies : {}, TOptions["guards"] extends Record<string, LambderApiGuard<any, any, any>> ? LambderGuardMetaMap<TOptions["guards"]> : {}, TOptions["idempotency"] extends LambderApiIdempotencyConfig ? true : false, [LambderGivenOption<TOptions, "requireSessionApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "requirePublicApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "session">] extends [undefined] ? false : true>;
453
- guard: import("../api/LambderApiGuards.js").LambderGuardBuilder<LambderRenderContext<any, Record<string, string>, {}, TSessionData>, LambderSessionRenderContext<any, TSessionData>>;
549
+ } ? TPolicies : {}, TOptions["guards"] extends Record<string, LambderApiGuard<any, any, any>> ? LambderGuardMetaMap<TOptions["guards"]> : {}, TOptions["idempotency"] extends LambderApiIdempotencyConfig ? true : false, [LambderGivenOption<TOptions, "requireSessionApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "requirePublicApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "session">] extends [undefined] ? false : true, {}, false>;
550
+ guard: import("../api/LambderApiGuards.js").LambderGuardBuilder<LambderRenderContext<any, Record<string, string>, {}, TSessionData>, LambderSessionRenderContext<any, TSessionData>, {}, false>;
454
551
  rateLimitKey: import("../api/LambderApiRateLimits.js").LambderRateLimitKeyBuilder<LambderRenderContext<any, Record<string, string>, {}, TSessionData>>;
455
552
  };
553
+ export {};