/** * Types for the schema definition system */ import { ContainerType } from "loro-crdt"; export type InferContainerOptions = { /** * When true, string values are inferred as `LoroText` containers instead of primitive strings. */ defaultLoroText?: boolean; /** * When true, array values are inferred as `LoroMovableList` containers instead of `LoroList`. * * Note: if a MovableList is created/inferred without an `idSelector` schema, diffs fall back * to index-based updates and do not emit `move` operations. */ defaultMovableList?: boolean; /** * When true, new child containers created under `LoroMap` keys use Loro's * `ensureMergeable*` APIs instead of `setContainer`, so peers that * concurrently create the same parent/key/type child converge on one logical * container instead of last-writer-wins silently dropping one peer's child. * * This is the schema-less/global fallback; prefer setting * `mergeableMapChildContainers` on the parent `schema.LoroMap` / * `schema.LoroMapRecord` options. Opt-in (default `false`) to keep the * document format compatible with older `setContainer` child-container * semantics. Requires `loro-crdt >= 1.13.3`. * * @see https://loro.dev/blog/mergeable-containers */ mergeableMapChildContainers?: boolean; }; /** * Options for schema definitions */ export interface SchemaOptions { /** Whether the field is required */ required?: boolean; /** Default value for the field */ defaultValue?: unknown; /** Description of the field */ description?: string; /** * Additional validation function. * Receives the domain value to validate - this will be the transformed value if a transform is defined, otherwise the raw value (string | number | boolean). * Return true if valid, or a string error message if invalid. */ validate?: (value: unknown) => boolean | string; /** * When set on a `schema.LoroMap` or `schema.LoroMapRecord`, new direct child * containers under that map's keys are created with Loro's `ensureMergeable*` * APIs instead of `setContainer`. * * By default, two peers that concurrently create a child container under the * same Map key produce distinct container IDs; the Map slot is * last-writer-wins, so one peer's child (and its contents) is silently * dropped after sync. With this enabled, the child's identity derives from * its logical position (parent container id + key + type), so every peer * resolves to the same CRDT object and their content merges. * * Opt-in (default `false`) to keep the document format compatible with older * `setContainer` child-container semantics. All collaborating clients must * use `loro-crdt >= 1.13.3`. * * @see https://loro.dev/blog/mergeable-containers */ mergeableMapChildContainers?: boolean; [key: string]: unknown; } type HasExplicitDefaultValue = S extends { options: { defaultValue: unknown; }; } ? true : false; export type AnySchemaOptions = SchemaOptions & { /** * Per-Any inference overrides. * * Notes: * - `defaultLoroText` defaults to `false` for Any when omitted (primitive string), * overriding the global `inferOptions.defaultLoroText`. * - `defaultMovableList` inherits from the global inference options unless specified. */ defaultLoroText?: boolean; defaultMovableList?: boolean; }; /** * Base interface for all schema types */ export interface BaseSchemaType { type: string; options: SchemaOptions; getContainerType(): ContainerType | null; } /** * Any schema type * * This schema defers container inference decisions to the runtime (Mirror). */ export interface AnySchemaType extends BaseSchemaType { type: "any"; options: AnySchemaOptions; } /** * String schema type */ export interface StringSchemaType extends BaseSchemaType { type: "string"; _t: T; } /** * Number schema type */ export interface NumberSchemaType extends BaseSchemaType { type: "number"; } /** * Boolean schema type */ export interface BooleanSchemaType extends BaseSchemaType { type: "boolean"; } /** * Ignored field schema type */ export interface IgnoreSchemaType extends BaseSchemaType { type: "ignore"; } /** * Loro Map schema type */ export interface LoroMapSchema> extends BaseSchemaType { type: "loro-map"; definition: SchemaDefinition; } /** * Enhanced LoroMapSchema with catchall support */ export interface LoroMapSchemaWithCatchall, C extends SchemaType> extends BaseSchemaType { type: "loro-map"; definition: SchemaDefinition; catchallType: C; catchall(catchallSchema: NewC): LoroMapSchemaWithCatchall; } /** * Loro List schema type */ export interface LoroListSchema extends BaseSchemaType { type: "loro-list"; itemSchema: T; idSelector?: (item: unknown) => string; } /** * Loro Movable List schema type */ export interface LoroMovableListSchema extends BaseSchemaType { type: "loro-movable-list"; itemSchema: T; idSelector?: (item: unknown) => string; } /** * Loro Text schema type */ export interface LoroTextSchemaType extends BaseSchemaType { type: "loro-text"; } /** * Loro Tree schema type * * Represents a tree where each node has a `data` map described by `nodeSchema`. */ export interface LoroTreeSchema> extends BaseSchemaType { type: "loro-tree"; nodeSchema: LoroMapSchema; } /** * Root schema type */ export interface RootSchemaType> extends BaseSchemaType { type: "schema"; definition: RootSchemaDefinition; } /** * Union of all schema types */ export type SchemaType = AnySchemaType | StringSchemaType | NumberSchemaType | BooleanSchemaType | IgnoreSchemaType | LoroMapSchema> | LoroMapSchemaWithCatchall, SchemaType> | LoroListSchema | LoroMovableListSchema | LoroTextSchemaType | LoroTreeSchema> | RootSchemaType>; export type ContainerSchemaType = LoroMapSchema> | LoroMapSchemaWithCatchall, SchemaType> | LoroListSchema | LoroMovableListSchema | LoroTextSchemaType | LoroTreeSchema>; /** * Schema definition type */ export type RootSchemaDefinition> = { [K in keyof T]: T[K]; }; /** * Schema definition type */ export type SchemaDefinition> = { [K in keyof T]: T[K]; }; /** * Check if a schema type is required * * true is default */ type IsSchemaRequired = S extends { options: { required: true; }; } ? true : S extends { options: { required: false; }; } ? false : S extends { options: { required?: undefined; }; } ? true : S extends { options: {}; } ? true : true; /** * Infer the JavaScript type from a schema type. */ export type InferType = S extends { transform: TransformDefinition; } ? WithTransformStartupOptionality : S extends StringSchemaType ? InferStringType : S extends NumberSchemaType ? InferNumberType : S extends BooleanSchemaType ? InferBooleanType : IsSchemaRequired extends false ? S extends AnySchemaType ? unknown : S extends IgnoreSchemaType ? unknown : S extends LoroTextSchemaType ? string | undefined : S extends LoroMapSchemaWithCatchall ? keyof M extends never ? ({ [key: string]: InferType; } & { $cid: string; }) | undefined : (({ [K in keyof M]: InferType; } & { [K in Exclude]: InferType; }) & { $cid: string; }) | undefined : S extends LoroMapSchema ? ({ [K in keyof M]: InferType; } & { $cid: string; }) | undefined : S extends LoroListSchema ? Array> | undefined : S extends LoroMovableListSchema ? Array> | undefined : S extends LoroTreeSchema ? Array> | undefined : S extends RootSchemaType ? { [K in keyof R]: InferType; } | undefined : never : S extends IgnoreSchemaType ? unknown : S extends LoroTextSchemaType ? string : S extends AnySchemaType ? unknown : S extends LoroMapSchemaWithCatchall ? keyof M extends never ? { [key: string]: InferType; } & { $cid: string; } : ({ [K in keyof M]: InferType; } & { [K in Exclude]: InferType; }) & { $cid: string; } : S extends LoroMapSchema ? { [K in keyof M]: InferType; } & { $cid: string; } : S extends LoroListSchema ? Array> : S extends LoroMovableListSchema ? Array> : S extends LoroTreeSchema ? Array> : S extends RootSchemaType ? { [K in keyof R]: InferType; } : never; /** * Infer the JavaScript type from a schema definition */ export type InferSchemaType> = { [K in keyof T]: InferType; }; /** * Infer the input (write) type for setState updates. * Identical to InferType except that for any LoroMap shape, the `$cid` field is optional. */ export type InferInputType = S extends { transform: TransformDefinition; } ? WithTransformStartupOptionality : S extends StringSchemaType ? InferStringType : S extends NumberSchemaType ? InferNumberType : S extends BooleanSchemaType ? InferBooleanType : IsSchemaRequired extends false ? S extends AnySchemaType ? unknown : S extends IgnoreSchemaType ? unknown : S extends LoroTextSchemaType ? string | undefined : S extends LoroMapSchemaWithCatchall ? keyof M extends never ? ({ [key: string]: InferInputType; } & { $cid?: string; }) | undefined : (({ [K in keyof M]: InferInputType; } & { [K in Exclude]: InferInputType; }) & { $cid?: string; }) | undefined : S extends LoroMapSchema ? ({ [K in keyof M]: InferInputType; } & { $cid?: string; }) | undefined : S extends LoroListSchema ? Array> | undefined : S extends LoroMovableListSchema ? Array> | undefined : S extends LoroTreeSchema ? Array> | undefined : S extends RootSchemaType ? { [K in keyof R]: InferInputType; } | undefined : never : S extends IgnoreSchemaType ? unknown : S extends LoroTextSchemaType ? string : S extends AnySchemaType ? unknown : S extends LoroMapSchemaWithCatchall ? keyof M extends never ? { [key: string]: InferInputType; } & { $cid?: string; } : ({ [K in keyof M]: InferInputType; } & { [K in Exclude]: InferInputType; }) & { $cid?: string; } : S extends LoroMapSchema ? { [K in keyof M]: InferInputType; } & { $cid?: string; } : S extends LoroListSchema ? Array> : S extends LoroMovableListSchema ? Array> : S extends LoroTreeSchema ? Array> : S extends RootSchemaType ? { [K in keyof R]: InferInputType; } : never; /** * Helper: Infer the node type for a tree schema */ export type InferTreeNodeType> = { id: string; data: { [K in keyof M]: InferType; }; children: Array>; }; /** * Helper: Infer the node type for a tree schema whose node.data map includes $cid */ export type InferTreeNodeTypeWithCid> = { id: string; data: { [K in keyof M]: InferType; } & { $cid: string; }; children: Array>; }; /** * Helper: Input node type for a tree schema (node.data has optional $cid) */ export type InferInputTreeNodeType> = { id: string; data: { [K in keyof M]: InferInputType; } & { $cid?: string; }; children: Array>; }; /** * Equality comparison strategy for transformed values. * * Reference equality is ALWAYS checked first. * This setting controls what happens when references differ. * * - "reference-equality" (default): If refs differ, treat as not equal (no encoding). * Correct for Immer-draftable types, immutable types, and copy-on-change patterns. * Fast because no encoding is needed. * * - "encoded-value-equality": If refs differ, encode both and compare. * Use when you may have different objects with the same encoded value * and want to avoid redundant updates. * * - "deep-equality": If refs differ, perform a deep equality check on the domain values. * This is rarely needed as Immer-draftable objects typically maintain reference equality when unchanged, * but can be useful for non-Immer-managed objects that are slow to encode and have no simple equality check. * * - Custom function: Your own comparison logic. Receives the domain values. */ export type EqualityStrategy = "reference-equality" | "encoded-value-equality" | "deep-equality" | ((a: DomainType, b: DomainType) => boolean); /** * Transform definition for bidirectional conversion between CRDT primitives and domain types. * It is strongly recommend that DomainType is immutable or * [supported by Immer](https://immerjs.github.io/immer/complex-objects/) otherwise changes to transformed * values may not be detected and converted to CRDT operations. Never mutate DomainType instances * outside of Loro Mirror's setState function. CRDTType and DomainType can be null or undefined, but decode/encode functions * will never receive null/undefined - they pass through as-is. Validation is performed on the domain type after transformation * (or more precisely, validation on domain types happens before encoding). */ export interface TransformDefinition { /** * Convert CRDT primitive to domain type. * Never called with null/undefined - they pass through as-is. */ decode: (value: CRDTType & {}) => DomainType & {}; /** * Convert domain type to CRDT primitive. * Never called with null/undefined - they pass through as-is. */ encode: (value: DomainType & {}) => CRDTType & {}; /** * Validate the domain value. * Called during schema validation after null/undefined checks pass. * Return true if valid, or a string error message if invalid. * The validate function passed to schema.String | Number | Boolean will also be called. */ validate?: (value: DomainType & {}) => boolean | string; /** * How to compare domain values for equality. * * Reference equality (===) is always checked first. * This setting controls behavior when references differ. * * @default "reference-equality" */ isEqual?: EqualityStrategy; /** * Whether to validate that encode() returns the correct CRDT type during schema validation. * When true, encode() is called on every validation to check the return type. * When false, encode type checking is skipped for better performance. * * @default false */ validateEncodedType?: boolean; } /** * Add | undefined to T if schema is optional (required: false). * Used by InferType to add optionality to both transformed and non-transformed fields. */ type WithOptionality = IsSchemaRequired extends false ? T | undefined : T; type WithTransformStartupOptionality = IsSchemaRequired extends false ? T | undefined : HasExplicitDefaultValue extends true ? T : T | undefined; type InferStringType = S extends { transform: TransformDefinition; } ? WithOptionality : S extends StringSchemaType ? WithOptionality : WithOptionality; type InferNumberType = S extends { transform: TransformDefinition; } ? WithOptionality : WithOptionality; type InferBooleanType = S extends { transform: TransformDefinition; } ? WithOptionality : WithOptionality; export {}; //# sourceMappingURL=types.d.ts.map