Skip to content
Layers
Esc
↑↓navigate↵open⌘Jpreview
On this page

Identity & types

Key vs instance id, DataTag response inference, layerKey, Register, and DefaultLayerError.

Two opens with the same key share logical identity; each still gets a unique instance id (physical identity). DataTag branding carries the response type through await confirm.open(...) (wired handle or bag-form), so no explicit generic is needed.

Key vs instance id

Concept Role
Key Logical identity. Must be JSON-safe (see below). find, upsert, and gcTime operate on keySignature(key). Multiple live layers may share a key in a parallel stack.
Instance id (LayerState.id) Physical, unique id of one Layer instance (`${hashKey(key)}#n`). Used for rendering keys and instance lookup/removal (getLayer, #remove).

JSON-safe key domain

hashKey / keySignature serialize with sorted-object JSON.stringify. Keys must stay inside that domain or hashKey, open / find / cancelQueued, and createLayer (eager keySignature) throw LayerKeyError synchronously (narrow with isLayerKeyError).

Allowed Rejected
string, boolean, null, finite number undefined (anywhere, including object props)
Plain objects / arrays of the above (shared refs / DAGs ok) NaN, ±Infinity, bigint, symbol, functions
Property order in plain objects (sorted for equality) Non-plain objects (Date, class instances), cycles

Prefer null or an explicit sentinel (filterId ?? "none") over undefined. Omit optional object fields instead of setting them to undefined.

Wired handle path

Bind a declaration with useLayer(options) (adapter) or createLayer(options, client) (headless). Payload-only open/upsert infer R from the same DataTag on options.key:

import { useLayer } from "@stainless-code/react-layers";

const confirmLayer = useLayer(confirm);
const ok = await confirmLayer.open({ title: "Remove?" });
//    ^? boolean

Pass { id: state.id } on dismiss/update/cancelQueued when targeting a specific instance — current on the handle is the caller’s last open, not an implicit default for stack ops.

Bag-form client.open({ …layerOptions, payload }) stays valid — see Migration.

DataTag — typed await open

layerOptions brands its key with a DataTag, so handle open and LayerClient.open infer the response type with no explicit generic:

import { LayerClient, createLayer, layerOptions } from "@stainless-code/layers";

type ConfirmPayload = { title: string };
type ConfirmResponse = boolean;

const client = new LayerClient();

const confirm = layerOptions<ConfirmPayload, ConfirmResponse>({
  stack: "confirm",
  key: ["confirm", "remove"],
});

const c = createLayer(confirm, client);
const ok = await c.open({ title: "Remove?" });
//    ^? boolean

Brand a key alone when you don’t need a full options object:

import { layerKey } from "@stainless-code/layers";

const removeKey = layerKey<ConfirmResponse>()(["confirm", "remove"]);

Type helpers exported from core: DataTag, InferDataTagResponse, InferDataTagError, ResponseOf, ErrorOf.

Payload and response are optional

The response type R defaults to void (fire-and-forget), and payload is omittable when the layer needs no input:

import { createLayer } from "@stainless-code/layers";

const about = layerOptions({
  stack: "modal",
  key: ["about"],
});

const c = createLayer(about, client);
void c.open(); // fire-and-forget

Register and DefaultLayerError

Augment Register once to set the app-wide error type:

export {};

declare module "@stainless-code/layers" {
  interface Register {
    defaultError: AppError;
  }
}

DefaultLayerError defaults to Error unless augmented. open returns Promise<R> — rejections are not typed on the promise (a TypeScript limitation); narrow at runtime with guards like isPayloadValidationError and isLayerCancelledError.

Payload validation

Optional validate on layerOptions or open — a StandardSchemaV1 schema or sync Validator fn ((input) => output, throws on invalid). Parsed synchronously at open; failure rejects with PayloadValidationError and mounts nothing. Input/output inference: InferValidatorInput, InferValidatorOutput.

Last updated on

Was this page helpful?