From d83120a45f32dcf6a8e9261e62578f4711955325 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaras=C5=82a=C5=AD=20Viktor=C4=8Dyk?= <ugzuzg@gmail.com>
Date: Sun, 26 Jan 2025 09:54:10 +0100
Subject: [PATCH] feat: switch DataFrame generic to its schema

---
 __tests__/type.test.ts   |  78 ++++++++++++
 package.json             |   1 +
 polars/dataframe.ts      | 256 +++++++++++++++++++++++----------------
 polars/lazy/dataframe.ts | 150 ++++++++++++++---------
 polars/series/index.ts   |  27 ++++-
 yarn.lock                |   8 ++
 6 files changed, 350 insertions(+), 170 deletions(-)
 create mode 100644 __tests__/type.test.ts

diff --git a/__tests__/type.test.ts b/__tests__/type.test.ts
new file mode 100644
index 00000000..22d04217
--- /dev/null
+++ b/__tests__/type.test.ts
@@ -0,0 +1,78 @@
+import { expectType } from "ts-expect";
+
+import {
+  DataFrame,
+  type Float64,
+  type Int64,
+  type String as PlString,
+  type Series,
+} from "../polars";
+
+describe("type tests", () => {
+  it("is cleaned up later", () => {
+    const a = null as unknown as DataFrame<{
+      id: Int64;
+      age: Int64;
+      name: PlString;
+    }>;
+    const b = null as unknown as DataFrame<{
+      id: Int64;
+      age: Int64;
+      fl: Float64;
+    }>;
+    expectType<Series<Int64, "age">>(a.getColumn("age"));
+    expectType<
+      (Series<Int64, "id"> | Series<Int64, "age"> | Series<PlString, "name">)[]
+    >(a.getColumns());
+    expectType<DataFrame<{ id: Int64; age: Int64 }>>(a.drop("name"));
+    expectType<DataFrame<{ id: Int64 }>>(a.drop(["name", "age"]));
+    expectType<DataFrame<{ id: Int64 }>>(a.drop("name", "age"));
+    // expectType<Series<Int64, "age">>(a.age);
+    expectType<
+      DataFrame<{
+        id: Int64;
+        age: Int64;
+        age_right: Int64;
+        name: PlString;
+        fl: Float64;
+      }>
+    >(a.join(b, { on: ["id"] }));
+    expectType<
+      DataFrame<{
+        id: Int64;
+        age: Int64;
+        ageRight: Int64;
+        name: PlString;
+        fl: Float64;
+      }>
+    >(a.join(b, { on: ["id"], suffix: "Right" }));
+    expectType<
+      DataFrame<{
+        id: Int64;
+        id_right: Int64;
+        age: Int64;
+        name: PlString;
+        fl: Float64;
+      }>
+    >(a.join(b, { leftOn: "id", rightOn: ["age"] }));
+    expectType<
+      DataFrame<{
+        id: Int64;
+        id_right: Int64;
+        age: Int64;
+        age_right: Int64;
+        name: PlString;
+        fl: Float64;
+      }>
+    >(a.join(b, { how: "cross" }));
+  });
+
+  it("folds", () => {
+    const df = DataFrame({
+      a: [2, 1, 3],
+      b: [1, 2, 3],
+      c: [1.0, 2.0, 3.0],
+    });
+    expectType<Series<Float64, string>>(df.fold((s1, s2) => s1.plus(s2)));
+  });
+});
diff --git a/package.json b/package.json
index 3cdfbf71..b3d39c73 100644
--- a/package.json
+++ b/package.json
@@ -62,6 +62,7 @@
     "chance": "^1.1.12",
     "jest": "^29.7.0",
     "source-map-support": "^0.5.21",
+    "ts-expect": "^1.3.0",
     "ts-jest": "^29.2.5",
     "ts-node": "^10.9.2",
     "typedoc": "^0.27.3",
diff --git a/polars/dataframe.ts b/polars/dataframe.ts
index b62bc7a4..4d417fe9 100644
--- a/polars/dataframe.ts
+++ b/polars/dataframe.ts
@@ -184,6 +184,41 @@ interface WriteMethods {
   writeAvro(destination: string | Writable, options?: WriteAvroOptions): void;
 }
 
+export type Schema = Record<string, DataType>;
+type SchemaToSeriesRecord<T extends Record<string, DataType>> = {
+  [K in keyof T]: K extends string ? Series<T[K], K> : never;
+};
+
+type ExtractJoinKeys<T> = T extends string[] ? T[number] : T;
+type ExtractSuffix<T extends JoinOptions> = T extends { suffix: infer Suffix }
+  ? Suffix
+  : "_right";
+export type JoinSchemas<
+  S1 extends Schema,
+  S2 extends Schema,
+  Opt extends JoinOptions,
+> = Simplify<
+  {
+    [K1 in keyof S1]: S1[K1];
+  } & {
+    [K2 in Exclude<keyof S2, keyof S1>]: K2 extends keyof S1 ? never : S2[K2];
+  } & {
+    [K_SUFFIXED in keyof S1 &
+      Exclude<
+        keyof S2,
+        Opt extends { how: "cross" }
+          ? never
+          : Opt extends Pick<JoinOptions, "on">
+            ? ExtractJoinKeys<Opt["on"]>
+            : Opt extends Pick<JoinOptions, "leftOn" | "rightOn">
+              ? ExtractJoinKeys<Opt["rightOn"]>
+              : never
+      > as `${K_SUFFIXED extends string ? K_SUFFIXED : never}${ExtractSuffix<Opt>}`]: K_SUFFIXED extends string
+      ? S2[K_SUFFIXED]
+      : never;
+  }
+>;
+
 /**
  * A DataFrame is a two-dimensional data structure that represents data as a table
  * with rows and columns.
@@ -255,10 +290,9 @@ interface WriteMethods {
  * ╰─────┴─────┴─────╯
  * ```
  */
