/** * @sylphx/lens-core - Schema Type Builders * * Type-safe DSL for defining entity schemas. * Every type supports full TypeScript inference. */ declare const __brand: unique symbol; type Brand< T, B > = T & { [__brand]: B; }; /** Resolver context with parent data */ interface ResolverContext< Parent, TContext > { parent: Parent; ctx: TContext; } /** Subscription context with emit function */ interface SubscriptionContext< T, Parent, TContext > { parent: Parent; ctx: TContext; emit: (value: T) => void; onCleanup?: ((fn: () => void) => void) | undefined; } /** Resolver function type */ type ResolverFn< T, Parent, TContext > = (context: ResolverContext) => T | Promise; /** Subscription resolver function type */ type SubscriptionResolverFn< T, Parent, TContext > = (context: SubscriptionContext) => void; /** Field resolution mode */ type FieldResolutionMode = "exposed" | "resolve" | "subscribe"; /** Base class for all field types */ declare abstract class FieldType< T = unknown, SerializedT = T > { abstract readonly _type: string; abstract readonly _tsType: T; readonly _nullable: boolean; readonly _optional: boolean; readonly _default?: T | undefined; /** Resolution mode for this field */ readonly _resolutionMode: FieldResolutionMode; /** Resolver function (if _resolutionMode is 'resolve') */ readonly _resolver?: ResolverFn | undefined; /** Subscription resolver function (if _resolutionMode is 'subscribe') */ readonly _subscriptionResolver?: SubscriptionResolverFn | undefined; /** Make this field nullable (value can be null) */ nullable(): NullableType; /** * Make this field optional (may not be included in response) * Use for input types where a field is not required */ optional(): OptionalType; /** Set default value */ default(value: T): DefaultType; /** Check if field is nullable */ isNullable(): boolean; /** Check if field is optional */ isOptional(): boolean; /** Get default value */ getDefault(): T | undefined; /** * Serialize value for transport (server → client) * Override this for custom types (e.g., Date → ISO string) */ serialize(value: T): SerializedT; /** * Deserialize value from transport (client ← server) * Override this for custom types (e.g., ISO string → Date) */ deserialize(value: SerializedT): T; /** * Attach a resolver function to compute this field's value. * Use when the field value needs to be computed from parent data or context. * * @example * ```typescript * const User = entity("User", (t) => ({ * fullName: t.string().resolve(({ parent }) => * `${parent.firstName} ${parent.lastName}` * ), * })); * ``` */ resolve< Parent = unknown, TContext = unknown >(fn: ResolverFn): ResolvedFieldType; /** * Attach a subscription resolver to stream this field's value. * Use for real-time fields that push updates to clients. * * @example * ```typescript * const User = entity("User", (t) => ({ * status: t.json().subscribe(({ ctx }) => { * ctx.emit({ isActive: true, text: "Online" }); * }), * })); * ``` */ subscribe< Parent = unknown, TContext = unknown >(fn: SubscriptionResolverFn): SubscribedFieldType; /** * Check if this field has a resolver attached. */ hasResolver(): boolean; /** * Check if this field has a subscription resolver attached. */ hasSubscription(): boolean; /** * Get the resolution mode for this field. */ getResolutionMode(): FieldResolutionMode; } /** Wrapper type for nullable fields */ type NullableType> = Omit & { _nullable: true; _tsType: T["_tsType"] | null; }; /** Wrapper type for optional fields (undefined, not included in response) */ type OptionalType> = Omit & { _tsType: T["_tsType"] | undefined; _optional: true; }; /** Wrapper type for fields with defaults */ type DefaultType< T extends FieldType, D > = T & { _default: D; }; /** Wrapper type for fields with resolver attached */ type ResolvedFieldType< T extends FieldType, _Parent = unknown, _TContext = unknown > = Omit & { _resolutionMode: "resolve"; _resolver: ResolverFn; }; /** Wrapper type for fields with subscription resolver attached */ type SubscribedFieldType< T extends FieldType, _Parent = unknown, _TContext = unknown > = Omit & { _resolutionMode: "subscribe"; _subscriptionResolver: SubscriptionResolverFn; }; /** ID field type (primary key) */ declare class IdType extends FieldType { readonly _type: "id"; readonly _tsType: string; } /** String field type */ declare class StringType extends FieldType { readonly _type: "string"; readonly _tsType: string; } /** Integer field type */ declare class IntType extends FieldType { readonly _type: "int"; readonly _tsType: number; } /** Float field type */ declare class FloatType extends FieldType { readonly _type: "float"; readonly _tsType: number; } /** Boolean field type */ declare class BooleanType extends FieldType { readonly _type: "boolean"; readonly _tsType: boolean; } /** DateTime field type (serialized as ISO string) */ declare class DateTimeType extends FieldType { readonly _type: "datetime"; readonly _tsType: Date; /** * Serialize Date → ISO string for transport * @example Date(2024-01-15) → "2024-01-15T00:00:00.000Z" */ serialize(value: Date): string; /** * Deserialize ISO string → Date * @example "2024-01-15T00:00:00.000Z" → Date(2024-01-15) */ deserialize(value: string): Date; validate(value: unknown): boolean; } /** Timestamp field type (Unix timestamp in milliseconds) */ declare class TimestampType extends FieldType { readonly _type: "timestamp"; readonly _tsType: number; /** * Validate timestamp is a valid Unix timestamp (milliseconds) */ validate(value: unknown): boolean; } /** Decimal field type (serialized as string for precision) */ declare class DecimalType extends FieldType { readonly _type: "decimal"; readonly _tsType: number; /** * Serialize number → string to preserve precision * @example 123.456789 → "123.456789" * * **Why string?** JavaScript numbers lose precision for large/small values. * Decimal/currency values must maintain exact precision during transport. */ serialize(value: number): string; /** * Deserialize string → number * @example "123.456789" → 123.456789 */ deserialize(value: string): number; validate(value: unknown): boolean; } /** BigInt field type (serialized as string for precision) */ declare class BigIntType extends FieldType { readonly _type: "bigint"; readonly _tsType: bigint; /** * Serialize BigInt → string * @example 9007199254740993n → "9007199254740993" * * **Why string?** BigInt exceeds JSON number limits. * String preserves exact value during transport. */ serialize(value: bigint): string; /** * Deserialize string → BigInt * @example "9007199254740993" → 9007199254740993n */ deserialize(value: string): bigint; validate(value: unknown): boolean; } /** Bytes field type (serialized as base64 string) */ declare class BytesType extends FieldType { readonly _type: "bytes"; readonly _tsType: Uint8Array; /** * Serialize Uint8Array → base64 string * @example Uint8Array([72, 101, 108, 108, 111]) → "SGVsbG8=" */ serialize(value: Uint8Array): string; /** * Deserialize base64 string → Uint8Array * @example "SGVsbG8=" → Uint8Array([72, 101, 108, 108, 111]) */ deserialize(value: string): Uint8Array; validate(value: unknown): boolean; } /** JSON field type (arbitrary JSON data, typed as unknown) */ declare class JsonType extends FieldType { readonly _type: "json"; readonly _tsType: unknown; /** * JSON passes through as-is (already JSON-serializable) * Use for schemaless/dynamic data where type isn't known at compile time */ serialize(value: unknown): unknown; deserialize(value: unknown): unknown; } /** Enum field type */ declare class EnumType extends FieldType { readonly values: T; readonly _type: "enum"; readonly _tsType: T[number]; constructor(values: T); } /** Typed object field type */ declare class ObjectType extends FieldType { readonly _type: "object"; readonly _tsType: T; } /** Array field type */ declare class ArrayType extends FieldType { readonly itemType: FieldType; readonly _type: "array"; readonly _tsType: T[]; constructor(itemType: FieldType); } /** * Scalar type definition interface. * Used with `scalar()` to create custom scalar types. */ interface ScalarTypeDefinition< T, SerializedT = T > { /** Type name (for debugging/introspection) */ name: string; /** Serialize value for transport (T → SerializedT) */ serialize: (value: T) => SerializedT; /** Deserialize value from transport (SerializedT → T) */ deserialize: (value: SerializedT) => T; /** Optional validation before serialization */ validate?: (value: unknown) => boolean; } /** * Custom scalar type with user-defined serialization. * * @example * ```typescript * const User = model('User', { * location: scalar('Point', { * serialize: (p) => ({ lat: p.lat, lng: p.lng }), * deserialize: (data) => new Point(data.lat, data.lng), * }), * }) * ``` */ declare class ScalarType< T, SerializedT = T > extends FieldType { readonly definition: ScalarTypeDefinition; readonly _type: "scalar"; readonly _tsType: T; constructor(definition: ScalarTypeDefinition); serialize(value: T): SerializedT; deserialize(value: SerializedT): T; validate(value: unknown): boolean; } /** Relation type brand */ type RelationBrand = Brand; /** HasOne relation (1:1, owns the relation) */ declare class HasOneType extends FieldType { readonly target: Target; readonly foreignKey?: string | undefined; readonly _type: "hasOne"; readonly _tsType: RelationBrand; readonly _relationKind: "hasOne"; constructor(target: Target, foreignKey?: string | undefined); } /** HasMany relation (1:N) */ declare class HasManyType extends FieldType { readonly target: Target; readonly foreignKey?: string | undefined; readonly _type: "hasMany"; readonly _tsType: RelationBrand[]; readonly _relationKind: "hasMany"; constructor(target: Target, foreignKey?: string | undefined); } /** BelongsTo relation (N:1, foreign key side) */ declare class BelongsToType extends FieldType { readonly target: Target; readonly foreignKey?: string | undefined; readonly _type: "belongsTo"; readonly _tsType: RelationBrand; readonly _relationKind: "belongsTo"; constructor(target: Target, foreignKey?: string | undefined); } /** * Lazy one-to-one relation. * Uses lazy evaluation to solve circular reference issues. * * @example * ```typescript * const User = entity("User", (t) => ({ * profile: t.one(() => Profile).resolve(({ parent, ctx }) => * ctx.db.profiles.find(p => p.userId === parent.id) * ), * })); * ``` */ declare class LazyOneType< Target, TargetData = unknown > extends FieldType { readonly targetRef: () => Target; readonly _type: "lazyOne"; readonly _tsType: TargetData; readonly _relationKind: "one"; constructor(targetRef: () => Target); /** Get the target entity (evaluates lazy reference) */ getTarget(): Target; } /** * Lazy one-to-many relation. * Uses lazy evaluation to solve circular reference issues. * * @example * ```typescript * const User = entity("User", (t) => ({ * posts: t.many(() => Post).resolve(({ parent, ctx }) => * ctx.db.posts.filter(p => p.authorId === parent.id) * ), * })); * ``` */ declare class LazyManyType< Target, TargetData = unknown > extends FieldType { readonly targetRef: () => Target; readonly _type: "lazyMany"; readonly _tsType: TargetData[]; readonly _relationKind: "many"; constructor(targetRef: () => Target); /** Get the target entity (evaluates lazy reference) */ getTarget(): Target; } /** Check if field is a scalar type */ declare function isScalarType(field: FieldType): boolean; /** Field definition (any field type) */ type FieldDefinition = FieldType; /** Entity definition (collection of fields) */ type EntityDefinition = Record; /** Schema definition (collection of entities) */ type SchemaDefinition = Record; import { EntityMarker } from "@sylphx/standard-entity"; /** ID field (primary key, string type) */ declare function id(): IdType; /** String field */ declare function string(): StringType; /** Integer field */ declare function int(): IntType; /** Float field */ declare function float(): FloatType; /** Boolean field */ declare function boolean(): BooleanType; /** DateTime field (serialized as ISO string) */ declare function datetime(): DateTimeType; /** Timestamp field (Unix timestamp in milliseconds) */ declare function timestamp(): TimestampType; /** Decimal field (serialized as string for precision) */ declare function decimal(): DecimalType; /** BigInt field (serialized as string for precision) */ declare function bigint(): BigIntType; /** Binary data field (serialized as base64) */ declare function bytes(): BytesType; /** JSON field (schemaless, typed as unknown) */ declare function json(): JsonType; /** Enum field with specific values */ declare function enumType(values: T): EnumType; /** Typed object field */ declare function object(): ObjectType; /** * Custom scalar type with user-defined serialization. * * @example * ```typescript * const User = model('User', { * id: id(), * location: scalar('Point', { * serialize: (p) => ({ lat: p.lat, lng: p.lng }), * deserialize: (data) => new Point(data.lat, data.lng), * }), * }) * ``` */ declare function scalar< T, SerializedT = T >(name: string, options: { serialize: (value: T) => SerializedT; deserialize: (value: SerializedT) => T; validate?: (value: unknown) => boolean; }): ScalarType; /** Model-like type that can be referenced */ type ModelLike = ModelDef; /** Lazy model reference (for circular dependencies) */ type LazyModelRef = () => T; /** * Valid field definition types: * - Scalar types: id(), string(), int(), etc. * - Direct model reference: Profile * - Lazy model reference: () => Profile * - List: list(string()), list(Profile), list(() => Post) * - Nullable: nullable(string()), nullable(Profile) */ type FieldDef = FieldType | ModelLike | LazyModelRef | ListDef | NullableDef; /** Symbol to identify list types */ declare const LIST_SYMBOL: unique symbol; /** Symbol to identify nullable types */ declare const NULLABLE_SYMBOL: unique symbol; /** * List/array wrapper. * Works for both model fields and return types. */ interface ListDef { [LIST_SYMBOL]: true; _inner: T; } /** Check if value is a ListDef */ declare function isListDef(value: unknown): value is ListDef; /** * List/array type. * Works for both model fields and return types. * * @example * ```typescript * // Model fields * tags: list(string()) // string[] * posts: list(Post) // Post[] * posts: list(() => Post) // Post[] (lazy ref for circular deps) * * // Return types * .returns(list(User)) // User[] * ``` */ declare function list>(inner: T): ListDef; declare function list(inner: T): ListDef; declare function list(inner: LazyModelRef): ListDef>; /** * Nullable wrapper. * Works for both model fields and return types. */ interface NullableDef { [NULLABLE_SYMBOL]: true; _inner: T; } /** Check if value is a NullableDef */ declare function isNullableDef(value: unknown): value is NullableDef; /** * Nullable type. * Works for both model fields and return types. * * @example * ```typescript * // Model fields * bio: nullable(string()) // string | null * profile: nullable(Profile) // Profile | null * tags: nullable(list(string())) // string[] | null * * // Return types * .returns(nullable(User)) // User | null * ``` */ declare function nullable>(inner: T): NullableDef; declare function nullable(inner: T): NullableDef; declare function nullable(inner: LazyModelRef): NullableDef>; declare function nullable(inner: ListDef): NullableDef>; /** * Unwrap a wrapped type to get the inner model. */ type UnwrapType = T extends NullableDef ? UnwrapType : T extends ListDef ? UnwrapType : T; /** * Check if a type is nullable. */ type IsNullable = T extends NullableDef ? true : false; /** * Check if a type is a list. */ type IsList = T extends NullableDef ? IsList : T extends ListDef ? true : false; /** * Check if inner type is a lazy reference (function). */ declare function isLazyRef(value: unknown): value is () => unknown; /** Infer the TypeScript type from a FieldDef */ type InferFieldDefType = T extends ListDef ? InferFieldDefType[] : T extends NullableDef ? InferFieldDefType | null : T extends FieldType ? V : T extends ModelLike ? T : T extends LazyModelRef ? M : never; /** * Process a field definition to extract metadata. * Used internally by model() to understand field structure. */ declare function processFieldDef(def: FieldDef): { isNullable: boolean; isList: boolean; isLazy: boolean; innerType: unknown; }; /** Type mapping from field _type to TypeScript type */ type ScalarTypeMap = { id: string; string: string; int: number; float: number; decimal: number; boolean: boolean; datetime: Date; date: Date; bigint: bigint; bytes: Uint8Array; json: unknown; }; /** Infer base TypeScript type from field's _type property */ type InferBaseType = T extends { _type: infer Type extends keyof ScalarTypeMap; } ? ScalarTypeMap[Type] : T extends EnumType ? V[number] : T extends ObjectType ? O : T extends ArrayType ? I[] : never; /** Apply optional/nullable modifiers to base type */ type ApplyModifiers< Base, T > = T extends { _optional: true; } ? Base | undefined : T extends { _nullable: true; } ? Base | null : Base; /** Infer TypeScript type from a scalar field type (handles optional/nullable) */ type InferScalar = ApplyModifiers, T>; /** Infer the target entity name from a relation type */ type InferRelationTarget = T extends { target: infer Target; } ? Target : never; /** Check if a field is a relation */ type IsRelation = T extends { _relationKind: "hasOne" | "hasMany" | "belongsTo"; } ? true : false; /** Check if a field is hasMany */ type IsHasMany = T extends { _relationKind: "hasMany"; } ? true : false; /** Check if a field is hasOne */ type IsHasOne = T extends { _relationKind: "hasOne"; } ? true : false; /** Check if a field is belongsTo */ type IsBelongsTo = T extends { _relationKind: "belongsTo"; } ? true : false; /** Extract scalar field keys from entity definition */ type ScalarFields = { [K in keyof E] : IsRelation extends true ? never : K }[keyof E]; /** Extract relation field keys from entity definition */ type RelationFields = { [K in keyof E] : IsRelation extends true ? K : never }[keyof E]; /** Infer full entity type from definition, resolving relations within schema */ type InferEntity< E extends EntityDefinition, S extends SchemaDefinition = never > = { [K in ScalarFields] : InferFieldType } & { [K in RelationFields] : InferRelationType }; /** Infer field type (scalar or relation) */ type InferFieldType< F extends FieldDefinition, S extends SchemaDefinition > = IsRelation extends true ? InferRelationType : InferScalarWithNullable; /** Infer scalar type with nullable support */ type InferScalarWithNullable = F extends { _nullable: true; } ? InferScalar | null : InferScalar; /** Infer relation type, resolving to target entity if schema provided */ type InferRelationType< F extends FieldDefinition, S extends SchemaDefinition > = [S] extends [never] ? F extends HasManyType ? Array<{ __entity: Target; }> : F extends HasOneType ? { __entity: Target; } | null : F extends BelongsToType ? { __entity: Target; } : never : F extends HasManyType ? Target extends keyof S ? Array> : never : F extends HasOneType ? Target extends keyof S ? InferEntity | null : never : F extends BelongsToType ? Target extends keyof S ? InferEntity : never : never; /** Field arguments type (for computed/relation fields with args) */ type FieldArgs = Record; /** Scalar field selection options (for fields with arguments) */ type ScalarSelectOptions = { /** Field arguments (GraphQL-style) */ args?: FieldArgs; }; /** Nested relation selection options */ type RelationSelectOptions< Target extends string, S extends SchemaDefinition > = Target extends keyof S ? { /** Field arguments (GraphQL-style) */ args?: FieldArgs; /** Nested field selection */ select?: Select2; /** Limit results (for hasMany) */ take?: number; /** Skip results (for hasMany) */ skip?: number; } : never; /** Selection object type with type-safe nested relations */ type Select2< E extends EntityDefinition, S extends SchemaDefinition = never > = { [K in keyof E]? : IsRelation extends true ? true | RelationSelectOptions & string, S> : true | ScalarSelectOptions }; /** Infer selected type from selection */ type InferSelected< E extends EntityDefinition, Sel extends Select2, S extends SchemaDefinition = never > = { [K in keyof Sel & keyof E] : Sel[K] extends true ? InferFieldType : Sel[K] extends { select: infer NestedSel; } ? E[K] extends HasManyType ? Target extends keyof S ? NestedSel extends Select2 ? Array> : never : never : E[K] extends HasOneType ? Target extends keyof S ? NestedSel extends Select2 ? InferSelected | null : never : never : E[K] extends BelongsToType ? Target extends keyof S ? NestedSel extends Select2 ? InferSelected : never : never : never : Sel[K] extends { args: FieldArgs; } ? InferFieldType : InferFieldType }; /** Infer all entity types from schema */ type InferSchemaEntities = { [K in keyof S] : InferEntity }; /** Get entity names from schema */ type EntityNames = keyof S & string; /** Get entity type by name */ type EntityType< S extends SchemaDefinition, Name extends keyof S > = InferEntity; /** Check if a field is nullable or has a default */ type IsOptionalField = F extends { _nullable: true; } ? true : F extends { _default: unknown; } ? true : false; /** Extract required scalar fields (not id, not nullable, no default) */ type RequiredScalarFields = { [K in ScalarFields as K extends "id" ? never : IsOptionalField extends true ? never : K] : InferScalar }; /** Extract optional scalar fields (nullable or has default) */ type OptionalScalarFields = { [K in ScalarFields as K extends "id" ? never : IsOptionalField extends true ? K : never]? : InferScalarWithNullable }; /** Create input type with proper optional handling */ type CreateInput< E extends EntityDefinition, _S extends SchemaDefinition = never > = RequiredScalarFields & OptionalScalarFields & { [K in RelationFields]? : E[K] extends BelongsToType ? string : never }; /** Update input type (id required, all else optional) */ type UpdateInput< E extends EntityDefinition, S extends SchemaDefinition = never > = { id: string; } & Partial>; /** Delete input type */ type DeleteInput = { id: string; }; /** Make specific keys required */ type RequireKeys< T, K extends keyof T > = T & { [P in K]-? : T[P] }; /** Make specific keys optional */ type OptionalKeys< T, K extends keyof T > = Omit & { [P in K]? : T[P] }; /** Deep partial type */ type DeepPartial = T extends object ? { [P in keyof T]? : DeepPartial } : T; /** Symbol to identify model definitions */ declare const MODEL_SYMBOL: unique symbol; /** * Plain object field definition. * Each field can be a scalar type, model reference, or wrapped type. * * @example * ```typescript * { * id: id(), * name: string(), * bio: nullable(string()), * tags: list(string()), * posts: list(() => Post), * profile: Profile, * } * ``` */ type PlainFieldDefinition = Record; /** * Model definition with name and fields. * * Implements StandardEntity protocol for type-safe Reify operations. * Extends EntityMarker from @sylphx/standard-entity for protocol compliance. */ interface ModelDef< Name extends string = string, Fields extends EntityDefinition = EntityDefinition > extends EntityMarker { [MODEL_SYMBOL]: true; /** Model name */ _name: Name; /** Model fields */ readonly fields: Fields; /** Whether this model has an id field (normalizable) */ readonly _hasId: boolean; } /** * Extract the inferred model type from a ModelDef. * Use this when you need the actual TypeScript type of a model. * * @example * ```typescript * type UserData = InferModelType; * // { id: string; name: string; email: string; ... } * ``` */ type InferModelType = M extends ModelDef ? InferEntity : never; /** * Type helper to process plain field definitions. * Maps FieldDef types to their processed FieldType equivalents. */ type ProcessedFields = { [K in keyof T] : T[K] extends FieldType ? FieldType : FieldType }; /** * Factory for creating models with typed context. * Returns a function that creates models with plain object fields. * * @example * ```typescript * const { model } = lens(); * * const User = model("User", { * id: id(), * name: string(), * }); * ``` */ type ModelFactory<_TContext> = < Name extends string, FieldDefs extends PlainFieldDefinition >(name: Name, fields: FieldDefs) => ModelDef>; /** * Define a model with fields. * * Models with `id` field are normalizable and cacheable. * Models without `id` are pure types. * * @example * ```typescript * import { model, id, string, list, nullable } from '@sylphx/lens-core' * * const User = model('User', { * id: id(), * name: string(), * bio: nullable(string()), * posts: list(() => Post), * }) * ``` */ declare function model(): ModelFactory; declare function model< Name extends string, FieldDefs extends PlainFieldDefinition >(name: Name, fields: FieldDefs): ModelDef>; /** Check if value is a ModelDef */ declare function isModelDef(value: unknown): value is ModelDef; /** Check if model is normalizable (has id) */ declare function isNormalizableModel(value: unknown): boolean; import { z } from "zod"; /** * @sylphx/lens-core - Update Strategy Types * * Type definitions for update strategies. */ /** Update strategy names */ type StrategyName = "value" | "delta" | "patch" | "array"; /** Base update interface */ interface Update< S extends StrategyName = StrategyName, D = unknown > { strategy: S; data: D; } /** Value update - full replacement */ interface ValueUpdate extends Update<"value", T> { strategy: "value"; data: T; } /** Delta update - character-level diff for strings */ interface DeltaUpdate extends Update<"delta", DeltaOperation[]> { strategy: "delta"; data: DeltaOperation[]; } /** Delta operation (insert/delete at position) */ interface DeltaOperation { /** Position in the string */ position: number; /** Number of characters to delete */ delete?: number; /** Text to insert */ insert?: string; } /** Patch update - JSON Patch RFC 6902 */ interface PatchUpdate extends Update<"patch", PatchOperation[]> { strategy: "patch"; data: PatchOperation[]; } /** JSON Patch operation */ interface PatchOperation { op: "add" | "remove" | "replace" | "move" | "copy" | "test"; path: string; value?: unknown; from?: string; } /** Update strategy interface */ interface UpdateStrategy { name: StrategyName; /** Encode difference between prev and next */ encode(prev: T, next: T): Update; /** Decode update and apply to current value */ decode(current: T, update: Update): T; /** Estimate the size of the update in bytes */ estimateSize(update: Update): number; } /** * Array diff operation types */ type ArrayDiffOperation = { op: "push"; item: unknown; } | { op: "unshift"; item: unknown; } | { op: "insert"; index: number; item: unknown; } | { op: "remove"; index: number; } | { op: "update"; index: number; item: unknown; } | { op: "move"; from: number; to: number; } | { op: "replace"; items: unknown[]; }; /** * Array update with diff operations */ interface ArrayUpdate extends Update<"array", ArrayDiffOperation[]> { strategy: "array"; data: ArrayDiffOperation[]; } /** * Compute optimal diff operations between two arrays. * Assumes arrays contain objects with 'id' fields for identity tracking. * * @param prev - Previous array state * @param next - New array state * @returns Array of operations to transform prev into next, or null if full replace is more efficient */ declare function computeArrayDiff(prev: T[], next: T[]): ArrayDiffOperation[] | null; /** * Apply array diff operations to transform an array */ declare function applyArrayDiff(current: T[], operations: ArrayDiffOperation[]): T[]; /** * Create an array update with optimal diff */ declare function createArrayUpdate(prev: T[], next: T[]): ArrayUpdate | ValueUpdate; /** * Value strategy - sends full replacement * * Best for: * - Short strings (< 100 chars) * - Numbers, booleans, enums * - Small objects that change completely */ declare const valueStrategy: UpdateStrategy; /** * Delta strategy - character-level diff for strings * * Best for: * - Long strings with small changes * - Streaming text (LLM responses) * - ~57% bandwidth savings typical */ declare const deltaStrategy: UpdateStrategy; /** * Patch strategy - JSON Patch RFC 6902 * * Best for: * - Objects with nested changes * - Arrays with modifications * - ~99% bandwidth savings for large objects */ declare const patchStrategy: UpdateStrategy; /** * Select optimal update strategy based on data type and change */ declare function selectStrategy(prev: unknown, next: unknown): UpdateStrategy; /** * Create an optimized update from prev to next */ declare function createUpdate(prev: unknown, next: unknown): Update; /** * Apply an update to a current value */ declare function applyUpdate(current: T, update: Update): T; /** * Server message - exactly ONE kind per emission. * No ambiguity, no optional field mixing. * * @example * ```typescript * // Initial data * { $: "snapshot", data: { id: "1", name: "Alice" } } * * // Incremental update * { $: "ops", ops: [{ o: "set", p: "name", v: "Bob" }] } * * // Error * { $: "error", error: "Not found", code: "NOT_FOUND" } * ``` */ type Message = { $: "snapshot"; data: T; } | { $: "ops"; ops: Op[]; } | { $: "error"; error: string; code?: string; }; /** * Operation type - all incremental updates. * Uses short keys for minimal wire size. * * Key mapping: * - o: operation type * - p: path (dot notation) * - v: value * - d: delta operations * - i: index * - dc: delete count * - id: item id (for by-id operations) */ type Op = { o: "set"; p: string; v: unknown; } | { o: "del"; p: string; } | { o: "merge"; p: string; v: Record; } | { o: "delta"; p: string; d: DeltaOperation[]; } | { o: "patch"; p: string; d: PatchOperation[]; } | { o: "push"; p: string; v: unknown[]; } | { o: "unshift"; p: string; v: unknown[]; } | { o: "splice"; p: string; i: number; dc: number; v?: unknown[]; } | { o: "arrSet"; p: string; i: number; v: unknown; } | { o: "arrDel"; p: string; i: number; } | { o: "arrSetId"; p: string; id: string; v: unknown; } | { o: "arrDelId"; p: string; id: string; } | { o: "arrMerge"; p: string; i: number; v: Record; } | { o: "arrMergeId"; p: string; id: string; v: Record; }; /** * Check if message is a snapshot */ declare function isSnapshot(msg: Message): msg is { $: "snapshot"; data: T; }; /** * Check if message is an ops message */ declare function isOps(msg: Message): msg is { $: "ops"; ops: Op[]; }; /** * Check if message is an error */ declare function isError(msg: Message): msg is { $: "error"; error: string; code?: string; }; /** * Extract data type from Message */ type MessageData = M extends Message ? T : never; /** * Observable that emits Messages */ interface MessageObservable { subscribe(observer: MessageObserver): { unsubscribe(): void; }; } /** * Observer for Message streams */ interface MessageObserver { next?: (message: Message) => void; error?: (error: Error) => void; complete?: () => void; } /** * Convert an EmitCommand to Op[] for wire protocol. * * @param command - EmitCommand to convert * @param path - Optional base path (for nested field subscriptions) * @returns Array of operations * * @example * ```typescript * // Full data emission * toOps({ type: "full", data: { name: "Alice" }, replace: false }) * // → [{ o: "merge", p: "", v: { name: "Alice" } }] * * // Field update * toOps({ type: "field", field: "name", update: { strategy: "value", data: "Bob" } }) * // → [{ o: "set", p: "name", v: "Bob" }] * * // Array operation * toOps({ type: "array", operation: { op: "push", item: { id: "1" } } }, "users") * // → [{ o: "push", p: "users", v: [{ id: "1" }] }] * ``` */ declare function toOps(command: EmitCommand, path?: string): Op[]; /** Extract string keys from object type */ type StringKeyOf = Extract; /** Get array element type */ type ArrayElement = T extends readonly (infer E)[] ? E : never; /** * Emit API for object outputs (single entity or multi-entity). * Provides field-level operations. * * @typeParam T - Object type * * @example * ```typescript * // Single entity: .returns(User) * emit({ name: "Alice", email: "alice@example.com" }) * emit.merge({ name: "Bob" }) * emit.set("name", "Charlie") * emit.delta("bio", [{ position: 0, insert: "Hello " }]) * * // Multi-entity: .returns({ user: User, posts: [Post] }) * emit.set("user", newUser) * emit.set("posts", newPosts) * ``` */ interface EmitObject { /** * Emit full data (merge mode) */ (data: T): void; /** * Merge partial data into current state */ merge(partial: Partial): void; /** * Replace entire state (clears fields not in data) */ replace(data: T): void; /** * Set a single field value */ set>(field: K, value: T[K]): void; /** * Apply delta operations to a string field. * Only valid for string fields. * * @example * ```typescript * emit.delta("content", [{ position: Infinity, insert: "appended text" }]) * ``` */ delta>(field: K, operations: DeltaOperation[]): void; /** * Apply JSON Patch (RFC 6902) operations to an object field. * Only valid for object fields. * * @example * ```typescript * emit.patch("metadata", [{ op: "add", path: "/views", value: 100 }]) * ``` */ patch>(field: K, operations: PatchOperation[]): void; /** * Batch multiple field updates */ batch(updates: FieldUpdate[]): void; } /** * Emit API for array outputs. * Provides array-level operations. * * @typeParam T - Array type (e.g., User[]) * * @example * ```typescript * // .returns([User]) * emit([user1, user2]) // Replace entire array * emit.push(newUser) // Append item * emit.unshift(newUser) // Prepend item * emit.insert(1, newUser) // Insert at index * emit.remove(0) // Remove by index * emit.removeById("user-123") // Remove by id * emit.update(1, updatedUser) // Update item at index * emit.updateById("user-123", u) // Update by id * ``` */ interface EmitArray { /** * Replace entire array */ (items: T): void; /** * Replace entire array (alias) */ replace(items: T): void; /** * Append item to end of array */ push(item: ArrayElement): void; /** * Prepend item to start of array */ unshift(item: ArrayElement): void; /** * Insert item at specific index */ insert(index: number, item: ArrayElement): void; /** * Remove item at index */ remove(index: number): void; /** * Remove item by id field * Assumes items have an 'id' field */ removeById(id: string): void; /** * Update item at index */ update(index: number, item: ArrayElement): void; /** * Update item by id field * Assumes items have an 'id' field */ updateById(id: string, item: ArrayElement): void; /** * Merge partial data into item at index */ merge(index: number, partial: Partial>): void; /** * Merge partial data into item by id */ mergeById(id: string, partial: Partial>): void; } /** * Emit API for scalar outputs (string, number, boolean, etc). * Provides value replacement and delta operations for strings. * * @typeParam T - Scalar type * * @example * ```typescript * // String field with delta support * .subscribe({ * content: ({ source }) => ({ emit }) => { * emit("full replacement") * emit.delta([{ position: Infinity, insert: " appended" }]) * } * }) * * // Number field * .subscribe({ * count: ({ source }) => ({ emit }) => { * emit(42) * } * }) * ``` */ interface EmitScalar { /** * Replace entire value */ (value: T): void; /** * Apply delta operations (only for string values). * @example * emit.delta([{ position: 0, insert: "Hello " }]) * emit.delta([{ position: Infinity, insert: " World" }]) */ delta: T extends string ? (operations: DeltaOperation[]) => void : never; } /** * Type-safe Emit API that varies based on output type. * * - If T is an array → EmitArray * - If T is an object → EmitObject * - If T is a scalar → EmitScalar */ type Emit = T extends readonly unknown[] ? EmitArray : T extends object ? EmitObject : EmitScalar; /** * Field update specification for batch operations */ interface FieldUpdate { field: StringKeyOf; strategy: "value" | "delta" | "patch"; data: unknown; } /** * Internal field update representation */ interface InternalFieldUpdate { field: string; update: Update; } /** * Array operation types */ type ArrayOperation = { op: "push"; item: unknown; } | { op: "unshift"; item: unknown; } | { op: "insert"; index: number; item: unknown; } | { op: "remove"; index: number; } | { op: "removeById"; id: string; } | { op: "update"; index: number; item: unknown; } | { op: "updateById"; id: string; item: unknown; } | { op: "merge"; index: number; partial: unknown; } | { op: "mergeById"; id: string; partial: unknown; }; /** * Emit command - internal representation of emit calls */ type EmitCommand = { type: "full"; data: unknown; replace: boolean; } | { type: "field"; field: string; update: Update; } | { type: "batch"; updates: InternalFieldUpdate[]; } | { type: "array"; operation: ArrayOperation; field?: string; }; /** * Create an EmitObject instance for object outputs. */ declare function createEmitObject(handler: (command: EmitCommand) => void): EmitObject; /** * Create an EmitArray instance for array outputs. */ declare function createEmitArray(handler: (command: EmitCommand) => void): EmitArray; /** * Create an EmitScalar instance for scalar outputs (string, number, etc). */ declare function createEmitScalar(handler: (command: EmitCommand) => void): EmitScalar; /** * Create appropriate Emit instance based on output type. * * @param handler - Function to handle emit commands * @param outputType - "array" | "object" | "scalar" or boolean (true = array, false = object) for backwards compatibility * @returns Emit instance (EmitArray, EmitObject, or EmitScalar) */ declare function createEmit(handler: (command: EmitCommand) => void, outputType?: "array" | "object" | "scalar" | boolean): Emit; /** * Any entity-like definition (EntityDef or ModelDef). * Uses structural typing to accept both types without exactOptionalPropertyTypes issues. */ type AnyEntityLike< Name extends string = string, Fields extends Record = Record > = { readonly _name: Name | undefined; readonly fields: Fields; }; /** Base context type for field resolvers - extend this for your app */ type FieldResolverContext = Record; /** Cleanup function registration */ type OnCleanup = (fn: () => void) => () => void; /** Emit function for field updates (generic version) */ type FieldEmit = (value: T) => void; /** * Subscription callbacks passed to publisher function. * Keeps user context clean - emit/onCleanup are NOT on ctx. * * emit has full Emit API with .delta(), .patch(), .push(), etc. */ interface SubscriptionCallbacks { /** Emit updates - full API with .delta(), .patch(), .push(), etc. */ emit: Emit; /** Register cleanup function called when subscription ends */ onCleanup: OnCleanup; } /** * Publisher function returned from subscribe. * Receives subscription callbacks separately from user context. * * @example * ```typescript * .subscribe(({ parent, ctx }) => ({ emit, onCleanup }) => { * ctx.db.onChange(parent.id, (v) => emit(v)); * onCleanup(() => ctx.db.unsubscribe(parent.id)); * }) * ``` */ type Publisher = (callbacks: SubscriptionCallbacks) => void; /** * Context for field resolvers that return a value (no emit/onCleanup). * Used with .resolve() - returns value once. */ type FieldQueryContext = TContext; /** * Field resolver params for .resolve() - returns value, no emit/onCleanup. */ type FieldResolveParams< TParent, TArgs, TContext > = { /** * Source object being resolved. * @example * ```typescript * .resolve(({ source, ctx }) => ctx.db.posts.filter(p => p.authorId === source.id)) * ``` */ source: TParent; /** Field arguments (if any) */ args: TArgs; ctx: FieldQueryContext; }; /** * Field resolver params for .subscribe() - pure context, no emit/onCleanup. * Returns a Publisher that receives subscription callbacks. */ type FieldSubscribeParams< TParent, TArgs, TContext > = { /** Source object being resolved */ source: TParent; /** Field arguments (if any) */ args: TArgs; ctx: TContext; }; /** * Field resolver function for .resolve() - returns value. */ type FieldResolveFn< TParent, TArgs, TContext, TResult > = (params: FieldResolveParams) => TResult | Promise; /** * Live field subscriber function - returns Publisher. * Used with .resolve().subscribe() pattern. * * @example * ```typescript * .resolve().subscribe(({ parent, ctx }) => ({ emit, onCleanup }) => { * ctx.db.onChange(parent.id, (v) => emit(v)); * onCleanup(() => ctx.db.unsubscribe(parent.id)); * }) * ``` */ type FieldLiveSubscribeFn< TParent, TArgs, TContext, TResult > = (params: FieldSubscribeParams) => Publisher; /** Field resolver function without args for .resolve() */ type FieldResolveFnNoArgs< TParent, TContext, TResult > = (params: { source: TParent; ctx: FieldQueryContext; }) => TResult | Promise; /** Live field subscriber function without args - returns Publisher */ type FieldLiveSubscribeFnNoArgs< TParent, TContext, TResult > = (params: { source: TParent; ctx: TContext; }) => Publisher; /** * Plain function resolver - simple arrow function without builder API. * Used for inline field resolvers in resolver definitions. * * @example * ```typescript * resolver(User, (t) => ({ * id: t.expose("id"), * displayName: ({ source }) => `${source.firstName}`, // plain function * })) * ``` */ type PlainFieldResolver< TSource, TContext, TResult > = (params: { source: TSource; ctx: FieldQueryContext; }) => TResult | Promise; /** Exposed field - directly uses parent value */ interface ExposedField { readonly _kind: "exposed"; readonly _fieldName: string; readonly _type: T; } /** Resolved field - uses resolver function (returns value) */ interface ResolvedField< T = unknown, TArgs = Record, TContext = FieldResolverContext > { readonly _kind: "resolved"; readonly _mode: "resolve"; readonly _returnType: T; readonly _argsSchema: z.ZodType | null; readonly _resolver: (params: { parent: unknown; args: TArgs; ctx: FieldQueryContext; }) => T | Promise; } /** * Live field - has both resolver (initial) and subscriber (updates). * Created by chaining .resolve().subscribe() * * @example * ```typescript * status: f.string() * .resolve(({ parent, ctx }) => ctx.db.getStatus(parent.id)) * .subscribe(({ parent, ctx }) => ({ emit, onCleanup }) => { * ctx.statusService.watch(parent.id, (s) => emit(s)); * onCleanup(() => ctx.statusService.unwatch(parent.id)); * }) * ``` */ interface LiveField< T = unknown, TArgs = Record, TContext = FieldResolverContext > { readonly _kind: "resolved"; readonly _mode: "live"; readonly _returnType: T; readonly _argsSchema: z.ZodType | null; /** Resolver for initial value (Phase 1 - batchable) */ readonly _resolver: (params: { parent: unknown; args: TArgs; ctx: FieldQueryContext; }) => T | Promise; /** Subscriber for live updates (Phase 2 - returns Publisher) */ readonly _subscriber: (params: { parent: unknown; args: TArgs; ctx: TContext; }) => Publisher; } /** Field definition (exposed, resolved, or live) */ type FieldDef2< T = unknown, TArgs = unknown, TContext = FieldResolverContext > = ExposedField | ResolvedField | LiveField; /** * Any field definition - can be plain function OR builder result. * Used as return type for resolver() builder callback. * * Plain functions are automatically wrapped as ResolvedField internally. * * @example * ```typescript * resolver(User, (t) => ({ * id: t.expose("id"), // ExposedField * displayName: ({ source }) => `${source.firstName}`, // PlainFieldResolver * posts: t.args(z.object({...})).resolve(...), // ResolvedField * })) * ``` */ type AnyFieldDef = PlainFieldResolver | ExposedField | ResolvedField | LiveField; /** * Resolved field with chainable .subscribe() for live updates. * Returned by .resolve() to allow optional .subscribe() chaining. * * @example * ```typescript * // Just resolve (one-shot, batchable) * posts: f.many(Post).resolve(({ parent, ctx }) => ctx.db.getPosts(parent.id)) * * // Resolve + subscribe (initial + live updates) * status: f.string() * .resolve(({ parent, ctx }) => ctx.db.getStatus(parent.id)) * .subscribe(({ parent, ctx, emit }) => { * ctx.statusService.watch(parent.id, (s) => emit(s)); * }) * ``` */ interface ResolvedFieldChainable< T = unknown, TArgs = Record, TParent = unknown, TContext = FieldResolverContext > extends ResolvedField { /** * Add subscription for live updates after initial resolution. * The resolve function handles initial data (batchable), * the subscribe function sets up watchers for updates (Publisher pattern). */ subscribe(fn: TArgs extends Record ? FieldLiveSubscribeFnNoArgs : FieldLiveSubscribeFn): LiveField; } /** Check if a field is optional */ type IsOptionalField2 = T extends { _optional: true; } ? true : false; /** Extract required field keys */ type RequiredFieldKeys = { [K in keyof E] : IsOptionalField2 extends true ? never : K }[keyof E]; /** Extract optional field keys */ type OptionalFieldKeys = { [K in keyof E] : IsOptionalField2 extends true ? K : never }[keyof E]; /** Infer parent type from entity fields (with proper optional handling) */ type InferParent = { [K in RequiredFieldKeys] : E[K] extends FieldType ? InferScalar : never } & { [K in OptionalFieldKeys]? : E[K] extends FieldType ? InferScalar : never }; /** * Infer parent type from any fields (permissive version). * Used for field builders where exact field types aren't critical. */ type InferParentAny = E extends EntityDefinition ? InferParent : any; /** Field builder with args already defined */ interface FieldBuilderWithArgs< TParent, TArgs, TContext > { /** * Define how to resolve this field with args (returns value). * Can optionally chain .subscribe() for live updates. */ resolve(fn: FieldResolveFn): ResolvedFieldChainable; } /** * Simplified field builder for an entity. * Types are now defined in the Model, so builder only handles resolution. * * Use plain functions for simple computed fields: * ```typescript * resolver(User, (t) => ({ * id: t.expose('id'), // expose source field * displayName: ({ source }) => `${source.firstName}`, // plain function * posts: t.args(z.object({ limit: z.number() })) // with args * .resolve(({ source, args, ctx }) => ...) * })) * ``` */ interface FieldBuilder< TEntity extends AnyEntityLike, TContext = FieldResolverContext > { /** * Expose a field from the parent entity directly. * The field must exist in the entity definition. * * @example * ```typescript * resolver(User, (t) => ({ * id: t.expose('id'), // expose parent.id * name: t.expose('name'), // expose parent.name * })); * ``` */ expose(fieldName: K): ExposedField; /** * Add field arguments (GraphQL-style). * Chain with .resolve() for computed fields with arguments. * * @example * ```typescript * resolver(User, (t) => ({ * posts: t * .args(z.object({ limit: z.number().default(10) })) * .resolve(({ source, args, ctx }) => ctx.db.posts.filter(...).slice(0, args.limit)), * })); * ``` */ args(schema: z.ZodObject): FieldBuilderWithArgs, z.infer>, TContext>; /** * Define a computed field resolver (no args). * For simple computed fields, prefer using a plain function instead: * `displayName: ({ source }) => ...` * * @example * ```typescript * resolver(User, (t) => ({ * displayName: t.resolve(({ source }) => `${source.firstName} ${source.lastName}`), * })); * ``` */ resolve(fn: FieldResolveFnNoArgs, TContext, TResult>): ResolvedFieldChainable, InferParentAny, TContext>; } /** Resolver definition for an entity */ interface ResolverDef< TEntity extends AnyEntityLike = AnyEntityLike, TFields extends Record> = Record>, TContext = FieldResolverContext > { /** The entity this resolver is for */ readonly entity: TEntity; /** Field definitions */ readonly fields: TFields; /** Get field names */ getFieldNames(): (keyof TFields)[]; /** Check if field exists */ hasField(name: string): boolean; /** Check if field is exposed (vs resolved) */ isExposed(name: string): boolean; /** Check if field is a subscription (live mode with updates) */ isSubscription(name: string): boolean; /** Check if field is a live field (has both resolver and subscriber) */ isLive(name: string): boolean; /** Get the field mode: "exposed", "resolve", "live", or null if not found */ getFieldMode(name: string): "exposed" | "resolve" | "live" | null; /** Get the args schema for a field (if any) */ getArgsSchema(name: string): z.ZodType | null; /** * Resolve initial value for a field. * Works for "resolve" and "live" modes. * For "subscribe" mode, returns undefined (subscribe handles initial value). */ resolveField(name: K, parent: InferParentAny, args: Record, ctx: FieldQueryContext): Promise; /** * Set up subscription for a live field. * Returns a Publisher that receives { emit, onCleanup } callbacks. */ subscribeField(name: K, parent: InferParentAny, args: Record, ctx: TContext): Publisher | null; /** Resolve all fields for a parent with args per field */ resolveAll(parent: InferParentAny, ctx: TContext, select?: Array<{ name: string; args?: Record; }> | string[]): Promise>; } /** Infer the resolved type from a resolver definition */ type InferResolverOutput = { [K in keyof R["fields"]] : R["fields"][K] extends ExposedField ? T : R["fields"][K] extends ResolvedField ? T : R["fields"][K] extends LiveField ? T : never }; /** Infer selected fields from resolver */ type InferResolverSelected< R extends ResolverDef, Select extends (keyof R["fields"])[] | undefined > = Select extends (keyof R["fields"])[] ? Pick, Select[number]> : InferResolverOutput; /** Array of resolver definitions */ type Resolvers = ResolverDef[]; /** * Extract scalar (non-relation) fields from entity definition. * This is the "source" type available in field resolvers. * * Relations are excluded because they need to be resolved separately. */ type ScalarFieldsOnly = { [K in ScalarFields] : InferScalar }; /** * Infer source type from a ModelDef. * Source = only scalar fields (relations need field resolvers). * * @example * ```typescript * const { model } = lens(); * * const User = model('User', { * id: id(), * name: string(), * posts: list(() => Post), * }) * * type UserSource = InferModelSource * // { id: string; name: string } - posts excluded (relation) * ``` */ type InferModelSource> = M extends ModelDef ? ScalarFieldsOnly : never; /** * Infer entity type from a ModelDef. * For model chains, returns all scalar fields as the expected resolver output. */ type InferModelEntity = M extends ModelDef ? ScalarFieldsOnly : unknown; /** * Extract the TypeScript type from a field definition. * * For scalar fields: uses `_tsType` directly * For lazy relations: infers from the target model's scalar fields * For legacy relations: falls back to unknown (requires schema context) */ type InferFieldOutputType = F extends LazyManyType ? Array> : F extends LazyOneType ? InferModelEntity | null : F extends { _tsType: infer T; } ? T : unknown; /** * Extract field args type if the field has an args schema. * Falls back to Record if no args defined. */ type InferFieldArgs = F extends { _argsSchema: infer S; } ? S extends { _output: infer A; } ? A : Record : Record; /** * Field resolver function type. * Receives source (parent), args, and context. * Return type is checked against the field's expected type. * * @example * ```typescript * const userResolver = resolver(User, (t) => ({ * id: t.expose('id'), * posts: t.args(z.object({ limit: z.number() })) * .resolve(({ source, args, ctx }) => * ctx.db.posts.filter(p => p.authorId === source.id).slice(0, args.limit) * ), * })); * ``` */ type ModelFieldResolver< TSource, TArgs, TContext, TResult > = (params: { /** Source object being resolved */ source: TSource; /** Field arguments (if any) */ args: TArgs; /** Application context */ ctx: FieldQueryContext; }) => TResult | Promise; /** * Field subscriber function type (returns Publisher). * Used for real-time field updates. * * @example * ```typescript * const userResolver = resolver(User, (t) => ({ * id: t.expose('id'), * name: t.resolve(({ source, ctx }) => ctx.db.getUser(source.id).name) * .subscribe(({ source, ctx }) => ({ emit, onCleanup }) => { * const unsub = ctx.events.on(`user:${source.id}:name`, emit); * onCleanup(unsub); * }), * })); * ``` */ type ModelFieldSubscriber< TSource, TArgs, TContext, TResult > = (params: { /** Source object being resolved */ source: TSource; /** Field arguments (if any) */ args: TArgs; /** Application context (pure, no emit/onCleanup) */ ctx: TContext; }) => Publisher; /** * Map of field resolvers for a model. * Keys are field names, values are resolver functions. * Return types are checked against the field's expected type. */ type FieldResolverMap< Fields extends EntityDefinition, TContext, TSource = ScalarFieldsOnly > = { [K in keyof Fields]? : ModelFieldResolver, TContext, InferFieldOutputType> }; /** * Map of field subscribers for a model. * Keys are field names, values are subscriber functions. * Emit types are checked against the field's expected type. */ type FieldSubscriberMap< Fields extends EntityDefinition, TContext, TSource = ScalarFieldsOnly > = { [K in keyof Fields]? : ModelFieldSubscriber, TContext, InferFieldOutputType> }; /** * @sylphx/lens-core - Shared Type Utilities * * Common utility types used across the codebase. */ /** * Convert union type to intersection type. * * @example * ```typescript * type A = { a: 1 } | { b: 2 }; * type B = UnionToIntersection; // { a: 1 } & { b: 2 } * ``` */ type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; /** * Flatten intersection types for better IDE display. * * @example * ```typescript * type Messy = { a: 1 } & { b: 2 } & { c: 3 }; * type Clean = Prettify; // { a: 1; b: 2; c: 3 } * ``` */ type Prettify = { [K in keyof T] : T[K] } & {}; import { StepBuilder as StepBuilder2 } from "@sylphx/reify"; import { Pipeline, StepBuilder } from "@sylphx/reify"; import { isPipeline } from "@sylphx/reify"; /** * Zod-like schema interface for input validation. * Compatible with Zod, Valibot, Arktype, and other schema libraries. */ interface ZodLikeSchema { parse(data: unknown): T; safeParse?(data: unknown): { success: true; data: T; } | { success: false; error: unknown; }; _output?: T; } /** Model definition type for return specifications (uses any for fields to avoid variance issues) */ type AnyModelDef = ModelDef; /** * Return type specification * - Model: Model definition * - nullable(Model): Nullable model * - list(Model): Array of models * - nullable(list(Model)): Nullable array * * Uses `any` for inner types to avoid variance issues with specific ModelDef types. */ type ReturnSpec = AnyModelDef | NullableDef | ListDef | NullableDef>; /** * Infer entity/model type from definition fields. * Uses permissive `any` constraint to avoid variance issues with ProcessedFields. */ type InferModelFromFieldsAny = Prettify<{ [K in keyof F as F[K] extends { _optional: true; } ? never : K] : F[K] extends FieldType ? T : unknown } & { [K in keyof F as F[K] extends { _optional: true; } ? K : never]? : F[K] extends FieldType ? T : unknown }>; /** * Structural check for ModelDef-like objects. * Avoids issues with nominal typing and EntityMarker extension. */ type IsModelDefLike = T extends { readonly fields: infer F; _name: string; } ? F : never; /** * Infer TypeScript type from return spec. * Uses structural matching to avoid variance issues with ProcessedFields. */ type InferReturnType = R extends NullableDef ? Inner extends ListDef ? IsModelDefLike extends infer F ? F extends never ? never : InferModelFromFieldsAny[] | null : never : IsModelDefLike extends infer F ? F extends never ? never : InferModelFromFieldsAny | null : never : R extends ListDef ? IsModelDefLike extends infer F ? F extends never ? never : InferModelFromFieldsAny[] : never : IsModelDefLike extends infer F ? F extends never ? never : InferModelFromFieldsAny : never; /** * Context for query resolvers (return value). * No emit or onCleanup - queries are one-shot. */ type QueryContext = TContext; /** * Resolver context for queries (return value). * ctx has NO emit/onCleanup. */ interface QueryResolverContext< TInput = unknown, TContext = unknown > { /** * Parsed and validated arguments. * @example * ```typescript * .resolve(({ args, ctx }) => ctx.db.user.find(args.id)) * ``` */ args: TInput; /** User context (no Lens extensions for queries) */ ctx: QueryContext; } /** Query resolver - returns value, no emit/onCleanup */ type QueryResolverFn< TInput, TOutput, TContext = unknown > = (ctx: QueryResolverContext) => TOutput | Promise; /** * Generic resolver function for mutations. * Receives typed arguments and context, returns output. */ type ResolverFn2< TInput, TOutput, TContext = unknown > = (ctx: { args: TInput; ctx: TContext; }) => TOutput | Promise; /** Sugar syntax for common optimistic update patterns */ type OptimisticSugar = "merge" | "create" | "delete" | { merge: Record; }; /** * OptimisticDSL - Defines optimistic update behavior * * Can be: * - Sugar syntax ("merge", "create", "delete", { merge: {...} }) for common patterns * - Reify Pipeline for complex multi-entity operations */ type OptimisticDSL = OptimisticSugar | Pipeline; /** * Check if value is an OptimisticDSL (sugar or Pipeline) */ declare function isOptimisticDSL(value: unknown): value is OptimisticDSL; /** Context passed to optimistic callback for type inference */ interface OptimisticContext { /** Typed arguments - inferred from .args() schema */ args: TInput; } /** Optimistic callback that receives typed arguments and returns step builders */ type OptimisticCallback = (ctx: OptimisticContext) => StepBuilder[]; /** Query mode - determines how the query is executed */ type QueryMode = "query" | "subscribe" | "live"; /** Base query definition */ interface QueryDefBase< TInput = void, TOutput = unknown, _TContext = unknown > { _type: "query"; /** Query name (optional - derived from export key if not provided) */ _name?: string | undefined; _input?: ZodLikeSchema | undefined; _output?: ReturnSpec | undefined; /** Branded phantom types for inference */ _brand: { input: TInput; output: TOutput; }; } /** Query definition - one-shot query (returns value) */ interface QueryDef2< TInput = void, TOutput = unknown, TContext = unknown > extends QueryDefBase { _mode?: "query"; /** Method syntax for bivariance - allows flexible context types */ _resolve?(ctx: import("./types.js").QueryResolverContext): TOutput | Promise | AsyncGenerator | void | Promise; } /** Live subscription definition - uses Publisher pattern */ interface LiveQueryDef< TInput = void, TOutput = unknown, TContext = unknown > extends QueryDefBase { _mode: "live"; /** One-shot resolver for initial value */ _resolve?(ctx: import("./types.js").QueryResolverContext): TOutput | Promise; /** Subscriber for live updates (returns Publisher) - method syntax for bivariance */ _subscriber?(ctx: QueryResolverContext): Publisher; } /** Any query definition */ type AnyQueryDef< TInput = void, TOutput = unknown, TContext = unknown > = QueryDef2 | LiveQueryDef; /** Publisher-based subscription resolver - returns Publisher */ type PublisherResolverFn< TInput, TOutput, TContext = unknown > = (ctx: QueryResolverContext) => Publisher; /** * Chainable query definition - returned by .resolve(), can chain .subscribe() */ interface QueryDefChainable< TInput = void, TOutput = unknown, TContext = unknown > extends QueryDef2 { /** * Add live subscription to query (Publisher pattern). * Creates a LiveQueryDef that fetches initial value then streams updates. * * @example * ```typescript * query() * .args(z.object({ id: z.string() })) * .resolve(({ args, ctx }) => ctx.db.user.find(args.id)) * .subscribe(({ args, ctx }) => ({ emit, onCleanup }) => { * const unsub = pubsub.on(`user:${args.id}`, emit); * onCleanup(unsub); * }); * ``` */ subscribe(fn: PublisherResolverFn): LiveQueryDef; } /** Query builder - fluent interface */ interface QueryBuilder2< TInput = void, TOutput = unknown, TContext = unknown > { /** Define args validation schema (optional for queries) */ args(schema: ZodLikeSchema): QueryBuilder2; /** Define return type (optional - for entity outputs) */ returns(spec: R): QueryBuilder2, TContext>; /** * Define query resolver (returns value). * ctx has NO emit/onCleanup - queries are one-shot. * Can chain .subscribe() for live updates (Publisher pattern). * * @example * ```typescript * // One-shot query * query() * .args(z.object({ id: z.string() })) * .resolve(({ args, ctx }) => db.user.find(args.id)); * * // Live query (resolve + subscribe) * query() * .args(z.object({ id: z.string() })) * .resolve(({ args, ctx }) => db.user.find(args.id)) * .subscribe(({ args, ctx }) => ({ emit, onCleanup }) => { * const unsub = pubsub.on(`user:${args.id}`, emit); * onCleanup(unsub); * }); * ``` */ resolve(fn: QueryResolverFn): QueryDefChainable; } /** * Create a query builder * * @example * ```typescript * // Basic usage * const getUser = query() * .args(z.object({ id: z.string() })) * .returns(User) * .resolve(({ args }) => db.user.findUnique({ where: { id: args.id } })); * * // With typed context * const getUser = query() * .args(z.object({ id: z.string() })) * .resolve(({ args, ctx }) => ctx.db.user.find(args.id)); * ``` */ declare function query(): QueryBuilder2; declare function query(name: string): QueryBuilder2; /** Check if value is a query definition (any mode) */ declare function isQueryDef(value: unknown): value is AnyQueryDef; /** Check if value is a live query definition (Publisher pattern) */ declare function isLiveQueryDef(value: unknown): value is LiveQueryDef; /** Subscription definition - event stream only (no initial data) */ interface SubscriptionDef< TInput = void, TOutput = unknown, TContext = unknown > { _type: "subscription"; /** Subscription name (optional - derived from key if not provided) */ _name?: string | undefined; _input?: ZodLikeSchema | undefined; _output?: ReturnSpec | undefined; /** Branded phantom types for inference */ _brand: { input: TInput; output: TOutput; }; /** Method syntax for bivariance - allows flexible context types */ _subscriber?(ctx: { args: TInput; ctx: TContext; }): Publisher; } /** Subscription builder - fluent interface */ interface SubscriptionBuilder< TInput = void, TOutput = unknown, TContext = unknown > { /** Define args validation schema (optional for subscriptions) */ args(schema: ZodLikeSchema): SubscriptionBuilder; /** Define return type (optional - for entity outputs) */ returns(spec: R): SubscriptionBuilder, TContext>; /** * Define subscription publisher (returns Publisher). * The Publisher receives { emit, onCleanup } callbacks. * Returns only event stream - no initial data fetch. * * @example * ```typescript * // Event-only subscription * subscription() * .args(z.object({ authorId: z.string().optional() })) * .returns(Post) * .subscribe(({ args, ctx }) => ({ emit, onCleanup }) => { * const unsub = ctx.events.on("post:created", (post) => { * if (!args.authorId || post.authorId === args.authorId) { * emit(post); * } * }); * onCleanup(unsub); * }); * ``` */ subscribe(fn: (ctx: { args: TInput; ctx: TContext; }) => Publisher): SubscriptionDef; } /** * Create a subscription builder * * @example * ```typescript * // Basic usage * const onPostCreated = subscription() * .args(z.object({ authorId: z.string().optional() })) * .returns(Post) * .subscribe(({ args, ctx }) => ({ emit, onCleanup }) => { * const unsub = ctx.events.on("post:created", (post) => { * if (!args.authorId || post.authorId === args.authorId) { * emit(post); * } * }); * onCleanup(unsub); * }); * * // With typed context * const onUserStatusChange = subscription() * .args(z.object({ userId: z.string() })) * .returns(UserStatus) * .subscribe(({ args, ctx }) => ({ emit, onCleanup }) => { * const unsub = ctx.statusService.watch(args.userId, emit); * onCleanup(unsub); * }); * ``` */ declare function subscription(): SubscriptionBuilder; declare function subscription(name: string): SubscriptionBuilder; /** Check if value is a subscription definition */ declare function isSubscriptionDef(value: unknown): value is SubscriptionDef; /** Any procedure (query, mutation, or subscription) */ type AnyProcedure = AnyQueryDef | MutationDef | SubscriptionDef; /** Router routes - can contain procedures or nested routers */ type RouterRoutes = { [key: string]: AnyProcedure | RouterDef; }; /** Router definition with context type */ interface RouterDef< TRoutes extends RouterRoutes = RouterRoutes, TContext = unknown > { _type: "router"; _routes: TRoutes; /** Phantom type for context inference */ _context?: TContext; } /** * Extract context from a procedure (non-recursive, single level) */ type ExtractProcedureContext = T extends QueryDef2 ? C : T extends MutationDef ? C : T extends SubscriptionDef ? C : unknown; /** * Extract context from router's explicit context or from its routes */ type ExtractRouterContext = T extends RouterDef ? unknown extends C ? R extends Record ? ExtractProcedureContext : unknown : C : unknown; /** * Extract contexts from a routes object (one level deep) * Handles both direct procedures and nested routers */ type ExtractRoutesContext = T extends Record ? V extends RouterDef ? ExtractRouterContext : ExtractProcedureContext : unknown; /** * Infer merged context type from router or routes * * Each procedure can declare its own context requirements. * The final context is the intersection of all requirements. * * @example * ```typescript * // Each query declares what it needs * const userGet = query<{ db: DB; user: User }>().resolve(...) * const postList = query<{ db: DB; cache: Cache }>().resolve(...) * * const appRouter = router({ user: { get: userGet }, post: { list: postList } }) * * // InferRouterContext = { db: DB; user: User; cache: Cache } * ``` */ type InferRouterContext = UnionToIntersection ? unknown extends C ? ExtractRoutesContext : C : T extends Record ? ExtractRoutesContext : unknown>; /** Check if value is a router definition */ declare function isRouterDef(value: unknown): value is RouterDef; /** * Create a router for namespacing operations * * The router automatically infers the context type from its routes. * When used with createApp, the context function must return * a matching type. * * @example * ```typescript * import { router, query, mutation } from '@sylphx/lens-core'; * import { z } from 'zod'; * * // Using typed lens instance * const lens = initLens.context().create() * * const appRouter = router({ * user: { * get: lens.query() * .args(z.object({ id: z.string() })) * .resolve(({ args, ctx }) => ctx.db.user.find(args.id)), * create: lens.mutation() * .args(z.object({ name: z.string() })) * .resolve(({ args, ctx }) => ctx.db.user.create(args)), * }, * }); * // appRouter is RouterDef<..., MyContext> * * // createServer will enforce context type * const server = createApp({ * router: appRouter, * context: () => ({ * db: prisma, // Must match MyContext! * }), * }) * ``` */ declare function router(routes: TRoutes): RouterDef>; /** Flatten router to dot-notation paths for server processing */ declare function flattenRouter(routerDef: RouterDef, prefix?: string): Map; /** * Query result type (thenable with reactive features) * Matches the client's QueryResult interface */ interface QueryResultType { /** Current value (for peeking without subscribing) */ readonly value: T | null; /** Subscribe to updates */ subscribe(callback?: (data: T) => void): () => void; /** Promise interface - allows await */ then< TResult1 = T, TResult2 = never >(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike) | null): Promise; } /** * Mutation result type * Matches the client's MutationResult interface */ interface MutationResultType { data: T; rollback?: () => void; } /** * Subscription result type (async iterable of events) * Matches the client's Subscription interface */ interface SubscriptionResultType { /** Subscribe to events */ subscribe(callback: (data: T) => void): () => void; /** Async iterator interface */ [Symbol.asyncIterator](): AsyncIterator; } /** Infer the client type from a router definition */ type InferRouterClient = TRouter extends RouterDef ? { [K in keyof TRoutes] : TRoutes[K] extends RouterDef ? InferRouterClient> : TRoutes[K] extends { _type: "query"; _brand: { input: infer TInput; output: infer TOutput; }; } ? TInput extends void ? () => QueryResultType : (input: TInput) => QueryResultType : TRoutes[K] extends { _type: "mutation"; _brand: { input: infer TInput; output: infer TOutput; }; } ? (input: TInput) => Promise> : TRoutes[K] extends { _type: "subscription"; _brand: { input: infer TInput; output: infer TOutput; }; } ? TInput extends void ? () => SubscriptionResultType : (input: TInput) => SubscriptionResultType : never } : never; /** * Operations factory result - typed query, mutation, and subscription builders */ interface Operations { /** Create a query with pre-typed context */ query: { (): QueryBuilder2; (name: string): QueryBuilder2; }; /** Create a mutation with pre-typed context */ mutation: { (): MutationBuilder2; (name: string): MutationBuilder2; }; /** Create a subscription with pre-typed context */ subscription: { (): SubscriptionBuilder; (name: string): SubscriptionBuilder; }; } /** * Create typed query, mutation, and subscription builders with shared context. * * @example * ```typescript * type AppContext = { db: DB; user: User }; * const { query, mutation, subscription } = operations(); * * const getUser = query() * .args(z.object({ id: z.string() })) * .resolve(({ args, ctx }) => ctx.db.user.find(args.id)); * * const onUserCreated = subscription() * .returns(User) * .subscribe(({ ctx }) => ({ emit, onCleanup }) => { * const unsub = ctx.events.on("user:created", emit); * onCleanup(unsub); * }); * ``` */ declare function operations(): Operations; /** * Generate a temporary ID for optimistic updates. * Uses timestamp + random suffix for uniqueness without global state. * * @example * ```typescript * .optimistic('create') // Auto-generates tempId * tempId() // Returns "temp_1234567890_abc123", etc. * ``` */ declare function tempId(): string; /** Check if an ID is a temporary ID */ declare function isTempId(id: string): boolean; /** * Type-level method definitions for the optimistic plugin. * * This type provides the method signatures for each builder stage. * Used by ExtractPluginMethods to compose builder types. * * @typeParam TStage - Builder stage name * @typeParam TInput - Input type from .args() * @typeParam TOutput - Output type from .returns() * @typeParam TContext - Context type from lens() */ type OptimisticPluginMethods< TStage extends string, TInput, TOutput, TContext > = TStage extends "MutationBuilderWithReturns" ? { /** * Define optimistic update behavior with typed callback. * The callback receives `{ args }` with the args type inferred from `.args()`. * * IMPORTANT: Callback overload comes FIRST to enable proper TypeScript inference * for inline arrow functions. TypeScript tries overloads in order. * * @param callback - Callback that receives typed args and returns step builders * @returns Builder with .resolve() method * * @example * ```typescript * .optimistic(({ args }) => [ * e.update(User, { id: args.id, name: args.name }), * ]) * ``` */ optimistic(callback: (ctx: { args: TInput; }) => StepBuilder2[]): MutationBuilderWithOptimistic; /** * Define optimistic update behavior with DSL spec. * * @param spec - Optimistic update specification (sugar or Pipeline) * @returns Builder with .resolve() method * * @example * ```typescript * .optimistic("merge") // Merge input with existing entity * .optimistic("create") // Create new entity from input * .optimistic({ merge: { published: true } }) // Merge specific fields * ``` */ optimistic(spec: OptimisticDSL): MutationBuilderWithOptimistic; } : {}; /** * Type extension for optimistic updates plugin. * * This interface is used by the lens() factory to identify the plugin * and compose builder types. The actual methods come from the * PluginMethodRegistry augmentation above. */ interface OptimisticPluginExtension extends PluginExtension { readonly name: "optimistic"; } /** * Symbol to identify optimistic plugin instances. * Used for runtime type checking and plugin detection. */ declare const OPTIMISTIC_PLUGIN_BRAND: unique symbol; declare const OPTIMISTIC_PLUGIN_SYMBOL: typeof OPTIMISTIC_PLUGIN_BRAND; /** * Marker interface for optimistic plugin instances. * Combines RuntimePlugin with a unique symbol for type narrowing. */ interface OptimisticPluginMarker extends RuntimePlugin { readonly [OPTIMISTIC_PLUGIN_SYMBOL]: true; } /** * Type guard to check if a plugin is an optimistic plugin. */ declare function isOptimisticPlugin(plugin: unknown): plugin is OptimisticPluginMarker; /** * Base interface for plugin type extensions. * * Each plugin declares methods it adds to various builder stages. * Methods are generic over TInput, TOutput, TContext to work with any operation. * * IMPORTANT: Plugin methods must be defined with proper generics to receive * the builder's type parameters. Use the helper types below. */ interface PluginExtension { /** Plugin name - must match the runtime plugin's name */ readonly name: string; /** * Methods added to MutationBuilder (before .args()) */ readonly MutationBuilder?: Record; /** * Methods added to MutationBuilder after .args() is called. * These methods are available before .returns() or .resolve(). */ readonly MutationBuilderWithArgs?: Record; /** * Methods added to MutationBuilder after .returns() is called. * These methods are available before .resolve(). */ readonly MutationBuilderWithReturns?: Record; /** * Methods added to QueryBuilder. */ readonly QueryBuilder?: Record; } /** * Empty extension type - represents no additional methods. * Uses {} (any object) rather than Record because * Record breaks intersection types (T & Record = never-like). * With {}, we get T & {} = T, which is the desired behavior for "no additional methods". */ type EmptyExtension = {}; /** * Empty plugin extension (no methods added). * Used as default when no plugins configured. */ interface NoPlugins extends PluginExtension { readonly name: "none"; } /** * Plugin Method Lookup - maps plugin names to their method types. * * This type performs compile-time lookup of plugin methods based on the plugin name. * * To add a new plugin: * 1. Create MyPluginMethods type * 2. Import it above * 3. Add a conditional branch here: Name extends "my-plugin" ? MyPluginMethods<...> : ... * * Note: This is intentionally explicit rather than using module augmentation. * It provides better cross-package type visibility and simpler TypeScript semantics. */ type LookupPluginMethods< Name extends string, TStage extends string, TInput, TOutput, TContext > = Name extends "optimistic" ? OptimisticPluginMethods : EmptyExtension; /** * Extract methods for a specific builder stage from plugin array. * * Uses direct type lookup based on plugin name. Each plugin's methods * are defined in its own file and looked up via LookupPluginMethods. * * @example * ```typescript * type Plugins = [OptimisticPluginExtension]; * type Methods = ExtractPluginMethods; * // Result: { optimistic(spec): MutationBuilderWithOptimistic } * ``` */ type ExtractPluginMethods< TPlugins extends readonly PluginExtension[], TStage extends string, TInput = unknown, TOutput = unknown, TContext = unknown > = TPlugins extends readonly [] ? EmptyExtension : UnionToIntersection : EmptyExtension : EmptyExtension>; /** * Check if plugin array includes a specific plugin by name. * * @example * ```typescript * type Has = HasPlugin<[OptimisticPlugin], 'optimistic'>; // true * type HasNot = HasPlugin<[ValidationPlugin], 'optimistic'>; // false * ``` */ type HasPlugin< TPlugins extends readonly PluginExtension[], Name extends string > = Extract extends never ? false : true; /** * Conditional type based on plugin presence. * * @example * ```typescript * type Result = IfPlugin * ``` */ type IfPlugin< TPlugins extends readonly PluginExtension[], Name extends string, Then, Else = EmptyExtension > = HasPlugin extends true ? Then : Else; /** * Extract methods from a specific stage across all plugins. * This is the legacy approach that extracts directly from plugin extension interfaces. * For plugins with generic parameters, use ExtractPluginMethods with PluginMethodRegistry. * * @deprecated Use ExtractPluginMethods with PluginMethodRegistry for generic-aware extraction * * @example * ```typescript * type Methods = ExtractExtension<[PluginA, PluginB], 'MutationBuilderWithReturns'>; * // Result: { methodA(): void } & { methodB(): void } * ``` */ type ExtractExtension< TPlugins extends readonly PluginExtension[], TStage extends keyof PluginExtension > = UnionToIntersection ? P[TStage] : EmptyExtension : EmptyExtension : EmptyExtension : EmptyExtension>; /** * Merge all extension categories from plugins into a single extension type. * This is the legacy approach that merges directly from plugin extension interfaces. * * @deprecated Use ExtractPluginMethods for individual stage extraction * * @example * ```typescript * type Merged = MergeExtensions<[PluginA, PluginB]>; * // Result: { * // MutationBuilderWithReturns: { methodA(): void } & { methodB(): void }; * // QueryBuilder: { queryMethod(): void }; * // } * ``` */ type MergeExtensions = { MutationBuilder: ExtractExtension; MutationBuilderWithArgs: ExtractExtension; MutationBuilderWithReturns: ExtractExtension; QueryBuilder: ExtractExtension; }; /** * @deprecated Use NoPlugins instead */ type NoExtension = NoPlugins; /** * Runtime plugin marker. * Used to connect type extensions with runtime plugin instances. * * @typeParam TExt - The plugin's type extension interface */ interface RuntimePlugin { /** Plugin name - must match extension's name */ readonly name: TExt["name"]; /** * Phantom type to store the extension type for extraction. * Never set at runtime - only used for type inference. */ readonly _extension?: TExt; /** * Runtime hooks for the plugin. * Called at various points during operation execution. */ readonly hooks?: PluginHooks; /** * Extension methods to add to builders. * Called during lens() initialization to wire up methods. */ readonly builderExtensions?: { /** * Factory for MutationBuilder methods (before .args()). */ MutationBuilder?: (builder: unknown) => Record; /** * Factory for MutationBuilderWithArgs methods. */ MutationBuilderWithArgs?: (builder: unknown) => Record; /** * Factory for MutationBuilderWithReturns methods. */ MutationBuilderWithReturns?: (builder: unknown) => Record; /** * Factory for QueryBuilder methods. */ QueryBuilder?: (builder: unknown) => Record; }; } /** * Plugin lifecycle hooks. * Plugins can hook into various stages of operation execution. */ interface PluginHooks { /** * Called before mutation execution. * Can modify input or abort execution. */ beforeMutation?: (ctx: { path: string; input: unknown; meta: Record; }) => void | Promise; /** * Called after mutation execution. * Can modify output or perform side effects. */ afterMutation?: (ctx: { path: string; input: unknown; output: unknown; meta: Record; }) => void | Promise; /** * Called before query execution. */ beforeQuery?: (ctx: { path: string; input: unknown; meta: Record; }) => void | Promise; /** * Called after query execution. */ afterQuery?: (ctx: { path: string; input: unknown; output: unknown; meta: Record; }) => void | Promise; } /** * Type guard to check if a value is a RuntimePlugin. */ declare function isRuntimePlugin(value: unknown): value is RuntimePlugin; /** * Helper type to extract extension from a single RuntimePlugin. * Uses the _extension phantom property for reliable extraction. */ type ExtractSingleExtension

