loro-mirror
Version:
Type-safe state management synchronized with Loro CRDT via a declarative schema and bidirectional mirroring.
334 lines (308 loc) • 11.8 kB
text/typescript
/**
* Types for the schema definition system
*/
import { ContainerType } from "loro-crdt";
/**
* 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 */
validate?: (value: unknown) => boolean | string;
[key: string]: unknown;
}
/**
* Base interface for all schema types
*/
export interface BaseSchemaType {
type: string;
options: SchemaOptions;
getContainerType(): ContainerType | null;
}
/**
* String schema type
*/
export interface StringSchemaType<T extends string = string> 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<T extends Record<string, SchemaType>>
extends BaseSchemaType {
type: "loro-map";
definition: SchemaDefinition<T>;
}
/**
* Enhanced LoroMapSchema with catchall support
*/
export interface LoroMapSchemaWithCatchall<
T extends Record<string, SchemaType>,
C extends SchemaType
> extends BaseSchemaType {
type: "loro-map";
definition: SchemaDefinition<T>;
catchallType: C;
catchall<NewC extends SchemaType>(catchallSchema: NewC): LoroMapSchemaWithCatchall<T, NewC>;
}
/**
* Loro List schema type
*/
export interface LoroListSchema<T extends SchemaType> extends BaseSchemaType {
type: "loro-list";
itemSchema: T;
idSelector?: (item: unknown) => string;
}
/**
* Loro Movable List schema type
*/
export interface LoroMovableListSchema<T extends SchemaType>
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<T extends Record<string, SchemaType>>
extends BaseSchemaType {
type: "loro-tree";
nodeSchema: LoroMapSchema<T>;
}
/**
* Root schema type
*/
export interface RootSchemaType<T extends Record<string, ContainerSchemaType>>
extends BaseSchemaType {
type: "schema";
definition: RootSchemaDefinition<T>;
}
/**
* Union of all schema types
*/
export type SchemaType =
| StringSchemaType
| NumberSchemaType
| BooleanSchemaType
| IgnoreSchemaType
| LoroMapSchema<Record<string, SchemaType>>
| LoroMapSchemaWithCatchall<Record<string, SchemaType>, SchemaType>
| LoroListSchema<SchemaType>
| LoroMovableListSchema<SchemaType>
| LoroTextSchemaType
| LoroTreeSchema<Record<string, SchemaType>>
| RootSchemaType<Record<string, ContainerSchemaType>>;
export type ContainerSchemaType =
| LoroMapSchema<Record<string, SchemaType>>
| LoroMapSchemaWithCatchall<Record<string, SchemaType>, SchemaType>
| LoroListSchema<SchemaType>
| LoroMovableListSchema<SchemaType>
| LoroTextSchemaType
| LoroTreeSchema<Record<string, SchemaType>>;
/**
* Schema definition type
*/
export type RootSchemaDefinition<
T extends Record<string, ContainerSchemaType>,
> = {
[K in keyof T]: T[K];
};
/**
* Schema definition type
*/
export type SchemaDefinition<T extends Record<string, SchemaType>> = {
[K in keyof T]: T[K];
};
/**
* Check if a schema type is required
*
* true is default
*/
type IsSchemaRequired<S extends SchemaType> =
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 SchemaType> =
IsSchemaRequired<S> extends false
? (
S extends StringSchemaType<infer T>
? T | undefined
: S extends NumberSchemaType
? number | undefined
: S extends BooleanSchemaType
? boolean | undefined
: S extends IgnoreSchemaType
? unknown
: S extends LoroTextSchemaType
? string | undefined
: S extends LoroMapSchemaWithCatchall<infer M, infer C>
? (keyof M extends never
? ({ [key: string]: InferType<C> } & { $cid: string }) | undefined
: (({ [K in keyof M]: InferType<M[K]> } & { [K in Exclude<string, keyof M>]: InferType<C> }) & { $cid: string }) | undefined)
: S extends LoroMapSchema<infer M>
? ({ [K in keyof M]: InferType<M[K]> } & { $cid: string }) | undefined
: S extends LoroListSchema<infer I>
? Array<InferType<I>> | undefined
: S extends LoroMovableListSchema<infer I>
? Array<InferType<I>> | undefined
: S extends LoroTreeSchema<infer M>
? Array<InferTreeNodeTypeWithCid<M>> | undefined
: S extends RootSchemaType<infer R>
? { [K in keyof R]: InferType<R[K]> } | undefined
: never
)
: (
S extends StringSchemaType<infer T>
? T
: S extends NumberSchemaType
? number
: S extends BooleanSchemaType
? boolean
: S extends IgnoreSchemaType
? unknown
: S extends LoroTextSchemaType
? string
: S extends LoroMapSchemaWithCatchall<infer M, infer C>
? keyof M extends never
? ({ [key: string]: InferType<C> } & { $cid: string })
: (({ [K in keyof M]: InferType<M[K]> } & { [K in Exclude<string, keyof M>]: InferType<C> }) & { $cid: string })
: S extends LoroMapSchema<infer M>
? ({ [K in keyof M]: InferType<M[K]> } & { $cid: string })
: S extends LoroListSchema<infer I>
? Array<InferType<I>>
: S extends LoroMovableListSchema<infer I>
? Array<InferType<I>>
: S extends LoroTreeSchema<infer M>
? Array<InferTreeNodeTypeWithCid<M>>
: S extends RootSchemaType<infer R>
? { [K in keyof R]: InferType<R[K]> }
: never
);
/**
* Infer the JavaScript type from a schema definition
*/
export type InferSchemaType<T extends Record<string, SchemaType>> = {
[K in keyof T]: InferType<T[K]>;
};
/**
* Infer the input (write) type for setState updates.
* Identical to InferType<S> except that for any LoroMap shape, the `$cid` field is optional.
*/
export type InferInputType<S extends SchemaType> =
IsSchemaRequired<S> extends false
? (
S extends StringSchemaType<infer T>
? T | undefined
: S extends NumberSchemaType
? number | undefined
: S extends BooleanSchemaType
? boolean | undefined
: S extends IgnoreSchemaType
? unknown
: S extends LoroTextSchemaType
? string | undefined
: S extends LoroMapSchemaWithCatchall<infer M, infer C>
? (keyof M extends never
? ({ [key: string]: InferInputType<C> } & { $cid?: string }) | undefined
: (({ [K in keyof M]: InferInputType<M[K]> } & { [K in Exclude<string, keyof M>]: InferInputType<C> }) & { $cid?: string }) | undefined)
: S extends LoroMapSchema<infer M>
? ({ [K in keyof M]: InferInputType<M[K]> } & { $cid?: string }) | undefined
: S extends LoroListSchema<infer I>
? Array<InferInputType<I>> | undefined
: S extends LoroMovableListSchema<infer I>
? Array<InferInputType<I>> | undefined
: S extends LoroTreeSchema<infer M>
? Array<InferInputTreeNodeType<M>> | undefined
: S extends RootSchemaType<infer R>
? { [K in keyof R]: InferInputType<R[K]> } | undefined
: never
)
: (
S extends StringSchemaType<infer T>
? T
: S extends NumberSchemaType
? number
: S extends BooleanSchemaType
? boolean
: S extends IgnoreSchemaType
? unknown
: S extends LoroTextSchemaType
? string
: S extends LoroMapSchemaWithCatchall<infer M, infer C>
? keyof M extends never
? ({ [key: string]: InferInputType<C> } & { $cid?: string })
: (({ [K in keyof M]: InferInputType<M[K]> } & { [K in Exclude<string, keyof M>]: InferInputType<C> }) & { $cid?: string })
: S extends LoroMapSchema<infer M>
? ({ [K in keyof M]: InferInputType<M[K]> } & { $cid?: string })
: S extends LoroListSchema<infer I>
? Array<InferInputType<I>>
: S extends LoroMovableListSchema<infer I>
? Array<InferInputType<I>>
: S extends LoroTreeSchema<infer M>
? Array<InferInputTreeNodeType<M>>
: S extends RootSchemaType<infer R>
? { [K in keyof R]: InferInputType<R[K]> }
: never
);
/**
* Helper: Infer the node type for a tree schema
*/
export type InferTreeNodeType<M extends Record<string, SchemaType>> = {
id: string;
data: { [K in keyof M]: InferType<M[K]> };
children: Array<InferTreeNodeType<M>>;
};
/**
* Helper: Infer the node type for a tree schema whose node.data map includes $cid
*/
export type InferTreeNodeTypeWithCid<M extends Record<string, SchemaType>> = {
id: string;
data: ({ [K in keyof M]: InferType<M[K]> } & { $cid: string });
children: Array<InferTreeNodeTypeWithCid<M>>;
};
/**
* Helper: Input node type for a tree schema (node.data has optional $cid)
*/
export type InferInputTreeNodeType<M extends Record<string, SchemaType>> = {
id: string;
data: ({ [K in keyof M]: InferInputType<M[K]> } & { $cid?: string });
children: Array<InferInputTreeNodeType<M>>;
};