@stainless-code/layers
Version:
Headless, UI-agnostic layer/stack manager for modal/dialog/drawer/popover/toast UI. Call a layer like an async function and await its response.
120 lines (112 loc) • 3.43 kB
text/typescript
/** Provides validator-independent details for one payload failure. */
export interface ValidationIssue {
readonly message: string;
readonly path?: ReadonlyArray<PropertyKey>;
}
/** Normalizes payload failures across supported validator styles. */
export class PayloadValidationError extends Error {
readonly issues: ReadonlyArray<ValidationIssue>;
constructor(
issues: ReadonlyArray<ValidationIssue>,
options?: { cause?: unknown },
) {
super(issues[0]?.message ?? "Payload validation failed", options);
this.name = "PayloadValidationError";
this.issues = issues;
}
}
/**
* Narrows an unknown rejection to {@link PayloadValidationError}.
*
* @example
* ```ts
* import { isPayloadValidationError } from "@stainless-code/layers";
*
* function validationMessages(error: unknown) {
* return isPayloadValidationError(error)
* ? error.issues.map((issue) => issue.message)
* : [];
* }
* ```
*/
export function isPayloadValidationError(
value: unknown,
): value is PayloadValidationError {
return value instanceof PayloadValidationError;
}
/** Thrown when a layer key is not JSON-safe (from `assertLayerKey` / `hashKey`). */
export class LayerKeyError extends Error {
readonly path: ReadonlyArray<PropertyKey>;
constructor(message: string, path: ReadonlyArray<PropertyKey> = []) {
super(message);
this.name = "LayerKeyError";
this.path = path;
}
}
/**
* Narrows an unknown synchronous throw to {@link LayerKeyError}.
*
* @example
* ```ts
* import { isLayerKeyError } from "@stainless-code/layers";
*
* try {
* client.open({ key: [maybeId], payload });
* } catch (error) {
* if (isLayerKeyError(error)) {
* console.error(error.path, error.message);
* }
* }
* ```
*/
export function isLayerKeyError(value: unknown): value is LayerKeyError {
return value instanceof LayerKeyError;
}
/**
* Why {@link LayerCancelledError} rejected an `open()` promise.
*
* - `parentDismiss` — child stack `cancelAll`'d when its parent layer dismissed
* - `groupDispose` — adapter / host cleaned up a `useLayerGroup` owner
* - `cancelAll` — explicit {@link LayerStack#cancelAll} / {@link LayerClient#cancelAll}
* - `stackDisconnect` — host disconnect (e.g. Lit `hostDisconnected`)
*/
export type LayerCancelReason =
| "parentDismiss"
| "groupDispose"
| "cancelAll"
| "stackDisconnect";
/**
* Rejects `open()` when a stack is torn down without a completion response
* (`cancelAll`, parent dismiss child clear, group dispose, `stackDisconnect`).
*/
export class LayerCancelledError extends Error {
/** {@link LayerCancelReason} that triggered this rejection. */
readonly reason: LayerCancelReason;
constructor(reason: LayerCancelReason = "cancelAll") {
super(`LayerCancelledError: ${reason}`);
this.name = "LayerCancelledError";
this.reason = reason;
}
}
/**
* Narrows an unknown rejection to {@link LayerCancelledError}.
* Treat any cancel reason as normal teardown unless you branch on
* {@link LayerCancelledError#reason}.
*
* @example
* ```ts
* import { isLayerCancelledError } from "@stainless-code/layers";
*
* try {
* await confirm.open(payload);
* } catch (error) {
* if (isLayerCancelledError(error)) return;
* throw error;
* }
* ```
*/
export function isLayerCancelledError(
value: unknown,
): value is LayerCancelledError {
return value instanceof LayerCancelledError;
}