= P extends { readonly _extension?: infer E; } ? E extends PluginExtension ? E : NoPlugins : NoPlugins; /** * Extract PluginExtension types from a RuntimePlugin array. * * This allows lens({ plugins: [optimisticPlugin()] }) to work where * optimisticPlugin() returns RuntimePlugin. * * @example * ```typescript * type Plugins = [RuntimePlugin]; * type Extensions = ExtractPluginExtensions; * // Result: [OptimisticPluginExtension] * ``` */ type ExtractPluginExtensions = { [K in keyof T] : ExtractSingleExtension }; /** Mutation definition */ interface MutationDef< TInput = unknown, TOutput = unknown, TContext = unknown > { _type: "mutation"; /** Mutation name (optional - derived from key if not provided) */ _name?: string | undefined; _input: ZodLikeSchema; _output?: ReturnSpec | undefined; /** Branded phantom types for inference */ _brand: { input: TInput; output: TOutput; }; /** Optimistic update DSL (declarative, serializable for client) */ _optimistic?: OptimisticDSL | undefined; /** Method syntax for bivariance - allows flexible context types */ _resolve(ctx: { args: TInput; ctx: TContext; }): TOutput | Promise | AsyncGenerator; } /** Mutation builder - fluent interface */ interface MutationBuilder2< _TInput = unknown, TOutput = unknown, TContext = unknown, TPlugins extends readonly PluginExtension[] = readonly PluginExtension[] > { /** Define args validation schema (required for mutations) */ args(schema: ZodLikeSchema): MutationBuilderWithArgs2; } /** Mutation builder after args is defined */ interface MutationBuilderWithArgs2< TInput, _TOutput = unknown, TContext = unknown, TPlugins extends readonly PluginExtension[] = readonly PluginExtension[] > { /** Define return type (optional - for entity outputs) */ returns(spec: R): MutationBuilderWithReturns22, TContext> & ExtractPluginMethods, TContext>; /** Define resolver function directly (without .returns()) */ resolve(fn: ResolverFn2): MutationDef; } /** * Mutation builder after returns is defined (strict version). * Only has .resolve() - no .optimistic(). */ interface MutationBuilderWithReturns22< TInput, TOutput, TContext = unknown > { /** Define resolver function */ resolve(fn: ResolverFn2): MutationDef; } /** Mutation builder after optimistic is defined */ interface MutationBuilderWithOptimistic< TInput, TOutput, TContext = unknown > { /** Define resolver function */ resolve(fn: ResolverFn2): MutationDef; } /** * Create a mutation builder * * @example * ```typescript * // Basic usage * const createPost = mutation() * .args(z.object({ title: z.string(), content: z.string() })) * .returns(Post) * .resolve(({ args }) => db.post.create({ data: args })); * * // With typed context * const createPost = mutation() * .args(z.object({ title: z.string() })) * .resolve(({ args, ctx }) => ctx.db.post.create({ data: args })); * ``` */ declare function mutation(): MutationBuilder2; declare function mutation(name: string): MutationBuilder2; /** Check if value is a mutation definition */ declare function isMutationDef(value: unknown): value is MutationDef; /** Check if value is any operation definition */ declare function isOperationDef(value: unknown): value is import("./query.js").QueryDef | MutationDef; /** * Map of collected models by name */ type CollectedModels = Map>; /** * Collect all models from a router definition. * * Traverses the router tree and extracts models from all operation return types. * * @example * ```typescript * const router = router({ * user: { * get: query().returns(User).resolve(...), * list: query().returns(list(User)).resolve(...), * }, * }); * * const models = collectModelsFromRouter(router); * // Map { "User" => UserModelDef } * ``` */ declare function collectModelsFromRouter(routerDef: RouterDef): CollectedModels; /** * Collect models from queries and mutations maps. * * @example * ```typescript * const models = collectModelsFromOperations(queries, mutations); * ``` */ declare function collectModelsFromOperations(queries: Record> | undefined, mutations: Record> | undefined): CollectedModels; /** * Merge multiple model collections into one. * Later collections take priority over earlier ones. */ declare function mergeModelCollections(...collections: (CollectedModels | Record> | undefined)[]): CollectedModels; /** * Define field resolvers for a model. * * Model = pure schema (types only) * Resolver = separate implementation * * Every field must have a resolver (use t.expose() for passthrough fields). * * @example * ```typescript * const userResolver = resolver(User, (t) => ({ * id: t.expose('id'), * name: t.expose('name'), * avatarKey: t.expose('avatarKey'), * // Computed field with plain function * avatar: ({ source, ctx }) => ctx.cdn.getAvatar(source.avatarKey), * })); * * // With typed context * const userResolver = resolver()(User, (t) => ({ * id: t.expose('id'), * posts: ({ source, ctx }) => ctx.db.posts.filter(p => p.authorId === source.id), * })); * ``` */ /** * Structural type for resolver entity parameter. * Accepts both EntityDef and ModelDef without strict constraint issues. */ type ResolverEntity = { readonly _name: string | undefined; readonly fields: Record; }; /** * Helper type for resolver field requirements. * - All model fields MUST be present (enforced) * - Extra computed/relation fields are ALLOWED (not in model) * * Model = scalar fields (data shape) * Resolver = all fields including computed/relations */ type ResolverFieldRequirements< TEntity extends ResolverEntity, TFields extends Record, TContext > = TFields & { [K in keyof TEntity["fields"]] : AnyFieldDef }; declare function resolver(): < TEntity extends ResolverEntity, TFields extends { [K in keyof TEntity["fields"]] : AnyFieldDef } >(entity: TEntity, builder: (f: FieldBuilder) => ResolverFieldRequirements) => ResolverDef; declare function resolver< TEntity extends ResolverEntity, TFields extends { [K in keyof TEntity["fields"]] : AnyFieldDef } >(entity: TEntity, builder: (f: FieldBuilder) => ResolverFieldRequirements): ResolverDef; /** * Convert resolver array to lookup map. * * @example * ```typescript * const resolverMap = toResolverMap([userResolver, postResolver]); * const userDef = resolverMap.get("User"); * ``` */ declare function toResolverMap(resolvers: Resolvers): Map>; /** Check if field is exposed */ declare function isExposedField(field: FieldDef2): field is ExposedField; /** Check if field is resolved */ declare function isResolvedField(field: FieldDef2): field is ResolvedField; /** Check if value is a resolver definition */ declare function isResolverDef(value: unknown): value is ResolverDef; /** * Create a ResolverDef from a model with all fields exposed. * * Use this when you want a simple passthrough resolver for all fields. * For custom field resolution, use the `resolver()` function instead. * * @example * ```typescript * const User = model('User', { * id: id(), * name: string(), * email: string(), * }); * * // Create simple exposed-only resolver * const simpleResolver = createResolverFromEntity(User); * * // Or use resolver() for custom field handling * const customResolver = resolver(User, (t) => ({ * id: t.expose('id'), * name: t.expose('name'), * email: t.expose('email'), * avatar: ({ source, ctx }) => ctx.cdn.getAvatar(source.avatarKey), * })); * ``` */ declare function createResolverFromEntity< TEntity extends AnyEntityLike, TContext = FieldResolverContext >(entity: TEntity): ResolverDef>, TContext>; /** * Check if an entity has any inline resolvers defined. * * @deprecated Models no longer support inline resolvers. * Use standalone `resolver(Model, ...)` instead. * * This function always returns false for new ModelDef instances. * Kept for backward compatibility during migration. */ declare function hasInlineResolvers(_entity: AnyEntityLike): boolean; /** * @sylphx/lens-core - Context Types * * Type definitions for the context system. * Implementation is in @sylphx/lens-server. */ /** Context value - can be any object */ type ContextValue = object; /** Context store type (opaque - platform specific) */ type ContextStore = unknown & { __brand: "ContextStore"; __type: T; }; import { StepBuilder as StepBuilder3 } from "@sylphx/reify"; /** * Structural type for resolver entity parameter. * Matches both EntityDef and ModelDef. */ type ResolverEntity2 = { readonly _name: string | undefined; readonly fields: Record; }; /** * Any field definition for resolver. */ type AnyResolverFieldDef = FieldDef2 | ((params: { source: any; ctx: TContext; }) => any); /** * Typed resolver factory function. * Enforces that ALL entity fields must have a resolver. */ type LensResolver = (entity: TEntity, builder: (f: FieldBuilder) => { [K in keyof TEntity["fields"]] : AnyResolverFieldDef }) => ResolverDef>, TContext>; /** * Typed query factory function */ interface LensQuery { (): QueryBuilder2; (name: string): QueryBuilder2; } /** * Typed subscription factory function */ interface LensSubscription { (): SubscriptionBuilder; (name: string): SubscriptionBuilder; } /** * Typed model factory function. * Creates models with pre-typed context using plain object fields. */ type LensModel = ModelFactory; /** * Mutation builder with plugin support. * Carries TPlugins through the entire chain. */ interface LensMutationBuilder< _TInput, TOutput, TContext, TPlugins extends readonly PluginExtension[] > { /** Define args validation schema */ args(schema: ZodLikeSchema): LensMutationBuilderWithArgs; } /** * Mutation builder after .args() with plugin support. */ interface LensMutationBuilderWithArgs< TInput, _TOutput, TContext, TPlugins extends readonly PluginExtension[] > { /** Define return type */ returns(spec: R): LensMutationBuilderWithReturns, TContext, TPlugins>; /** Define resolver directly (for simple return types) */ resolve(fn: ResolverFn2): MutationDef; } /** * Mutation builder after .returns() with plugin support. * * Uses conditional type to select the appropriate interface: * - With optimistic plugin: interface with proper optimistic() overloads * - Without: base interface with just resolve() * * This approach preserves TypeScript's overload resolution for better * callback parameter inference. */ type LensMutationBuilderWithReturns< TInput, TOutput, TContext, TPlugins extends readonly PluginExtension[] > = HasPlugin extends true ? LensMutationBuilderWithReturnsAndOptimistic : MutationBuilderWithReturns22; /** * Mutation builder with optimistic() overloads. * This interface preserves proper overload semantics for callback inference. * * IMPORTANT: Callback overload comes FIRST to enable proper TypeScript inference * for inline arrow functions. TypeScript tries overloads in order. */ interface LensMutationBuilderWithReturnsAndOptimistic< TInput, TOutput, TContext > extends MutationBuilderWithReturns22 { /** * Define optimistic update behavior with typed callback. * The callback receives `{ args }` with the args type inferred from `.args()`. * * @param callback - Callback that receives typed args and returns step builders * @returns Builder with .resolve() method * * @example * ```typescript * .optimistic(({ args }) => [ * e.update(User, { id: args.id, name: args.name }), * ]) * ``` */ optimistic(callback: (ctx: { args: TInput; }) => StepBuilder3[]): MutationBuilderWithOptimistic; /** * Define optimistic update behavior with DSL spec. * * @param spec - Optimistic update specification (sugar or Pipeline) * @returns Builder with .resolve() method * * @example * ```typescript * .optimistic("merge") // Merge args with existing entity * .optimistic("create") // Create new entity from args * .optimistic({ merge: { published: true } }) // Merge specific fields * ``` */ optimistic(spec: OptimisticDSL): MutationBuilderWithOptimistic; } /** * Typed mutation factory function with plugin support. */ interface LensMutation< TContext, TPlugins extends readonly PluginExtension[] > { (): LensMutationBuilder; (name: string): LensMutationBuilder; } /** * Result of lens() without plugins. */ interface Lens { /** * Create a model with pre-typed context. */ model: LensModel; /** * Create a resolver with pre-typed context. */ resolver: LensResolver; /** * Create a query with pre-typed context. */ query: LensQuery; /** * Create a mutation with pre-typed context. * No plugin methods available (use lens({ plugins }) to enable). */ mutation: LensMutation; /** * Create a subscription with pre-typed context. */ subscription: LensSubscription; } /** * Result of lens({ plugins }) with plugins. * * @typeParam TContext - User context type * @typeParam TPlugins - Tuple of plugin extension types */ interface LensWithPlugins< TContext, TPlugins extends readonly PluginExtension[] > { /** * Create a model with pre-typed context. */ model: LensModel; /** * Create a resolver with pre-typed context. */ resolver: LensResolver; /** * Create a query with pre-typed context. */ query: LensQuery; /** * Create a mutation with pre-typed context and plugin methods. */ mutation: LensMutation; /** * Create a subscription with pre-typed context. */ subscription: LensSubscription; /** * Runtime plugins for use with createApp(). */ plugins: RuntimePlugin[]; } /** * Configuration for lens() with plugins. */ interface LensConfig[] = readonly RuntimePlugin[]> { /** * Plugins that extend builder functionality. * * @example * ```typescript * const { mutation } = lens({ * plugins: [optimisticPlugin()], * }); * ``` */ plugins?: TPlugins; } /** * Create typed query, mutation, and resolver builders with shared context. * * This is the primary API for Lens - call once with your context type, * then use the returned builders everywhere. * * @example Without plugins * ```typescript * const { query, mutation, resolver } = lens(); * * // .optimistic() is NOT available * const updateUser = mutation() * .args(z.object({ id: z.string() })) * .returns(User) * .resolve(({ args }) => ...); // Only .resolve() available * ``` * * @example With plugins * ```typescript * const { mutation, plugins } = lens({ * plugins: [optimisticPlugin()], * }); * * // .optimistic() is now available * const updateUser = mutation() * .args(z.object({ id: z.string() })) * .returns(User) * .optimistic('merge') // ✅ Plugin method available * .resolve(({ args }) => ...); * * // Pass plugins to server * createApp({ router, plugins }); * ``` */ /** * Lens builder that allows separate configuration of context type and plugins. * This avoids TypeScript overload resolution issues when providing explicit type params. */ interface LensBuilder extends Lens { /** * Add plugins to the lens. * Returns LensWithPlugins which includes plugin-provided methods on builders. */ withPlugins[]>(plugins: TPlugins): LensWithPlugins>; } /** * Create a typed lens without plugins. * Use .withPlugins() to add plugins after the fact. * * @example * ```typescript * // Basic usage * const { query, mutation } = lens(); * * // With plugins - use .withPlugins() * const { mutation, plugins } = lens().withPlugins([optimisticPlugin()]); * mutation().args(...).returns(...).optimistic('merge'); * ``` */ declare function lens(): LensBuilder; /** * Create a typed lens with plugins. * This overload is for when you want to provide plugins inline. * * NOTE: Due to TypeScript overload resolution, do NOT provide an explicit type * parameter when using this form. Let TypeScript infer TContext from usage, * or use `lens().withPlugins([...])` instead. */ declare function lens< TContext, const TPlugins extends readonly RuntimePlugin[] >(config: { plugins: TPlugins; }): LensWithPlugins>; /** * @sylphx/lens-core - Reconnection Types * * Type definitions for the version-based reconnection system. * Enables seamless state synchronization after connection loss. */ /** * Version number for entity state. * Monotonically increasing, incremented on every state change. */ type Version = number; /** * Versioned entity state stored on server. */ interface VersionedEntityState> { /** Current entity data */ data: T; /** Monotonically increasing version number */ version: Version; /** Timestamp of last update (Unix ms) */ updatedAt: number; } /** * Versioned array state stored on server. */ interface VersionedArrayState { /** Current array items */ items: T[]; /** Version number */ version: Version; /** Timestamp of last update */ updatedAt: number; } /** * JSON Patch operation (RFC 6902). */ interface PatchOperation2 { op: "add" | "remove" | "replace" | "move" | "copy" | "test"; path: string; value?: unknown; from?: string; } /** * Single operation log entry. * Represents one state change that can be replayed. */ interface OperationLogEntry { /** Entity key (e.g., "user:123") */ entityKey: string; /** Version AFTER this operation */ version: Version; /** Timestamp when operation occurred (Unix ms) */ timestamp: number; /** JSON Patch operations (RFC 6902) */ patch: PatchOperation2[]; /** Size of patch in bytes (for memory tracking) */ patchSize: number; } /** * Configuration for operation log. */ interface OperationLogConfig { /** Maximum number of entries to retain (default: 10000) */ maxEntries: number; /** Maximum age of entries in milliseconds (default: 300000 = 5 min) */ maxAge: number; /** Maximum total memory usage in bytes (default: 10485760 = 10MB) */ maxMemory: number; /** Cleanup interval in milliseconds (default: 60000 = 1 min) */ cleanupInterval: number; } /** * Statistics for operation log. */ interface OperationLogStats { /** Current number of entries */ entryCount: number; /** Number of unique entities tracked */ entityCount: number; /** Total memory usage in bytes */ memoryUsage: number; /** Oldest entry timestamp */ oldestTimestamp: number | null; /** Newest entry timestamp */ newestTimestamp: number | null; /** Configuration limits */ config: OperationLogConfig; } /** * Subscription state. */ type SubscriptionState = "pending" | "active" | "reconnecting" | "error"; /** * Tracked subscription with version information. */ interface TrackedSubscription { /** Unique subscription ID */ id: string; /** Entity type (e.g., "user") */ entity: string; /** Entity ID (e.g., "123") */ entityId: string; /** Subscribed fields or "*" for all */ fields: string[] | "*"; /** Last received version from server */ version: Version; /** Last known data (for optimistic updates and reconnect) */ lastData: Record | null; /** Hash of last data (for efficient comparison) */ lastDataHash: string | null; /** Observer callbacks */ observer: SubscriptionObserver; /** Subscription state */ state: SubscriptionState; /** Original subscription input */ input: unknown; /** Timestamp when subscription was created */ createdAt: number; /** Timestamp of last update received */ lastUpdateAt: number | null; } /** * Observer callbacks for subscription. */ interface SubscriptionObserver { next?: (result: SubscriptionResult) => void; error?: (error: Error) => void; complete?: () => void; } /** * Result delivered to subscription observer. */ interface SubscriptionResult { data: T | null; deleted?: boolean; version?: Version; } /** * Statistics for subscription registry. */ interface SubscriptionRegistryStats { /** Total subscriptions */ total: number; /** By state */ byState: Record; /** By entity type */ byEntity: Record; } /** * Current protocol version. */ declare const PROTOCOL_VERSION = 2; /** * Subscription info sent during reconnect. */ interface ReconnectSubscription { /** Original subscription ID */ id: string; /** Entity type */ entity: string; /** Entity ID */ entityId: string; /** Subscribed fields */ fields: string[] | "*"; /** Last received version */ version: Version; /** Hash of last known data for verification */ dataHash?: string; /** Original subscription input */ input?: unknown; } /** * Client → Server: Request to restore subscriptions after reconnect. */ interface ReconnectMessage { type: "reconnect"; /** Protocol version for forward compatibility */ protocolVersion: number; /** Subscriptions to restore */ subscriptions: ReconnectSubscription[]; /** Client-generated reconnect ID for deduplication */ reconnectId: string; /** Client timestamp for latency measurement */ clientTime: number; } /** * Reconnect status for single subscription. */ type ReconnectStatus = "current" | "patched" | "snapshot" | "deleted" | "error"; /** * Result for single subscription in reconnect response. */ interface ReconnectResult { /** Subscription ID */ id: string; /** Entity type */ entity: string; /** Entity ID */ entityId: string; /** Sync status */ status: ReconnectStatus; /** Current server version */ version: Version; /** For "patched": ordered patches to apply */ patches?: PatchOperation2[][]; /** For "snapshot": full current state */ data?: Record; /** For "snapshot": hash of data for verification */ dataHash?: string; /** Error message if status is "error" */ error?: string; } /** * Server → Client: Response with catch-up data. */ interface ReconnectAckMessage { type: "reconnect_ack"; /** Results for each subscription */ results: ReconnectResult[]; /** Server timestamp for sync */ serverTime: number; /** Reconnect ID echo for correlation */ reconnectId: string; /** Processing duration on server (ms) */ processingTime: number; } /** * Server → Client: Subscription acknowledgment with initial state. */ interface SubscriptionAckMessage { type: "subscription_ack"; /** Subscription ID */ id: string; /** Entity type */ entity: string; /** Entity ID */ entityId: string; /** Initial version */ version: Version; /** Initial data */ data: Record; /** Data hash for future verification */ dataHash: string; } /** * Field update in versioned update message. */ interface VersionedFieldUpdate { /** Update strategy: value, delta, patch */ s: "v" | "d" | "p"; /** Update data */ d: unknown; } /** * Entity update with version. */ interface VersionedEntityUpdate { /** New version after this update */ v: Version; /** Field updates */ [field: string]: VersionedFieldUpdate | Version; } /** * Server → Client: State update with version. */ interface VersionedUpdateMessage { type: "update"; /** Grouped updates by entity type and ID */ updates: { [entity: string]: { [id: string]: VersionedEntityUpdate; }; }; /** Server timestamp */ serverTime: number; } /** * Connection state for client. */ type ConnectionState = "disconnected" | "connecting" | "connected" | "reconnecting"; /** * Reconnect configuration. */ interface ReconnectConfig { /** Enable auto-reconnect (default: true) */ enabled: boolean; /** Maximum reconnect attempts (default: 10) */ maxAttempts: number; /** Base delay between reconnects in ms (default: 1000) */ baseDelay: number; /** Maximum delay between reconnects in ms (default: 30000) */ maxDelay: number; /** Jitter factor 0-1 (default: 0.3) */ jitter: number; } /** * Connection quality metrics. */ interface ConnectionQuality { /** Round-trip latency in ms */ latency: number; /** Estimated bandwidth in bytes/sec */ bandwidth: number; /** Packet loss ratio 0-1 */ packetLoss: number; /** Number of successful pings */ successfulPings: number; /** Number of failed pings */ failedPings: number; } /** * Reconnection metrics. */ interface ReconnectionMetrics { /** Total reconnect attempts */ totalAttempts: number; /** Successful reconnects */ successfulReconnects: number; /** Failed reconnects */ failedReconnects: number; /** Success rate (0-1) */ successRate: number; /** Average latency in ms */ averageLatency: number; /** 50th percentile latency in ms */ p50Latency: number; /** 95th percentile latency in ms */ p95Latency: number; /** 99th percentile latency in ms */ p99Latency: number; /** Total subscriptions processed */ totalSubscriptionsProcessed: number; /** Breakdown by status */ statusBreakdown: Record; /** Total bytes transferred */ bytesTransferred: number; } /** * Health check result. */ interface ReconnectionHealth { /** Health status */ status: "healthy" | "degraded" | "unhealthy"; /** Current metrics */ metrics: ReconnectionMetrics; /** Issues detected */ issues: string[]; /** Last reconnect timestamp */ lastReconnect: number | null; /** Number of pending reconnections */ pendingReconnects: number; } /** * Generate unique ID. */ declare function generateReconnectId(): string; /** * Default operation log config. */ declare const DEFAULT_OPERATION_LOG_CONFIG: OperationLogConfig; /** * Default reconnect config. */ declare const DEFAULT_RECONNECT_CONFIG: ReconnectConfig; /** * @sylphx/lens-core - Hashing Utilities * * Fast hashing for change detection using MurmurHash3. * Optimized for comparing entity state without full JSON.stringify comparison. */ /** * MurmurHash3 32-bit implementation. * Fast, non-cryptographic hash with excellent distribution. * * @param key - String to hash * @param seed - Optional seed value (default: 0) * @returns 32-bit hash as hex string */ declare function murmurhash3(key: string, seed?: number): string; /** * Hash any value for fast comparison. * Uses type-specific handling for optimal performance. * * @param value - Value to hash * @returns Hash string */ declare function hashValue(value: unknown): string; /** * Stable JSON stringify with sorted keys. * Ensures consistent hash for equivalent objects. * * @param value - Value to stringify * @returns Stable JSON string */ declare function stableStringify(value: unknown): string; /** * Hash entire entity state. * Optimized for comparing full entity objects. * * @param data - Entity data object * @returns Hash string */ declare function hashEntityState(data: Record): string; /** * Hash specific fields of entity state. * Useful for partial comparisons. * * @param data - Entity data object * @param fields - Fields to include in hash * @returns Hash string */ declare function hashEntityFields(data: Record, fields: string[]): string; /** * LRU cache for hash results. * Avoids recomputing hashes for unchanged values. */ declare class HashCache { private cache; private maxSize; private maxAge; constructor(maxSize?: number, maxAge?: number); /** * Get cached hash or compute and cache. */ get(value: unknown): string; /** * Store hash in cache. */ private set; /** * Clear the cache. */ clear(): void; /** * Get cache statistics. */ getStats(): { size: number; maxSize: number; }; } /** * Per-field hash tracking for efficient change detection. * Stores hash of each field value to avoid deep comparison. */ declare class FieldHashMap { private hashes; /** * Check if field value has changed and update hash. * Returns true if value changed, false if unchanged. * * @param field - Field name * @param value - New value * @returns Whether the value changed */ hasChanged(field: string, value: unknown): boolean; /** * Update hash without checking for change. */ update(field: string, value: unknown): void; /** * Get current hash for field. */ getHash(field: string): string | undefined; /** * Remove field hash. */ delete(field: string): void; /** * Clear all hashes. */ clear(): void; /** * Get all field hashes. */ getAll(): Map; /** * Get combined hash of all fields. */ getCombinedHash(): string; } /** * Compare two values efficiently using hashes. * Falls back to deep comparison only if hashes differ but might be collision. * * @param a - First value * @param b - Second value * @param aHash - Pre-computed hash of a (optional) * @param bHash - Pre-computed hash of b (optional) * @returns Whether values are equal */ declare function valuesEqual(a: unknown, b: unknown, aHash?: string, bHash?: string): boolean; /** * Deep equality check (fallback for when hash comparison is inconclusive). */ declare function deepEqual(a: unknown, b: unknown): boolean; /** * Apply JSON Patch operations to object. * Implements RFC 6902. * * @param target - Object to patch * @param patch - Patch operations * @returns New patched object (does not mutate original) */ declare function applyPatch>(target: T, patch: PatchOperation2[]): T; /** * Metrics event emitted during reconnection operations. */ interface MetricsEvent { type: "reconnect_start" | "reconnect_complete" | "reconnect_error" | "subscription_result"; timestamp: number; data: Record; } /** * Metrics collector callback. */ type MetricsCollector = (event: MetricsEvent) => void; /** * Reconnection metrics configuration. */ interface MetricsConfig { /** Enable metrics collection (default: true) */ enabled: boolean; /** Sample rate for metrics (0.0 - 1.0, default: 1.0) */ sampleRate: number; /** Maximum history entries to keep (default: 1000) */ maxHistory: number; /** Custom metrics collector */ collector?: MetricsCollector; } /** * Default metrics configuration. */ declare const DEFAULT_METRICS_CONFIG: MetricsConfig; /** * Tracks and aggregates reconnection metrics. * * @example * ```typescript * const metrics = new ReconnectionMetricsTracker(); * * // Track reconnection * metrics.startReconnection("rc_123", 5); * // ... perform reconnection ... * metrics.completeReconnection("rc_123", results); * * // Get statistics * const stats = metrics.getMetrics(); * console.log(stats.successRate, stats.averageLatency); * ``` */ declare class ReconnectionMetricsTracker { private config; private history; private pending; private totalAttempts; private totalSuccesses; private totalFailures; private totalSubscriptionsProcessed; private totalBytesTransferred; private statusCounts; private latencies; constructor(config?: Partial); /** * Record start of a reconnection attempt. */ startReconnection(reconnectId: string, subscriptionCount: number): void; /** * Record successful completion of a reconnection. */ completeReconnection(reconnectId: string, results: ReconnectResult[]): void; /** * Record reconnection failure. */ failReconnection(reconnectId: string, error: Error): void; /** * Get current reconnection metrics. */ getMetrics(): ReconnectionMetrics; /** * Get reconnection health status. */ getHealth(): ReconnectionHealth; /** * Get recent reconnection history. */ getHistory(limit?: number): ReconnectionRecord[]; /** * Reset all metrics. */ reset(): void; /** * Export metrics as JSON. */ toJSON(): Record; private shouldSample; private emit; private addToHistory; private countStatuses; private percentile; } /** * Record of a single reconnection attempt. */ interface ReconnectionRecord { reconnectId: string; startTime: number; endTime: number; latency: number; subscriptionCount: number; resultCount: number; statusBreakdown: Record; success: boolean; error?: string; } /** * Create a new metrics tracker. */ declare function createMetricsTracker(config?: Partial): ReconnectionMetricsTracker; /** * Registry for tracking all active subscriptions on the client. * * Responsibilities: * - Track subscription metadata (entity, id, fields, input) * - Track last received version and data * - Provide subscription list for reconnect * - Manage subscription lifecycle states * * @example * ```typescript * const registry = new SubscriptionRegistry(); * * // When subscribing * registry.add({ * id: "sub_123", * entity: "user", * entityId: "456", * fields: ["name", "email"], * version: 0, * lastData: null, * observer: { next: (data) => console.log(data) }, * input: { id: "456" }, * }); * * // When receiving update * registry.updateVersion("sub_123", 5, { name: "Alice", email: "alice@example.com" }); * * // On reconnect * const subs = registry.getAllForReconnect(); * // [{ id: "sub_123", entity: "user", entityId: "456", version: 5, ... }] * ``` */ declare class SubscriptionRegistry { private subscriptions; private entityIndex; /** * Register new subscription. */ add(sub: Omit): void; /** * Get subscription by ID. */ get(id: string): TrackedSubscription | undefined; /** * Check if subscription exists. */ has(id: string): boolean; /** * Remove subscription. */ remove(id: string): void; /** * Get all subscriptions for an entity. */ getByEntity(entity: string, entityId: string): TrackedSubscription[]; /** * Update version after receiving update from server. */ updateVersion(id: string, version: Version, data?: Record): void; /** * Update last known data (for optimistic updates). */ updateData(id: string, data: Record): void; /** * Get last known data for subscription. */ getLastData(id: string): Record | null; /** * Get current version for subscription. */ getVersion(id: string): Version | null; /** * Mark subscription as active. */ markActive(id: string): void; /** * Mark subscription as error. */ markError(id: string): void; /** * Mark all active subscriptions as reconnecting. * Called when connection is lost. */ markAllReconnecting(): void; /** * Get subscriptions by state. */ getByState(state: SubscriptionState): TrackedSubscription[]; /** * Get all subscriptions formatted for reconnect message. * Only includes subscriptions that have received at least one update. */ getAllForReconnect(): ReconnectSubscription[]; /** * Process reconnect result for single subscription. */ processReconnectResult(id: string, version: Version, data?: Record): void; /** * Get observer for subscription. */ getObserver(id: string): SubscriptionObserver | undefined; /** * Update observer for subscription. */ updateObserver(id: string, observer: SubscriptionObserver): void; /** * Notify observer with data. */ notifyNext(id: string, data: T): void; /** * Notify observer with error. */ notifyError(id: string, error: Error): void; /** * Notify all reconnecting subscriptions with error. */ notifyAllReconnectingError(error: Error): void; /** * Get total subscription count. */ get size(): number; /** * Get all subscription IDs. */ getIds(): string[]; /** * Get all subscriptions (iterator). */ values(): IterableIterator; /** * Get statistics about the registry. */ getStats(): SubscriptionRegistryStats; /** * Clear all subscriptions. */ clear(): void; /** * Clear subscriptions in error state. */ clearErrors(): void; } /** * Create new subscription registry. */ declare function createSubscriptionRegistry(): SubscriptionRegistry; /** * @sylphx/lens-core - Observable Types * * Minimal Observable interface for streaming operations. * Compatible with RxJS and other Observable implementations. */ /** * Observer for receiving streamed values. */ interface Observer { /** Called for each emitted value */ next?: (value: T) => void; /** Called when an error occurs (terminates stream) */ error?: (err: Error) => void; /** Called when stream completes successfully */ complete?: () => void; } /** * Handle for unsubscribing from an Observable. */ interface Unsubscribable { /** Stop receiving values and clean up resources */ unsubscribe(): void; } /** * Observable represents a stream of values over time. * * Can emit: * - Zero or more values via next() * - An error via error() (terminates stream) * - Completion via complete() (terminates stream) * * @example * ```typescript * // One-shot (like Promise) * const oneShot: Observable = { * subscribe(observer) { * getData().then(data => { * observer.next?.({ data }); * observer.complete?.(); * }); * return { unsubscribe: () => {} }; * } * }; * * // Streaming (multiple values) * const streaming: Observable = { * subscribe(observer) { * const interval = setInterval(() => { * observer.next?.({ data: Date.now() }); * }, 1000); * return { unsubscribe: () => clearInterval(interval) }; * } * }; * ``` */ interface Observable { subscribe(observer: Observer): Unsubscribable; } /** * Check if a value is an Observable. */ declare function isObservable(value: unknown): value is Observable; /** * Convert Observable to Promise, taking only the first value. * * @throws Error if Observable completes without emitting * @throws Error from Observable's error callback */ declare function firstValueFrom(observable: Observable): Promise; /** * Create an Observable that emits a single value and completes. */ declare function of(value: T): Observable; /** * Create an Observable that immediately errors. */ declare function throwError(error: Error): Observable; /** * Create an Observable from an AsyncIterable. */ declare function fromAsyncIterable(iterable: AsyncIterable): Observable; /** * Create an Observable from a Promise. */ declare function fromPromise(promise: Promise): Observable; /** * Apply an array of operations to state, returning new state. * Immutable - always returns a new object. * * @example * ```typescript * const state = { user: { name: "Alice", age: 30 } }; * const ops: Op[] = [ * { o: "set", p: "user.name", v: "Bob" }, * { o: "set", p: "user.age", v: 31 } * ]; * const newState = applyOps(state, ops); * // { user: { name: "Bob", age: 31 } } * ``` */ declare function applyOps(state: T, ops: Op[]): T; /** * Apply a single operation to state */ declare function applyOp(state: T, op: Op): T; /** * @sylphx/lens-core - Paired Plugin Type * * PairedPlugin allows writing a single plugin that works on both * server and client. Each side extracts its relevant part. * * @example * ```typescript * import type { PairedPlugin } from '@sylphx/lens-core'; * import type { ServerPlugin } from '@sylphx/lens-server'; * import type { Plugin as ClientPlugin } from '@sylphx/lens-client'; * * const compression: PairedPlugin = { * __paired: true, * server: { * name: 'compression', * beforeSend: (ctx) => compress(ctx.data), * }, * client: { * name: 'compression', * afterResponse: (result) => decompress(result.data), * }, * }; * ``` */ /** * Paired plugin that works on both server and client. * * This pattern allows writing cross-cutting concerns (like compression, * encryption, or logging) as a single unit that's properly split * between server and client at runtime. * * @typeParam S - Server plugin type * @typeParam C - Client plugin type */ interface PairedPlugin< S = unknown, C = unknown > { /** Marker to identify paired plugins */ readonly __paired: true; /** Server-side plugin */ server: S; /** Client-side plugin */ client: C; } /** * Type guard to check if a value is a PairedPlugin. * * @param plugin - Value to check * @returns true if plugin is a PairedPlugin */ declare function isPairedPlugin< S, C >(plugin: unknown): plugin is PairedPlugin; /** * Extract server plugins from a mixed array of plugins. * * Handles both regular server plugins and paired plugins (extracts .server). * * @param plugins - Array of server plugins or paired plugins * @returns Array of server plugins only */ declare function resolveServerPlugins(plugins: (S | PairedPlugin)[]): S[]; /** * Extract client plugins from a mixed array of plugins. * * Handles both regular client plugins and paired plugins (extracts .client). * * @param plugins - Array of client plugins or paired plugins * @returns Array of client plugins only */ declare function resolveClientPlugins(plugins: (C | PairedPlugin)[]): C[]; /** Entity key format: "EntityName:id" */ type EntityKey = `${string}:${string}`; /** Create entity key from entity name and id */ declare function makeEntityKey(entity: string, id: string): EntityKey; /** Parse entity key into entity name and id */ declare function parseEntityKey(key: EntityKey): [string, string]; export { valuesEqual, valueStrategy, toResolverMap, toOps, timestamp, throwError, tempId, subscription, string, stableStringify, selectStrategy, scalar, router, resolver, resolveServerPlugins, resolveClientPlugins, query, processFieldDef, patchStrategy, parseEntityKey, operations, of, object, nullable, mutation, murmurhash3, model, mergeModelCollections, makeEntityKey, list, lens, json, isTempId, isSubscriptionDef, isSnapshot, isScalarType, isRuntimePlugin, isRouterDef, isResolverDef, isResolvedField, isQueryDef, isPipeline, isPairedPlugin, isOptimisticPlugin, isOptimisticDSL, isOps, isOperationDef, isObservable, isNullableDef, isNormalizableModel, isMutationDef, isModelDef, isLiveQueryDef, isListDef, isLazyRef, isExposedField, isError, int, id, hashValue, hashEntityState, hashEntityFields, hasInlineResolvers, generateReconnectId, fromPromise, fromAsyncIterable, float, flattenRouter, firstValueFrom, enumType, deltaStrategy, deepEqual, decimal, datetime, createUpdate, createSubscriptionRegistry, createResolverFromEntity, createMetricsTracker, createEmitScalar, createEmitObject, createEmitArray, createEmit, createArrayUpdate, computeArrayDiff, collectModelsFromRouter, collectModelsFromOperations, bytes, boolean, bigint, applyUpdate, applyPatch, applyOps, applyOp, applyArrayDiff, VersionedUpdateMessage, VersionedFieldUpdate, VersionedEntityUpdate, VersionedEntityState, VersionedArrayState, Version, ValueUpdate, UpdateStrategy, UpdateInput, Update, UnwrapType, Unsubscribable, UnionToIntersection, TrackedSubscription, TimestampType, SubscriptionState, SubscriptionResultType, SubscriptionResult, SubscriptionRegistryStats, SubscriptionRegistry, SubscriptionObserver, SubscriptionDef, SubscriptionBuilder, SubscriptionAckMessage, StringType, StrategyName, Select2 as Select, SchemaDefinition, ScalarTypeDefinition, ScalarType, ScalarSelectOptions, ScalarFieldsOnly, ScalarFields, RuntimePlugin, RouterRoutes, RouterDef, ReturnSpec, Resolvers, ResolverDef, ResolvedField, RequireKeys, RelationSelectOptions, RelationFields, ReconnectionRecord, ReconnectionMetricsTracker, ReconnectionMetrics, ReconnectionHealth, ReconnectSubscription, ReconnectStatus, ReconnectResult, ReconnectMessage, ReconnectConfig, ReconnectAckMessage, QueryResultType, QueryMode, QueryDefChainable, QueryDef2 as QueryDef, PublisherResolverFn, Prettify, PluginHooks, PluginExtension, PlainFieldDefinition, Pipeline, PatchUpdate, PatchOperation, PairedPlugin, PROTOCOL_VERSION, OptionalKeys, OptimisticSugar, OptimisticPluginMethods, OptimisticPluginMarker, OptimisticPluginExtension, OptimisticDSL, OptimisticContext, OptimisticCallback, Operations, OperationLogStats, OperationLogEntry, OperationLogConfig, Op, OnCleanup, Observer, Observable, ObjectType, OPTIMISTIC_PLUGIN_SYMBOL, NullableType, NullableDef, NoPlugins, NoExtension, NULLABLE_SYMBOL, MutationResultType, MutationDef, ModelFieldSubscriber, ModelFieldResolver, ModelFactory, ModelDef, MetricsEvent, MetricsConfig, MetricsCollector, MessageObserver, MessageObservable, MessageData, Message, MergeExtensions, MODEL_SYMBOL, LiveQueryDef, ListDef, LensWithPlugins, LensResolver, LensQuery, LensMutation, LensModel, LensConfig, LensBuilder, Lens, LIST_SYMBOL, IsRelation, IsNullable, IsList, IsHasOne, IsHasMany, IsBelongsTo, InternalFieldUpdate, IntType, InferSelected, InferSchemaEntities, InferScalarWithNullable, InferScalar, InferRouterContext, InferRouterClient, InferResolverSelected, InferResolverOutput, InferRelationType, InferRelationTarget, InferModelType, InferModelSource, InferFieldType, InferFieldDefType, InferEntity, IfPlugin, IdType, HashCache, HasPlugin, FloatType, FieldUpdate, FieldType, FieldSubscriberMap, FieldResolverMap, FieldResolverContext, FieldResolveParams, FieldQueryContext, FieldHashMap, FieldEmit, FieldDef as FieldDefinitionType, FieldDefinition, FieldDef2 as FieldDef, FieldBuilder, FieldArgs, ExtractPluginMethods, ExtractPluginExtensions, ExtractExtension, ExposedField, EnumType, EntityType, EntityNames, EntityKey, EntityDefinition, EmitScalar, EmitObject, EmitCommand, EmitArray, Emit, DeltaUpdate, DeltaOperation, DeleteInput, DefaultType, DeepPartial, DecimalType, DateTimeType, DEFAULT_RECONNECT_CONFIG, DEFAULT_OPERATION_LOG_CONFIG, DEFAULT_METRICS_CONFIG, CreateInput, ContextValue, ContextStore, ConnectionState, ConnectionQuality, CollectedModels, BooleanType, ArrayUpdate, ArrayType, ArrayOperation, ArrayDiffOperation, AnyQueryDef, AnyProcedure };