-export interface DataFrame<T extends Record<string, Series> = any>
-  extends Arithmetic<DataFrame<T>>,
-    Sample<DataFrame<T>>,
-    Arithmetic<DataFrame<T>>,
+export interface DataFrame<S extends Schema = Schema>
+  extends Arithmetic<DataFrame<S>>,
+    Sample<DataFrame<S>>,
     WriteMethods,
     Serialize,
     GroupByOps<RollingGroupBy> {
@@ -275,7 +309,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
   /**
    * Very cheap deep clone.
    */
-  clone(): DataFrame<T>;
+  clone(): DataFrame<S>;
   /**
    * __Summary statistics for a DataFrame.__
    *
@@ -346,14 +380,14 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────┴─────╯
    * ```
    */
-  drop<U extends string>(name: U): DataFrame<Simplify<Omit<T, U>>>;
+  drop<U extends string>(name: U): DataFrame<Simplify<Omit<S, U>>>;
   drop<const U extends string[]>(
     names: U,
-  ): DataFrame<Simplify<Omit<T, U[number]>>>;
+  ): DataFrame<Simplify<Omit<S, U[number]>>>;
   drop<U extends string, const V extends string[]>(
     name: U,
     ...names: V
-  ): DataFrame<Simplify<Omit<T, U | V[number]>>>;
+  ): DataFrame<Simplify<Omit<S, U | V[number]>>>;
   /**
    * __Return a new DataFrame where the null values are dropped.__
    *
@@ -379,9 +413,9 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * └─────┴─────┴─────┘
    * ```
    */
-  dropNulls(column: keyof T): DataFrame<T>;
-  dropNulls(columns: (keyof T)[]): DataFrame<T>;
-  dropNulls(...columns: (keyof T)[]): DataFrame<T>;
+  dropNulls(column: keyof S): DataFrame<S>;
+  dropNulls(columns: (keyof S)[]): DataFrame<S>;
+  dropNulls(...columns: (keyof S)[]): DataFrame<S>;
   /**
    * __Explode `DataFrame` to long format by exploding a column with Lists.__
    * ___
@@ -464,7 +498,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
 
    * @param other DataFrame to vertically add.
    */
-  extend(other: DataFrame<T>): DataFrame<T>;
+  extend(other: DataFrame<S>): DataFrame<S>;
   /**
    * Fill null/missing values by a filling strategy
    *
@@ -478,7 +512,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    *   - "one"
    * @returns DataFrame with None replaced with the filling strategy.
    */
-  fillNull(strategy: FillNullStrategy): DataFrame<T>;
+  fillNull(strategy: FillNullStrategy): DataFrame<S>;
   /**
    * Filter the rows in the DataFrame based on a predicate expression.
    * ___
@@ -517,7 +551,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * └─────┴─────┴─────┘
    * ```
    */
-  filter(predicate: any): DataFrame<T>;
+  filter(predicate: any): DataFrame<S>;
   /**
    * Find the index of a column by name.
    * ___
@@ -533,7 +567,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * 2
    * ```
    */
-  findIdxByName(name: keyof T): number;
+  findIdxByName(name: keyof S): number;
   /**
    * __Apply a horizontal reduction on a DataFrame.__
    *
@@ -590,7 +624,13 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ]
    * ```
    */
-  fold(operation: (s1: Series, s2: Series) => Series): Series;
+  fold<
+    D extends DataType,
+    F extends (
+      s1: SchemaToSeriesRecord<S>[keyof S] | Series<D>,
+      s2: SchemaToSeriesRecord<S>[keyof S],
+    ) => Series<D>,
+  >(operation: F): Series<D>;
   /**
    * Check if DataFrame is equal to other.
    * ___
@@ -637,7 +677,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * // column: pl.Series<Float64, "foo">
    * ```
    */
-  getColumn<U extends keyof T>(name: U): T[U];
+  getColumn<U extends keyof S>(name: U): SchemaToSeriesRecord<S>[U];
   getColumn(name: string): Series;
   /**
    * Get the DataFrame as an Array of Series.
@@ -658,7 +698,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * // columns: (pl.Series<Float64, "foo"> | pl.Series<Float64, "bar"> | pl.Series<Utf8, "ham">)[]
    * ```
    */
-  getColumns(): T[keyof T][];
+  getColumns(): SchemaToSeriesRecord<S>[keyof S][];
   /**
    * Start a groupby operation.
    * ___
@@ -705,7 +745,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────┴─────┴─────╯
    * ```
    */
-  head(length?: number): DataFrame<T>;
+  head(length?: number): DataFrame<S>;
   /**
    * Return a new DataFrame grown horizontally by stacking multiple Series to it.
    * @param columns - array of Series or DataFrame to stack
@@ -745,13 +785,12 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────┴─────┴─────┴───────╯
    * ```
    */
-  hstack<U extends Record<string, Series> = any>(
-    columns: DataFrame<U>,
-  ): DataFrame<Simplify<T & U>>;
+  hstack<S2 extends Schema = any>(
+    columns: DataFrame<S2>,
+  ): DataFrame<Simplify<S & S2>>;
   hstack<U extends Series[]>(
     columns: U,
-  ): DataFrame<Simplify<T & { [K in U[number] as K["name"]]: K }>>;
-  hstack(columns: Array<Series> | DataFrame): DataFrame;
+  ): DataFrame<Simplify<S & { [K in U[number] as K["name"]]: K }>>;
   hstack(columns: Array<Series> | DataFrame, inPlace?: boolean): void;
   /**
    * Insert a Series at a certain column index. This operation is in place.
@@ -762,7 +801,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
   /**
    * Interpolate intermediate values. The interpolation method is linear.
    */
-  interpolate(): DataFrame<T>;
+  interpolate(): DataFrame<S>;
   /**
    * Get a mask of all duplicated rows in this DataFrame.
    */
