lambder 3.5.2 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Readme.md CHANGED
@@ -428,9 +428,20 @@ Responses are finalized once at the end of the request: automatic gzip (when the
428
428
 
429
429
  **Die Methods**: `res.die.*` - Builds the response and throws it, immediately halting the request at any call depth (handlers, hooks, nested helper functions). Plain `throw res.html(...)` works the same way.
430
430
 
431
- ### Typed API Refusals (LambderApiError)
431
+ ### Typed API Refusals (refuse / LambderApiError)
432
432
 
433
- A refusal ("you are not allowed", "quota exceeded") is not a crash. `res.die.*` covers refusals where you hold the resolver, but shared helpers (permission checks, validators) usually don't. Throw `LambderApiError` from anywhere in an API call's stack and the pipeline maps it onto the structured envelope instead of the global error handler, so refusals never pollute crash logging and clients get a parseable response:
433
+ A refusal ("you are not allowed", "quota exceeded") is not a crash. `res.die.*` covers refusals where you hold the resolver, but shared helpers (permission checks, validators) usually don't. The one-liner for the common case is `refuse()`: callable from anywhere in an API call's stack, it throws a typed refusal carrying the standard `LambderRefusalMessage` shape (`{ type, title?, content }`) that the pipeline maps onto the envelope's `errorMessage`, so refusals never pollute crash logging and clients get a parseable response:
434
+
435
+ ```typescript
436
+ import { refuse } from "lambder";
437
+
438
+ if (!row) refuse("Record not found."); // { type: "warning", content }
439
+ if (!isAdmin) refuse("Admins only.", { notAuthorized: true }); // + envelope flag
440
+ refuse("Too many attempts.", { type: "error", statusCode: 429 }); // custom rendering intent + status
441
+ // TypeScript applies never-return narrowing: after `if (!row) refuse(...)`, row is defined.
442
+ ```
443
+
444
+ For full control of the errorMessage payload (apps with their own message vocabulary), throw `LambderApiError` directly; `refuse()` is sugar over it:
434
445
 
435
446
  ```typescript
436
447
  import { LambderApiError } from "lambder";
@@ -52,3 +52,40 @@ export declare class LambderApiError extends Error {
52
52
  }
53
53
  /** Brand-based type guard (see LambderApiError.isLambderApiError). */
54
54
  export declare const isLambderApiError: (err: unknown) => err is LambderApiError;
55
+ /**
56
+ * The standard shape refusals carry on the envelope's errorMessage field.
57
+ * The caller's errorMessageHandler receives it as-is; apps with their own
58
+ * errorMessage vocabulary can keep using LambderApiError directly instead.
59
+ */
60
+ export type LambderRefusalMessage = {
61
+ type: "warning" | "error" | "info";
62
+ title?: string;
63
+ content: string;
64
+ };
65
+ export type LambderRefuseOptions = {
66
+ /** Rendering intent for the client's errorMessageHandler. Default: "warning". */
67
+ type?: LambderRefusalMessage["type"];
68
+ /** Optional heading shown above the content. */
69
+ title?: string;
70
+ /** Sets the envelope's notAuthorized flag (routed to the caller's notAuthorizedHandler). */
71
+ notAuthorized?: boolean;
72
+ /** Sets the envelope's sessionExpired flag. */
73
+ sessionExpired?: boolean;
74
+ /** HTTP status of the refusal. Default 200; avoid 5xx (caller treats as crash) and 422 (reserved for validation). */
75
+ statusCode?: HttpStatusCode;
76
+ /** Underlying cause, preserved on the Error cause property. */
77
+ cause?: unknown;
78
+ };
79
+ /**
80
+ * Refuse the current API call: a routine business "no" (not found, invalid
81
+ * input, not allowed) with a user-facing message. Throws a LambderApiError
82
+ * carrying the standard LambderRefusalMessage shape, so the pipeline maps it
83
+ * onto the structured envelope instead of a 500, and crash logging never
84
+ * sees it. Callable from anywhere in the call stack — handlers, hooks,
85
+ * guards, shared helpers with no resolver access.
86
+ *
87
+ * The const carries the annotation so TypeScript applies never-return
88
+ * control-flow narrowing at call sites (`if (!row) refuse(...)` implies
89
+ * `row` is defined afterwards).
90
+ */
91
+ export declare const refuse: (content: string, options?: LambderRefuseOptions) => never;
@@ -36,3 +36,28 @@ export class LambderApiError extends Error {
36
36
  }
37
37
  /** Brand-based type guard (see LambderApiError.isLambderApiError). */
38
38
  export const isLambderApiError = (err) => err instanceof Error && err.isLambderApiError === true;
39
+ /**
40
+ * Refuse the current API call: a routine business "no" (not found, invalid
41
+ * input, not allowed) with a user-facing message. Throws a LambderApiError
42
+ * carrying the standard LambderRefusalMessage shape, so the pipeline maps it
43
+ * onto the structured envelope instead of a 500, and crash logging never
44
+ * sees it. Callable from anywhere in the call stack — handlers, hooks,
45
+ * guards, shared helpers with no resolver access.
46
+ *
47
+ * The const carries the annotation so TypeScript applies never-return
48
+ * control-flow narrowing at call sites (`if (!row) refuse(...)` implies
49
+ * `row` is defined afterwards).
50
+ */
51
+ export const refuse = (content, options = {}) => {
52
+ throw new LambderApiError(content, {
53
+ errorMessage: {
54
+ type: options.type ?? "warning",
55
+ ...(options.title !== undefined ? { title: options.title } : {}),
56
+ content,
57
+ },
58
+ notAuthorized: options.notAuthorized,
59
+ sessionExpired: options.sessionExpired,
60
+ statusCode: options.statusCode,
61
+ cause: options.cause,
62
+ });
63
+ };
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import Lambder from './Lambder.js';
2
2
  export default Lambder;
3
3
  export { default as LambderCaller } from "./LambderCaller.js";
4
4
  export type { LambderApiOutcome, LambderApiFailureReason, LambderCallOptions } from "./LambderCaller.js";
5
- export { LambderApiError, isLambderApiError } from "./LambderApiError.js";
6
- export type { LambderApiErrorOptions } from "./LambderApiError.js";
5
+ export { LambderApiError, isLambderApiError, refuse } from "./LambderApiError.js";
6
+ export type { LambderApiErrorOptions, LambderRefusalMessage, LambderRefuseOptions } from "./LambderApiError.js";
7
7
  export { default as LambderResponseBuilder } from "./LambderResponseBuilder.js";
8
8
  export { default as LambderResolver } from "./LambderResolver.js";
9
9
  export { default as LambderSessionManager } from "./LambderSessionManager.js";
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import Lambder from './Lambder.js';
2
2
  export default Lambder;
3
3
  export { default as LambderCaller } from "./LambderCaller.js";
4
4
  // Typed API refusals (isomorphic: shared code may throw them from anywhere)
5
- export { LambderApiError, isLambderApiError } from "./LambderApiError.js";
5
+ export { LambderApiError, isLambderApiError, refuse } from "./LambderApiError.js";
6
6
  export { default as LambderResponseBuilder } from "./LambderResponseBuilder.js";
7
7
  export { default as LambderResolver } from "./LambderResolver.js";
8
8
  export { default as LambderSessionManager } from "./LambderSessionManager.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "3.5.2",
3
+ "version": "3.6.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",