import { ExtensionCodec } from '@msgpack/msgpack'; /** * Encode/decode handlers for a custom structured type that crosses Weft's * checkpoint codec. `toJSON` reduces an instance to a plain, msgpack-encodable * value; `fromJSON` rebuilds the instance from that value on the far side. * * The handlers must round-trip deterministically: a value encoded and then * decoded must reconstruct an equivalent instance, with field order and content * stable across calls (the codec backs replay-deterministic checkpoints). * * @example * ```ts * import { type SerializerHandlers } from '@lostgradient/weft'; * * class ValidationError extends Error { * constructor( * message: string, * readonly issues: string[], * ) { * super(message); * this.name = 'ValidationError'; * } * } * * const handlers: SerializerHandlers = { * toJSON: (error) => ({ message: error.message, issues: error.issues }), * fromJSON: (data) => { * const record = data as { message: string; issues: string[] }; * return new ValidationError(record.message, record.issues); * }, * }; * void handlers; * ``` */ export type SerializerHandlers = { toJSON(value: T): unknown; fromJSON(data: unknown): T; }; /** * Constructor of a registrable type. Uses a `never[]`-rest abstract constructor * so it accepts any class (a concrete class is assignable to an abstract * constructor whose parameters are `never[]`) without an `any`. The handlers, * not the constructor signature, own (de)serialization; the constructor is used * only for instance-identity matching. */ type RegistrableConstructor = abstract new (...args: never[]) => T; /** * Register a custom (de)serializer for `constructor` on Weft's checkpoint codec. * Once registered, any instance of `constructor` that crosses the codec — an * activity result, workflow input, signal payload, or error — round-trips * through `handlers` instead of the generic structured-clone fallback (which, * for errors, would otherwise drop subclass fields like a `ZodError`'s * `.issues`). * * `options.tag` is a stable, developer-chosen discriminant stored inside each * encoded value. Decode resolves the handler by this tag, so registration order * and count are irrelevant and a checkpoint stays decodable across deploys. * Choose an explicit, durable string — do NOT rely on `constructor.name`, which * a minified build mangles, silently breaking cross-build decode. * * Matching is by exact constructor identity, and the built-in `Error` encoder * defers to a registered serializer. The other built-in extension types * (`Date`, `RegExp`, `Map`, `Set`) do NOT defer: registering a serializer for a * subclass of one of those built-ins has no effect, because the built-in * encoder matches the instance first. Register serializers for your own classes * (or `Error` subclasses), not for built-in-collection subclasses. * * Registration is process-global and one-shot per constructor and per tag: call * it once at module load, before constructing any engine. Re-registering the * same constructor, or reusing a `tag` already taken by another constructor, * throws. A checkpoint written with a registered serializer is decodable by any * process that registered the same tag → handler. * * @example * ```ts * import { registerSerializer } from '@lostgradient/weft'; * * class RateLimitError extends Error { * constructor(readonly retryAfterMs: number) { * super('rate limited'); * this.name = 'RateLimitError'; * } * } * * registerSerializer( * RateLimitError, * { * toJSON: (error) => ({ retryAfterMs: error.retryAfterMs }), * fromJSON: (data) => new RateLimitError((data as { retryAfterMs: number }).retryAfterMs), * }, * { tag: 'RateLimitError' }, * ); * ``` */ export declare function registerSerializer(constructor: RegistrableConstructor, handlers: SerializerHandlers, options: { tag: string; }): void; /** * Whether `value` is an instance of a registered constructor. The built-in * Error extension encoder consults this to defer to a registered serializer * (so a registered Error subclass uses its custom handler, not the generic * Error encoding), regardless of extension-encoder registration order. */ export declare function hasRegisteredSerializer(value: object): boolean; /** * Wire the shared extensionCodec so the single custom-serializer extension type * is registered on the live codec, along with the codec's `replaceUndefined` * preprocessor so custom-serializer output is encoded with the same `undefined` * semantics as the public `encode()`. Called once at codec construction. * `replaceUndefined` is passed in rather than imported to avoid a static cycle: * extension-codec.ts imports this module for `hasRegisteredSerializer`. */ export declare function bindSerializerRegistryToCodec(codec: ExtensionCodec, replaceUndefined: (value: unknown, visited: Set) => unknown): void; /** * Test-only reset of the global registry. Production code never unregisters — * a stale serializer could misread a checkpoint — but tests need isolation * between registration cases. Clears both tag and constructor maps; the single * extension-type decoder stays bound to the codec and simply finds an empty * registry until the next registration. */ export declare function resetSerializerRegistryForTesting(): void; export {};