@@ -809,21 +848,24 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────┴─────┴─────┴───────╯
    * ```
    */
-  join(
-    other: DataFrame,
-    options: { on: ValueOrArray<string> } & Omit<
+  join<
+    S2 extends Schema,
+    const Opts extends { on: ValueOrArray<keyof S & keyof S2> } & Omit<
       JoinOptions,
       "leftOn" | "rightOn"
     >,
-  ): DataFrame;
-  join(
-    other: DataFrame,
-    options: {
-      leftOn: ValueOrArray<string>;
-      rightOn: ValueOrArray<string>;
+  >(other: DataFrame<S2>, options: Opts): DataFrame<JoinSchemas<S, S2, Opts>>;
+  join<
+    S2 extends Schema,
+    const Opts extends {
+      leftOn: ValueOrArray<keyof S>;
+      rightOn: ValueOrArray<keyof S2>;
     } & Omit<JoinOptions, "on">,
-  ): DataFrame;
-  join(other: DataFrame, options: { how: "cross"; suffix?: string }): DataFrame;
+  >(other: DataFrame<S2>, options: Opts): DataFrame<JoinSchemas<S, S2, Opts>>;
+  join<S2 extends Schema, const Opts extends { how: "cross"; suffix?: string }>(
+    other: DataFrame<S2>,
+    options: Opts,
+  ): DataFrame<JoinSchemas<S, S2, Opts>>;
 
   /**
    * Perform an asof join. This is similar to a left-join except that we
@@ -930,12 +972,12 @@ export interface DataFrame<T extends Record<string, Series> = any>
       forceParallel?: boolean;
     },
   ): DataFrame;
-  lazy(): LazyDataFrame;
+  lazy(): LazyDataFrame<S>;
   /**
    * Get first N rows as DataFrame.
    * @see {@link head}
    */
-  limit(length?: number): DataFrame<T>;
+  limit(length?: number): DataFrame<S>;
   map<ReturnT>(
     // TODO: strong types for the mapping function
     func: (row: any[], i: number, arr: any[][]) => ReturnT,
@@ -963,8 +1005,8 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────┴─────┴──────╯
    * ```
    */
-  max(): DataFrame<T>;
-  max(axis: 0): DataFrame<T>;
+  max(): DataFrame<S>;
+  max(axis: 0): DataFrame<S>;
   max(axis: 1): Series;
   /**
    * Aggregate the columns of this DataFrame to their mean value.
@@ -973,8 +1015,8 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * @param axis - either 0 or 1
    * @param nullStrategy - this argument is only used if axis == 1
    */
-  mean(): DataFrame<T>;
-  mean(axis: 0): DataFrame<T>;
+  mean(): DataFrame<S>;
+  mean(axis: 0): DataFrame<S>;
   mean(axis: 1): Series;
   mean(axis: 1, nullStrategy?: "ignore" | "propagate"): Series;
   /**
@@ -998,7 +1040,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────┴─────┴──────╯
    * ```
    */
-  median(): DataFrame<T>;
+  median(): DataFrame<S>;
   /**
    * Unpivot a DataFrame from wide to long format.
    * ___
@@ -1055,8 +1097,8 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────┴─────┴──────╯
    * ```
    */
-  min(): DataFrame<T>;
-  min(axis: 0): DataFrame<T>;
+  min(): DataFrame<S>;
+  min(axis: 0): DataFrame<S>;
   min(axis: 1): Series;
   /**
    * Get number of chunks used by the ChunkedArrays of this DataFrame.
@@ -1084,13 +1126,13 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ```
    */
   nullCount(): DataFrame<{
-    [K in keyof T]: Series<JsToDtype<number>, K & string>;
+    [K in keyof S]: JsToDtype<number>;
   }>;
   partitionBy(
     cols: string | string[],
     stable?: boolean,
     includeKey?: boolean,
-  ): DataFrame<T>[];
+  ): DataFrame<S>[];
   partitionBy<T>(
     cols: string | string[],
     stable: boolean,
@@ -1208,13 +1250,13 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────┴─────┴──────╯
    * ```
    */
-  quantile(quantile: number): DataFrame<T>;
+  quantile(quantile: number): DataFrame<S>;
   /**
    * __Rechunk the data in this DataFrame to a contiguous allocation.__
    *
    * This will make sure all subsequent operations have optimal and predictable performance.
    */
-  rechunk(): DataFrame<T>;
+  rechunk(): DataFrame<S>;
   /**
    * __Rename column names.__
    * ___
@@ -1246,9 +1288,9 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰───────┴─────┴─────╯
    * ```
    */
-  rename<const U extends Partial<Record<keyof T, string>>>(
+  rename<const U extends Partial<Record<keyof S, string>>>(
     mapping: U,
-  ): DataFrame<{ [K in keyof T as U[K] extends string ? U[K] : K]: T[K] }>;
+  ): DataFrame<{ [K in keyof S as U[K] extends string ? U[K] : K]: S[K] }>;
   rename(mapping: Record<string, string>): DataFrame;
   /**
    * Replace a column at an index location.
@@ -1333,7 +1375,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * // }
    * ```
    */
-  get schema(): { [K in keyof T]: T[K]["dtype"] };
+  get schema(): S;
   /**
    * Select columns from this DataFrame.
    * ___
@@ -1368,8 +1410,8 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * └─────┘
    * ```
    */
-  select<U extends keyof T>(...columns: U[]): DataFrame<{ [P in U]: T[P] }>;
-  select(...columns: ExprOrString[]): DataFrame<T>;
+  select<U extends keyof S>(...columns: U[]): DataFrame<{ [P in U]: S[P] }>;
+  select(...columns: ExprOrString[]): DataFrame<S>;
   /**
    * Shift the values by a given period and fill the parts that will be empty due to this operation
    * with `Nones`.
@@ -1410,8 +1452,8 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * └──────┴──────┴──────┘
    * ```
    */
-  shift(periods: number): DataFrame<T>;
-  shift({ periods }: { periods: number }): DataFrame<T>;
+  shift(periods: number): DataFrame<S>;
+  shift({ periods }: { periods: number }): DataFrame<S>;
   /**
    * Shift the values by a given period and fill the parts that will be empty due to this operation
    * with the result of the `fill_value` expression.
@@ -1441,15 +1483,18 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * └─────┴─────┴─────┘
    * ```
    */
-  shiftAndFill(n: number, fillValue: number): DataFrame<T>;
+  shiftAndFill(n: number, fillValue: number): DataFrame<S>;
   shiftAndFill({
     n,
     fillValue,
-  }: { n: number; fillValue: number }): DataFrame<T>;
+  }: {
+    n: number;
+    fillValue: number;
+  }): DataFrame<S>;
   /**
    * Shrink memory usage of this DataFrame to fit the exact capacity needed to hold the data.
    */
