import { $resyncPrune } from "./symbols.js"; import type { PrimitiveType, RawPrimitiveType } from "../annotations.js"; import type { Schema } from "../Schema.js"; import type { ArraySchema } from "./custom/ArraySchema.js"; import type { CollectionSchema } from "./custom/CollectionSchema.js"; import type { MapSchema } from "./custom/MapSchema.js"; import type { SetSchema } from "./custom/SetSchema.js"; import type { StreamSchema } from "./custom/StreamSchema.js"; import type { FieldBuilder } from "./builder.js"; export type Constructor = new (...args: any[]) => T; type PrimitiveStringToType = T extends "string" ? string : T extends "number" | "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" | "int64" | "uint64" | "float32" | "float64" ? number : T extends "bigint64" | "biguint64" ? bigint : T extends "boolean" ? boolean : T; /** * What the decoder callbacks accept as "a collection": the public shape, which * a plain array satisfies too — `@type([X]) items: X[]` is a common way to * declare a field. {@link Collection} is the runtime contract on top of it. */ export interface CollectionLike { [Symbol.iterator](): IterableIterator; forEach(callback: Function): void; entries(): IterableIterator<[K, V]>; } export interface Collection extends CollectionLike { /** See {@link $resyncPrune} — every collection kind must declare its resync-sweep semantics. */ [$resyncPrune](visited: Set, prune: (value: V, identity: number | string) => void, keep: (value: V) => void): void; } export type InferValueType = T extends FieldBuilder ? V : T extends "string" ? string : T extends "number" ? number : T extends "int8" ? number : T extends "uint8" ? number : T extends "int16" ? number : T extends "uint16" ? number : T extends "int32" ? number : T extends "uint32" ? number : T extends "int64" ? number : T extends "uint64" ? number : T extends "float32" ? number : T extends "float64" ? number : T extends "bigint64" ? bigint : T extends "biguint64" ? bigint : T extends "boolean" ? boolean : T extends { type: infer ChildType extends PrimitiveType; } ? InferValueType : T extends { type: infer ChildType extends Constructor; } ? InstanceType : T extends { type: Array; } ? (ChildType extends Record ? ChildType[keyof ChildType][] : ChildType[]) : T extends { type: { map: infer ChildType; }; } ? (ChildType extends Record ? MapSchema : MapSchema) : T extends { type: { set: infer ChildType; }; } ? (ChildType extends Record ? SetSchema : SetSchema) : T extends { type: { collection: infer ChildType; }; } ? (ChildType extends Record ? CollectionSchema : CollectionSchema) : T extends { type: { stream: infer ChildType extends Constructor; }; } ? StreamSchema> : T extends { type: { stream: infer ChildType; }; } ? StreamSchema : T extends { type: infer ChildType; } ? (ChildType extends Record ? ChildType[keyof ChildType] : ChildType) : T extends Array ? ArraySchema> : T extends Array ? (ChildType extends Record ? ArraySchema : ArraySchema>) : T extends { array: infer ChildType extends Constructor; } ? ArraySchema> : T extends { array: infer ChildType; } ? (ChildType extends Record ? ArraySchema : ArraySchema>) : T extends { map: infer ChildType extends Constructor; } ? MapSchema> : T extends { map: infer ChildType; } ? (ChildType extends Record ? MapSchema : MapSchema>) : T extends { set: infer ChildType extends Constructor; } ? SetSchema> : T extends { set: infer ChildType extends RawPrimitiveType; } ? SetSchema> : T extends { set: infer ChildType; } ? (ChildType extends Record ? SetSchema : SetSchema) : T extends { collection: infer ChildType extends Constructor; } ? CollectionSchema> : T extends { collection: infer ChildType extends RawPrimitiveType; } ? CollectionSchema> : T extends { collection: infer ChildType; } ? (ChildType extends Record ? CollectionSchema : CollectionSchema) : T extends { stream: infer ChildType extends Constructor; } ? StreamSchema> : T extends { stream: infer ChildType; } ? StreamSchema : T extends Constructor ? InstanceType : T extends Record ? T[keyof T] : T extends PrimitiveType ? T : never; /** * Codecs that can carry a `T` — {@link InferValueType} run backwards, derived * from it so the two can't drift. Constrains the element refinement in * `t.array("uint8")`; `never` (an uncallable overload) when no codec * decodes into `T`. * * `[T] extends [...]` is deliberate: a distributive check would let a mixed * union like `string | number` match on either half. */ export type CodecFor = { [K in RawPrimitiveType]: [T] extends [InferValueType] ? K : never; }[RawPrimitiveType]; type IsOptionalBuilderKey = T[K] extends FieldBuilder ? O : false; export type InferSchemaInstanceType = { -readonly [K in keyof T as IsOptionalBuilderKey extends true ? never : K]-?: T[K] extends FieldBuilder ? InferValueType : T[K] extends (...args: any[]) => any ? (T[K] extends new (...args: any[]) => any ? InferValueType : T[K]) : InferValueType; } & { -readonly [K in keyof T as IsOptionalBuilderKey extends true ? K : never]?: T[K] extends FieldBuilder ? V : never; } & Schema; type DataKey = K extends keyof Schema ? never : T[K] extends Function ? never : K; export type NonFunctionPropNames = { [K in keyof T]-?: DataKey; }[keyof T]; export type NonFunctionNonPrimitivePropNames = { [K in keyof T]-?: [DataKey] extends [never] ? never : T[K] extends number | string | boolean ? never : K; }[keyof T]; type ToJSONValue = U extends Schema ? ToJSON : PrimitiveStringToType; type ToJSONField = X extends MapSchema ? Record> : X extends Map ? Record> : X extends ArraySchema ? ToJSONValue[] : X extends SetSchema ? ToJSONValue[] : X extends CollectionSchema ? ToJSONValue[] : X extends Schema ? ToJSON : X; type IsOptionalKey = undefined extends {} ? ({} extends Pick ? true : false) : (undefined extends T[K] ? true : false); export type ToJSON = { -readonly [K in keyof T as IsOptionalKey extends true ? never : DataKey]-?: ToJSONField; } & { -readonly [K in keyof T as IsOptionalKey extends true ? DataKey : never]?: ToJSONField>; }; /** * The plain DATA shape of a Schema instance type `T`: its synchronized fields * with all `Schema` machinery stripped (`assign`, `clone`, `toJSON`, the * change-tracking state, the internal symbol keys, …), so a plain object literal * satisfies it. Field types — including narrowed primitives like * `t.int8<-1 | 0 | 1>()` — are preserved exactly. * * Use it to type code that operates on schema-shaped *plain objects* rather than * decoded instances: deterministic simulation / physics steps, synthesized or * buffered input commands, plain DTOs, etc. * * ```ts * function applyInput(state: Player, cmd: Data) { … } * applyInput(player, { moveX: 1, jump: false, dt }); // plain literal — OK * ``` * * Unlike {@link ToJSON} (a recursive *serialization* shape), this is a flat * structural projection: nested Schema / collection fields keep their instance * types. */ export type Data = Omit; export type IsNever = [T] extends [never] ? true : false; /** * Type helper for .assign() method - allows assigning values in a flexible way * - Primitives can be assigned directly * - Schema instances can be assigned from plain objects or Schema instances * - Collections can be assigned from their JSON representations * * Keys filter through `DataKey` in the `as` clause — see THE RULE above. */ export type AssignableProps = { -readonly [K in keyof T as DataKey]?: AssignableValue; }; /** * Value-level assignment shape shared by `AssignableProps` and * `BuilderInitProps`. Captures the "you can pass the real instance, or the * plain-object / array shape" pattern. */ export type AssignableValue = V extends MapSchema ? MapSchema | Record) : U> : V extends ArraySchema ? ArraySchema | (U extends Schema ? (U | AssignableProps)[] : U[]) : V extends SetSchema ? SetSchema | Set | (U extends Schema ? (U | AssignableProps)[] : U[]) : V extends CollectionSchema ? CollectionSchema | (U extends Schema ? (U | AssignableProps)[] : U[]) : V extends Schema ? V | AssignableProps : V; export type RefHasDefault = C extends { prototype: { initialize(...args: infer P): any; }; } ? (P extends readonly [] ? true : false) : true; type FieldValue = F extends FieldBuilder ? V : F extends new (...args: any[]) => infer I ? (I extends Schema ? I : never) : never; type KeyClass = T[K] extends FieldBuilder ? (D extends true ? "optional" : O extends true ? "optional" : "required") : T[K] extends new (...args: any[]) => Schema ? (RefHasDefault extends true ? "optional" : "required") : "none"; /** * Constructor/init-props type for a schema() fields map. Required fields * (primitives without `.default()` or `.optional()`, and Schema refs with * non-zero-arg `initialize()`) are `:`; everything else is `?:`. * * Split by `KeyClass` in the `as` clause — see THE RULE above. `-?` matters * under `strictNullChecks: false`: an optional key in the fields map still * classifies as "required" there, and would otherwise inherit the `?`. */ export type BuilderInitProps = { -readonly [K in keyof T as KeyClass extends "required" ? K : never]-?: AssignableValue>; } & { -readonly [K in keyof T as KeyClass extends "optional" ? K : never]?: AssignableValue, undefined>>; }; export {};