-  shrinkToFit(): DataFrame<T>;
+  shrinkToFit(): DataFrame<S>;
   shrinkToFit(inPlace: true): void;
   shrinkToFit({ inPlace }: { inPlace: true }): void;
   /**
@@ -1478,8 +1523,8 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * └─────┴─────┴─────┘
    * ```
    */
-  slice({ offset, length }: { offset: number; length: number }): DataFrame<T>;
-  slice(offset: number, length: number): DataFrame<T>;
+  slice({ offset, length }: { offset: number; length: number }): DataFrame<S>;
+  slice(offset: number, length: number): DataFrame<S>;
   /**
    * Sort the DataFrame by column.
    * ___
@@ -1494,7 +1539,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
     descending?: boolean,
     nullsLast?: boolean,
     maintainOrder?: boolean,
-  ): DataFrame<T>;
+  ): DataFrame<S>;
   sort({
     by,
     reverse, // deprecated
@@ -1505,7 +1550,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
     reverse?: boolean; // deprecated
     nullsLast?: boolean;
     maintainOrder?: boolean;
-  }): DataFrame<T>;
+  }): DataFrame<S>;
   sort({
     by,
     descending,
@@ -1515,7 +1560,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
     descending?: boolean;
     nullsLast?: boolean;
     maintainOrder?: boolean;
-  }): DataFrame<T>;
+  }): DataFrame<S>;
   /**
    * Aggregate the columns of this DataFrame to their standard deviation value.
    * ___
@@ -1537,7 +1582,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────┴─────┴──────╯
    * ```
    */
-  std(): DataFrame<T>;
+  std(): DataFrame<S>;
   /**
    * Aggregate the columns of this DataFrame to their mean value.
    * ___
@@ -1545,8 +1590,8 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * @param axis - either 0 or 1
    * @param nullStrategy - this argument is only used if axis == 1
    */
-  sum(): DataFrame<T>;
-  sum(axis: 0): DataFrame<T>;
+  sum(): DataFrame<S>;
+  sum(axis: 0): DataFrame<S>;
   sum(axis: 1): Series;
   sum(axis: 1, nullStrategy?: "ignore" | "propagate"): Series;
   /**
@@ -1596,7 +1641,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────────┴─────╯
    * ```
    */
-  tail(length?: number): DataFrame<T>;
+  tail(length?: number): DataFrame<S>;
   /**
    * Converts dataframe object into row oriented javascript objects
    * @example
@@ -1610,7 +1655,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ```
    * @category IO
    */
-  toRecords(): { [K in keyof T]: DTypeToJs<T[K]["dtype"]> | null }[];
+  toRecords(): { [K in keyof S]: DTypeToJs<S[K]> | null }[];
 
   /**
    * compat with `JSON.stringify`
@@ -1640,8 +1685,8 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ```
    * @category IO
    */
-  toObject(): { [K in keyof T]: DTypeToJs<T[K]["dtype"] | null>[] };
-  toSeries(index?: number): T[keyof T];
+  toObject(): { [K in keyof S]: DTypeToJs<S[K] | null>[] };
+  toSeries(index?: number): SchemaToSeriesRecord<S>[keyof S];
   toString(): string;
   /**
    *  Convert a ``DataFrame`` to a ``Series`` of type ``Struct``
@@ -1753,12 +1798,12 @@ export interface DataFrame<T extends Record<string, Series> = any>
     maintainOrder?: boolean,
     subset?: ColumnSelection,
     keep?: "first" | "last",
-  ): DataFrame<T>;
+  ): DataFrame<S>;
   unique(opts: {
     maintainOrder?: boolean;
     subset?: ColumnSelection;
     keep?: "first" | "last";
-  }): DataFrame<T>;
+  }): DataFrame<S>;
   /**
       Decompose a struct into its fields. The fields will be inserted in to the `DataFrame` on the
       location of the `struct` type.
@@ -1818,7 +1863,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────┴─────┴──────╯
    * ```
    */
-  var(): DataFrame<T>;
+  var(): DataFrame<S>;
   /**
    * Grow this DataFrame vertically by stacking a DataFrame to it.
    * @param df - DataFrame to stack.
@@ -1851,16 +1896,14 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * ╰─────┴─────┴─────╯
    * ```
    */
-  vstack(df: DataFrame<T>): DataFrame<T>;
+  vstack(df: DataFrame<S>): DataFrame<S>;
   /**
    * Return a new DataFrame with the column added or replaced.
    * @param column - Series, where the name of the Series refers to the column in the DataFrame.
    */
   withColumn<SeriesTypeT extends DataType, SeriesNameT extends string>(
     column: Series<SeriesTypeT, SeriesNameT>,
-  ): DataFrame<
-    Simplify<T & { [K in SeriesNameT]: Series<SeriesTypeT, SeriesNameT> }>
-  >;
+  ): DataFrame<Simplify<S & { [K in SeriesNameT]: SeriesTypeT }>>;
   withColumn(column: Series | Expr): DataFrame;
   withColumns(...columns: (Expr | Series)[]): DataFrame;
   /**
@@ -1868,16 +1911,16 @@ export interface DataFrame<T extends Record<string, Series> = any>
    * @param existingName
    * @param newName
    */
-  withColumnRenamed<Existing extends keyof T, New extends string>(
+  withColumnRenamed<Existing extends keyof S, New extends string>(
     existingName: Existing,
     replacement: New,
-  ): DataFrame<{ [K in keyof T as K extends Existing ? New : K]: T[K] }>;
+  ): DataFrame<{ [K in keyof S as K extends Existing ? New : K]: S[K] }>;
   withColumnRenamed(existing: string, replacement: string): DataFrame;
 
-  withColumnRenamed<Existing extends keyof T, New extends string>(opts: {
+  withColumnRenamed<Existing extends keyof S, New extends string>(opts: {
     existingName: Existing;
     replacement: New;
-  }): DataFrame<{ [K in keyof T as K extends Existing ? New : K]: T[K] }>;
+  }): DataFrame<{ [K in keyof S as K extends Existing ? New : K]: S[K] }>;
   withColumnRenamed(opts: { existing: string; replacement: string }): DataFrame;
   /**
    * Add a column at index 0 that counts the rows.
@@ -1885,7 +1928,7 @@ export interface DataFrame<T extends Record<string, Series> = any>
    */
   withRowCount(name?: string): DataFrame;
   /** @see {@link filter} */
-  where(predicate: any): DataFrame<T>;
+  where(predicate: any): DataFrame<S>;
   /**
     Upsample a DataFrame at a regular frequency.
 
@@ -1961,13 +2004,13 @@ shape: (7, 3)
     every: string,
     by?: string | string[],
     maintainOrder?: boolean,
-  ): DataFrame<T>;
+  ): DataFrame<S>;
   upsample(opts: {
     timeColumn: string;
     every: string;
     by?: string | string[];
     maintainOrder?: boolean;
-  }): DataFrame<T>;
+  }): DataFrame<S>;
 }
 
 function prepareOtherArg(anyValue: any): Series {
@@ -2025,11 +2068,11 @@ function mapPolarsTypeToJSONSchema(colType: DataType): string {
 /**
  * @ignore
  */
-export const _DataFrame = (_df: any): DataFrame => {
+export const _DataFrame = <S extends Schema>(_df: any): DataFrame<S> => {
   const unwrap = (method: string, ...args: any[]) => {
     return _df[method as any](...args);
   };
-  const wrap = (method, ...args): DataFrame => {
+  const wrap = (method, ...args): DataFrame<any> => {
     return _DataFrame(unwrap(method, ...args));
   };
 
@@ -2089,7 +2132,7 @@ export const _DataFrame = (_df: any): DataFrame => {
         "text/html": limited.toHTML(),
       };
     },
-    get schema() {
+    get schema(): any {
       return this.getColumns().reduce((acc, curr) => {
         acc[curr.name] = curr.dtype;
 
@@ -2100,7 +2143,7 @@ export const _DataFrame = (_df: any): DataFrame => {
       return wrap("clone");
     },
     describe() {
-      const describeCast = (df: DataFrame) => {
+      const describeCast = (df: DataFrame<S>) => {
         return DataFrame(
           df.getColumns().map((s) => {
             if (s.isNumeric() || s.isBoolean()) {
@@ -2116,7 +2159,7 @@ export const _DataFrame = (_df: any): DataFrame => {
         describeCast(this.min()),
         describeCast(this.max()),
         describeCast(this.median()),
-      ]);
+      ] as any);
       summary.insertAtIdx(
         0,
         Series("describe", ["mean", "std", "min", "max", "median"]),
@@ -2178,7 +2221,7 @@ export const _DataFrame = (_df: any): DataFrame => {
     findIdxByName(name) {
       return unwrap("findIdxByName", name);
     },
-    fold(fn: (s1, s2) => Series) {
+    fold(fn: (s1, s2) => any) {
       if (this.width === 1) {
         return this.toSeries(0);
       }
@@ -2228,7 +2271,7 @@ export const _DataFrame = (_df: any): DataFrame => {
         by,
       );
     },
-    upsample(opts, every?, by?, maintainOrder?) {
+    upsample(opts, every?, by?, maintainOrder?): any {
       let timeColumn;
       if (typeof opts === "string") {
         timeColumn = opts;
@@ -2282,7 +2325,7 @@ export const _DataFrame = (_df: any): DataFrame => {
     isDuplicated: () => _Series(_df.isDuplicated()) as any,
     isEmpty: () => _df.height === 0,
     isUnique: () => _Series(_df.isUnique()) as any,
-    join(other: DataFrame, options): DataFrame {
+    join(other, options): any {
       options = { how: "inner", ...options };
       const on = columnOrColumns(options.on);
       const how = options.how;
@@ -2309,7 +2352,7 @@ export const _DataFrame = (_df: any): DataFrame => {
         .joinAsof(other.lazy(), options as any)
         .collectSync();
     },
-    lazy: () => _LazyDataFrame(_df.lazy()),
+    lazy: () => _LazyDataFrame(_df.lazy()) as unknown as LazyDataFrame<S>,
     limit: (length = 5) => wrap("head", length),
     max(axis = 0) {
       if (axis === 1) {
@@ -2401,7 +2444,7 @@ export const _DataFrame = (_df: any): DataFrame => {
     rechunk() {
       return wrap("rechunk");
     },
-    rename(mapping) {
+    rename(mapping): any {
       const df = this.clone();
       for (const [column, new_col] of Object.entries(mapping)) {
         (df as any).inner().rename(column, new_col);
@@ -2462,7 +2505,7 @@ export const _DataFrame = (_df: any): DataFrame => {
       return wrap("select", columnOrColumnsStrict(selection as any));
     },
     shift: (opt) => wrap("shift", opt?.periods ?? opt),
-    shiftAndFill(n: any, fillValue?: number | undefined) {
+    shiftAndFill(n: any, fillValue?: number | undefined): any {
       if (typeof n === "number" && fillValue) {
         return _DataFrame(_df).lazy().shiftAndFill(n, fillValue).collectSync();
       }
@@ -2576,7 +2619,7 @@ export const _DataFrame = (_df: any): DataFrame => {
 
       return { data, schema: { fields } };
     },
-    toObject() {
+    toObject(): any {
       return this.getColumns().reduce((acc, curr) => {
         acc[curr.name] = curr.toArray();
 
@@ -2733,7 +2776,7 @@ export const _DataFrame = (_df: any): DataFrame => {
         .withColumns(columns)
         .collectSync({ noOptimization: true });
     },
-    withColumnRenamed(opt, replacement?) {
+    withColumnRenamed(opt, replacement?): any {
       if (typeof opt === "string") {
         return this.rename({ [opt]: replacement });
       }
@@ -2756,10 +2799,10 @@ export const _DataFrame = (_df: any): DataFrame => {
     divideBy: (other) => wrap("div", prepareOtherArg(other).inner()),
     multiplyBy: (other) => wrap("mul", prepareOtherArg(other).inner()),
     modulo: (other) => wrap("rem", prepareOtherArg(other).inner()),
-  } as DataFrame;
+  } as DataFrame<S>;
 
   return new Proxy(df, {
-    get(target: DataFrame, prop, receiver) {
+    get(target: DataFrame<S>, prop, receiver) {
       if (typeof prop === "string" && target.columns.includes(prop)) {
         return target.getColumn(prop);
       }
@@ -2768,7 +2811,7 @@ export const _DataFrame = (_df: any): DataFrame => {
       }
       return Reflect.get(target, prop, receiver);
     },
-    set(target: DataFrame, prop, receiver) {
+    set(target: DataFrame<S>, prop, receiver) {
       if (Series.isSeries(receiver)) {
         if (typeof prop === "string" && target.columns.includes(prop)) {
           const idx = target.columns.indexOf(prop);
@@ -2862,21 +2905,22 @@ export interface DataFrameConstructor extends Deserialize<DataFrame> {
     data: T1,
     options?: DataFrameOptions,
   ): DataFrame<{
-    [K in T1[number] as K["name"]]: K;
+    [K in T1[number] as K["name"]]: K["dtype"];
   }>;
   <T2 extends Record<string, ArrayLike<any>>>(
     data: T2,
     options?: DataFrameOptions,
   ): DataFrame<{
-    [K in keyof T2]: K extends string
-      ? Series<JsToDtype<T2[K][number]>, K>
-      : never;
+    [K in keyof T2]: K extends string ? JsToDtype<T2[K][number]> : never;
   }>;
   (data: any, options?: DataFrameOptions): DataFrame;
   isDataFrame(arg: any): arg is DataFrame;
 }
 
-function DataFrameConstructor(data?, options?): DataFrame {
+function DataFrameConstructor<S extends Schema = Schema>(
+  data?,
+  options?,
+): DataFrame<S> {
   if (!data) {
     return _DataFrame(objToDF({}));
   }
diff --git a/polars/lazy/dataframe.ts b/polars/lazy/dataframe.ts
index 4b9d3d8a..7f1d4388 100644
--- a/polars/lazy/dataframe.ts
+++ b/polars/lazy/dataframe.ts
@@ -1,4 +1,9 @@
-import { type DataFrame, _DataFrame } from "../dataframe";
+import {
+  type DataFrame,
+  type JoinSchemas,
+  type Schema,
+  _DataFrame,
+} from "../dataframe";
 import pli from "../internals/polars_internal";
 import type { Series } from "../series";
 import type { Deserialize, GroupByOps, Serialize } from "../shared_traits";
@@ -12,6 +17,7 @@ import {
   type ColumnSelection,
   type ColumnsOrExpr,
   type ExprOrString,
+  type Simplify,
   type ValueOrArray,
   columnOrColumnsStrict,
   selectionToExprList,
@@ -24,7 +30,9 @@ const inspect = Symbol.for("nodejs.util.inspect.custom");
 /**
  * Representation of a Lazy computation graph / query.
  */
-export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
+export interface LazyDataFrame<S extends Schema = Schema>
+  extends Serialize,
+    GroupByOps<LazyGroupBy> {
   /** @ignore */
   _ldf: any;
   [inspect](): string;
@@ -33,8 +41,8 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
   /**
    * Cache the result once the execution of the physical plan hits this node.
    */
-  cache(): LazyDataFrame;
-  clone(): LazyDataFrame;
+  cache(): LazyDataFrame<S>;
+  clone(): LazyDataFrame<S>;
   /**
    *
    * Collect into a DataFrame.
@@ -57,8 +65,8 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
    * @return DataFrame
    *
    */
-  collect(opts?: LazyOptions): Promise<DataFrame>;
-  collectSync(opts?: LazyOptions): DataFrame;
+  collect(opts?: LazyOptions): Promise<DataFrame<S>>;
+  collectSync(opts?: LazyOptions): DataFrame<S>;
   /**
    * A string representation of the optimized query plan.
    */
@@ -71,16 +79,21 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
    * Remove one or multiple columns from a DataFrame.
    * @param columns - column or list of columns to be removed
    */
-  drop(name: string): LazyDataFrame;
-  drop(names: string[]): LazyDataFrame;
-  drop(name: string, ...names: string[]): LazyDataFrame;
+  drop<U extends string>(name: U): LazyDataFrame<Simplify<Omit<S, U>>>;
+  drop<const U extends string[]>(
+    names: U,
+  ): LazyDataFrame<Simplify<Omit<S, U[number]>>>;
+  drop<U extends string, const V extends string[]>(
+    name: U,
+    ...names: V
+  ): LazyDataFrame<Simplify<Omit<S, U | V[number]>>>;
   /**
    * Drop rows with null values from this DataFrame.
    * This method only drops nulls row-wise if any single value of the row is null.
    */
-  dropNulls(column: string): LazyDataFrame;
-  dropNulls(columns: string[]): LazyDataFrame;
-  dropNulls(...columns: string[]): LazyDataFrame;
+  dropNulls(column: string): LazyDataFrame<S>;
+  dropNulls(columns: string[]): LazyDataFrame<S>;
+  dropNulls(...columns: string[]): LazyDataFrame<S>;
   /**
    * Explode lists to long format.
    */
@@ -110,16 +123,16 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
                 at any point without it being considered a breaking change.
    * 
    */
-  fetch(numRows?: number): Promise<DataFrame>;
-  fetch(numRows: number, opts: LazyOptions): Promise<DataFrame>;
+  fetch(numRows?: number): Promise<DataFrame<S>>;
+  fetch(numRows: number, opts: LazyOptions): Promise<DataFrame<S>>;
   /** Behaves the same as fetch, but will perform the actions synchronously */
-  fetchSync(numRows?: number): DataFrame;
-  fetchSync(numRows: number, opts: LazyOptions): DataFrame;
+  fetchSync(numRows?: number): DataFrame<S>;
+  fetchSync(numRows: number, opts: LazyOptions): DataFrame<S>;
   /**
    * Fill missing values
    * @param fillValue value to fill the missing values with
    */
-  fillNull(fillValue: string | number | Expr): LazyDataFrame;
+  fillNull(fillValue: string | number | Expr): LazyDataFrame<S>;
   /**
    * Filter the rows in the DataFrame based on a predicate expression.
    * @param predicate - Expression that evaluates to a boolean Series.
@@ -144,11 +157,11 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
    * └─────┴─────┴─────┘
    * ```
    */
-  filter(predicate: Expr | string): LazyDataFrame;
+  filter(predicate: Expr | string): LazyDataFrame<S>;
   /**
    * Get the first row of the DataFrame.
    */
-  first(): DataFrame;
+  first(): DataFrame<S>;
   /**
    * Start a groupby operation.
    */
@@ -161,7 +174,7 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
    * Consider using the `fetch` operation.
    * The `fetch` operation will truly load the first `n`rows lazily.
    */
-  head(length?: number): LazyDataFrame;
+  head(length?: number): LazyDataFrame<S>;
   inner(): any;
   /**
    *  __SQL like joins.__
@@ -200,26 +213,38 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
    * ╰─────┴─────┴─────┴───────╯
    * ```
    */
-  join(
-    other: LazyDataFrame,
-    joinOptions: { on: ValueOrArray<string | Expr> } & LazyJoinOptions,
-  ): LazyDataFrame;
-  join(
-    other: LazyDataFrame,
-    joinOptions: {
-      leftOn: ValueOrArray<string | Expr>;
-      rightOn: ValueOrArray<string | Expr>;
-    } & LazyJoinOptions,
-  ): LazyDataFrame;
-  join(
-    other: LazyDataFrame,
-    options: {
+  join<
+    S2 extends Schema,
+    const Opts extends { on: ValueOrArray<string | Expr> } & Omit<
+      LazyJoinOptions,
+      "leftOn" | "rightOn"
+    >,
+  >(
+    other: LazyDataFrame<S2>,
+    joinOptions: Opts,
+  ): LazyDataFrame<JoinSchemas<S, S2, Opts>>;
+  join<
+    S2 extends Schema,
+    const Opts extends {
+      leftOn: ValueOrArray<keyof S>;
+      rightOn: ValueOrArray<keyof S2>;
+    } & Omit<LazyJoinOptions, "on">,
+  >(
+    other: LazyDataFrame<S2>,
+    joinOptions: Opts,
+  ): LazyDataFrame<JoinSchemas<S, S2, Opts>>;
+  join<
+    S2 extends Schema,
+    const Opts extends {
       how: "cross";
       suffix?: string;
       allowParallel?: boolean;
       forceParallel?: boolean;
     },
-  ): LazyDataFrame;
+  >(
+    other: LazyDataFrame<S2>,
+    options: Opts,
+  ): LazyDataFrame<JoinSchemas<S, S2, Opts>>;
 
   /**
      * Perform an asof join. This is similar to a left-join except that we
@@ -333,23 +358,23 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
   /**
    * Get the last row of the DataFrame.
    */
-  last(): LazyDataFrame;
+  last(): LazyDataFrame<S>;
   /**
    * @see {@link head}
    */
-  limit(n?: number): LazyDataFrame;
+  limit(n?: number): LazyDataFrame<S>;
   /**
    * @see {@link DataFrame.max}
    */
-  max(): LazyDataFrame;
+  max(): LazyDataFrame<S>;
   /**
    * @see {@link DataFrame.mean}
    */
-  mean(): LazyDataFrame;
+  mean(): LazyDataFrame<S>;
   /**
    * @see {@link DataFrame.median}
    */
-  median(): LazyDataFrame;
+  median(): LazyDataFrame<S>;
   /**
    * @see {@link DataFrame.unpivot}
    */
@@ -357,43 +382,44 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
   /**
    * @see {@link DataFrame.min}
    */
-  min(): LazyDataFrame;
+  min(): LazyDataFrame<S>;
   /**
    * @see {@link DataFrame.quantile}
    */
-  quantile(quantile: number): LazyDataFrame;
+  quantile(quantile: number): LazyDataFrame<S>;
   /**
    * @see {@link DataFrame.rename}
    */
+  rename<const U extends Partial<Record<keyof S, string>>>(
+    mapping: U,
+  ): LazyDataFrame<{ [K in keyof S as U[K] extends string ? U[K] : K]: S[K] }>;
   rename(mapping: Record<string, string>): LazyDataFrame;
   /**
    * Reverse the DataFrame.
    */
-  reverse(): LazyDataFrame;
+  reverse(): LazyDataFrame<S>;
   /**
    * @see {@link DataFrame.select}
    */
+  select<U extends keyof S>(...columns: U[]): LazyDataFrame<{ [P in U]: S[P] }>;
   select(column: ExprOrString): LazyDataFrame;
   select(columns: ExprOrString[]): LazyDataFrame;
   select(...columns: ExprOrString[]): LazyDataFrame;
   /**
    * @see {@link DataFrame.shift}
    */
-  shift(periods: number): LazyDataFrame;
-  shift(opts: { periods: number }): LazyDataFrame;
+  shift(periods: number): LazyDataFrame<S>;
+  shift(opts: { periods: number }): LazyDataFrame<S>;
   /**
    * @see {@link DataFrame.shiftAndFill}
    */
-  shiftAndFill(n: number, fillValue: number): LazyDataFrame;
-  shiftAndFill(opts: {
-    n: number;
-    fillValue: number;
-  }): LazyDataFrame;
+  shiftAndFill(n: number, fillValue: number): LazyDataFrame<S>;
+  shiftAndFill(opts: { n: number; fillValue: number }): LazyDataFrame<S>;
   /**
    * @see {@link DataFrame.slice}
    */
-  slice(offset: number, length: number): LazyDataFrame;
-  slice(opts: { offset: number; length: number }): LazyDataFrame;
+  slice(offset: number, length: number): LazyDataFrame<S>;
+  slice(opts: { offset: number; length: number }): LazyDataFrame<S>;
   /**
    * @see {@link DataFrame.sort}
    */
@@ -402,26 +428,26 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
     descending?: ValueOrArray<boolean>,
     nullsLast?: boolean,
     maintainOrder?: boolean,
-  ): LazyDataFrame;
+  ): LazyDataFrame<S>;
   sort(opts: {
     by: ColumnsOrExpr;
     descending?: ValueOrArray<boolean>;
     nullsLast?: boolean;
     maintainOrder?: boolean;
-  }): LazyDataFrame;
+  }): LazyDataFrame<S>;
   /**
    * @see {@link DataFrame.std}
    */
-  std(): LazyDataFrame;
+  std(): LazyDataFrame<S>;
   /**
    * Aggregate the columns in the DataFrame to their sum value.
    */
-  sum(): LazyDataFrame;
+  sum(): LazyDataFrame<S>;
   /**
    * Get the last `n` rows of the DataFrame.
    * @see {@link DataFrame.tail}
    */
-  tail(length?: number): LazyDataFrame;
+  tail(length?: number): LazyDataFrame<S>;
   /**
    * compatibility with `JSON.stringify`
    */
@@ -437,16 +463,16 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
     maintainOrder?: boolean,
     subset?: ColumnSelection,
     keep?: "first" | "last",
-  ): LazyDataFrame;
+  ): LazyDataFrame<S>;
   unique(opts: {
     maintainOrder?: boolean;
     subset?: ColumnSelection;
     keep?: "first" | "last";
-  }): LazyDataFrame;
+  }): LazyDataFrame<S>;
   /**
    * Aggregate the columns in the DataFrame to their variance value.
    */
-  var(): LazyDataFrame;
+  var(): LazyDataFrame<S>;
   /**
    * Add or overwrite column in a DataFrame.
    * @param expr - Expression that evaluates to column.
@@ -459,6 +485,10 @@ export interface LazyDataFrame extends Serialize, GroupByOps<LazyGroupBy> {
    */
   withColumns(exprs: (Expr | Series)[]): LazyDataFrame;
   withColumns(...exprs: (Expr | Series)[]): LazyDataFrame;
+  withColumnRenamed<Existing extends keyof S, New extends string>(
+    existing: Existing,
+    replacement: New,
+  ): LazyDataFrame<{ [K in keyof S as K extends Existing ? New : K]: S[K] }>;
   withColumnRenamed(existing: string, replacement: string): LazyDataFrame;
   /**
    * Add a column at index 0 that counts the rows.
diff --git a/polars/series/index.ts b/polars/series/index.ts
index a1eb106f..ea5daca0 100644
--- a/polars/series/index.ts
+++ b/polars/series/index.ts
@@ -1,6 +1,7 @@
 import { DataFrame, _DataFrame } from "../dataframe";
 import { DTYPE_TO_FFINAME, DataType, type Optional } from "../datatypes";
 import type {
+  Bool,
   DTypeToJs,
   DTypeToJsLoose,
   DtypeToJsName,
@@ -139,11 +140,17 @@ export interface Series<T extends DataType = any, Name extends string = string>
   argSort({
     descending,
     nullsLast,
-  }: { descending?: boolean; nullsLast?: boolean }): Series<T, Name>;
+  }: {
+    descending?: boolean;
+    nullsLast?: boolean;
+  }): Series<T, Name>;
   argSort({
     reverse, // deprecated
     nullsLast,
-  }: { reverse?: boolean; nullsLast?: boolean }): Series<T, Name>;
+  }: {
+    reverse?: boolean;
+    nullsLast?: boolean;
+  }): Series<T, Name>;
   /**
    * __Rename this Series.__
    *
@@ -405,7 +412,7 @@ export interface Series<T extends DataType = any, Name extends string = string>
   /**
    * Check if this Series is a Boolean.
    */
-  isBoolean(): boolean;
+  isBoolean(): this is Series<Bool>;
   /**
    * Check if this Series is a DataTime.
    */
@@ -507,7 +514,19 @@ export interface Series<T extends DataType = any, Name extends string = string>
   /**
    * Check if this Series datatype is numeric.
    */
-  isNumeric(): boolean;
+  isNumeric(): this is Series<
+    | DataType.Int8
+    | DataType.Int16
+    | DataType.Int32
+    | DataType.Int64
+    | DataType.UInt8
+    | DataType.UInt16
+    | DataType.UInt32
+    | DataType.UInt64
+    | DataType.Float32
+    | DataType.Float64
+    | DataType.Decimal
+  >;
   /**
    * __Get mask of unique values.__
    * ___
diff --git a/yarn.lock b/yarn.lock
index cce09f53..01932fd0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3138,6 +3138,7 @@ __metadata:
     chance: "npm:^1.1.12"
     jest: "npm:^29.7.0"
     source-map-support: "npm:^0.5.21"
+    ts-expect: "npm:^1.3.0"
     ts-jest: "npm:^29.2.5"
     ts-node: "npm:^10.9.2"
     typedoc: "npm:^0.27.3"
@@ -3767,6 +3768,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"ts-expect@npm:^1.3.0":
+  version: 1.3.0
+  resolution: "ts-expect@npm:1.3.0"
+  checksum: 10/3f33ba17f9cdad550e9b12a23d78b19836d4fd5741da590c25426ea31ceb8f1765feeeeef2c529bab3e2350b5f9ce5fbbb207012db2c7c4b4b116d1b7fed7ec5
+  languageName: node
+  linkType: hard
+
 "ts-jest@npm:^29.2.5":
   version: 29.2.5
   resolution: "ts-jest@npm:29.2.5"