diff --git a/frontend/src/metabase-lib/Question.ts b/frontend/src/metabase-lib/Question.ts
index d7f3c790b21f36db917003469e7a157cddc233bf..c02244fdc5a0a6ef74feb399a81ad7d672458f87 100644
--- a/frontend/src/metabase-lib/Question.ts
+++ b/frontend/src/metabase-lib/Question.ts
@@ -5,7 +5,6 @@ import { assoc, assocIn, chain, dissoc, getIn } from "icepick";
 /* eslint-disable import/order */
 // NOTE: the order of these matters due to circular dependency issues
 import slugg from "slugg";
-import * as MLv2 from "cljs/metabase.lib.js";
 import StructuredQuery, {
   STRUCTURED_QUERY_TEMPLATE,
 } from "metabase-lib/queries/StructuredQuery";
@@ -85,7 +84,8 @@ import {
 } from "metabase-lib/Alert";
 import { getBaseDimensionReference } from "metabase-lib/references";
 
-import { Query } from "./types";
+import type { Query } from "./types";
+import * as ML from "./v2";
 
 export type QuestionCreatorOpts = {
   databaseId?: DatabaseId;
@@ -1341,7 +1341,7 @@ class QuestionInner {
     // cache the metadata provider we create for our metadata.
     if (metadata === this._metadata) {
       if (!this.__mlv2MetadataProvider) {
-        this.__mlv2MetadataProvider = MLv2.metadataProvider(
+        this.__mlv2MetadataProvider = ML.metadataProvider(
           this.databaseId(),
           metadata,
         );
@@ -1356,7 +1356,7 @@ class QuestionInner {
 
     if (!this.__mlv2Query) {
       this.__mlv2QueryMetadata = metadata;
-      this.__mlv2Query = MLv2.query(
+      this.__mlv2Query = ML.fromLegacyQuery(
         this.databaseId(),
         metadata,
         this.datasetQuery(),
@@ -1368,7 +1368,7 @@ class QuestionInner {
 
   generateQueryDescription() {
     const query = this._getMLv2Query();
-    return MLv2.suggestedName(query);
+    return ML.suggestedName(query);
   }
 
   getUrlWithParameters(parameters, parameterValues, { objectId, clean } = {}) {
diff --git a/frontend/src/metabase-lib/index.ts b/frontend/src/metabase-lib/index.ts
index 323435ea77eb11c2e8c54a7d2d75f5092dd5b8d2..765c1b00239ba1a9bf2a7cbd6bb689edf8d8d050 100644
--- a/frontend/src/metabase-lib/index.ts
+++ b/frontend/src/metabase-lib/index.ts
@@ -1,5 +1 @@
-// Note: only metabase-lib v2 exports should be added here
-
-export * from "./limit";
-export * from "./query";
-export * from "./types";
+export * from "./v2";
diff --git a/frontend/src/metabase-lib/metadata.ts b/frontend/src/metabase-lib/metadata.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0ba300ba3f8ee986fb3761e313e8b008a8154f56
--- /dev/null
+++ b/frontend/src/metabase-lib/metadata.ts
@@ -0,0 +1,16 @@
+import * as ML from "cljs/metabase.lib.js";
+import * as ML_MetadataCalculation from "cljs/metabase.lib.metadata.calculation";
+import type { DatabaseId } from "metabase-types/api";
+import type Metadata from "./metadata/Metadata";
+import type { Clause, MetadataProvider, Query } from "./types";
+
+export function metadataProvider(
+  databaseId: DatabaseId,
+  metadata: Metadata,
+): MetadataProvider {
+  return ML.metadataProvider(databaseId, metadata);
+}
+
+export function displayName(query: Query, clause: Clause): string {
+  return ML_MetadataCalculation.display_name(query, clause);
+}
diff --git a/frontend/src/metabase-lib/order.unit.spec.ts b/frontend/src/metabase-lib/order.unit.spec.ts
deleted file mode 100644
index 601afed8c752592f759e765b83b1423b5bf67700..0000000000000000000000000000000000000000
--- a/frontend/src/metabase-lib/order.unit.spec.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import _ from "underscore";
-
-import * as ML from "cljs/metabase.lib.js";
-import * as ML_MetadataCalculation from "cljs/metabase.lib.metadata.calculation";
-
-import * as SampleDatabase from "__support__/sample_database_fixture";
-
-const createMetadataProvider = () =>
-  ML.metadataProvider(
-    SampleDatabase.SAMPLE_DATABASE.id,
-    SampleDatabase.metadata,
-  );
-
-const createQuery = () => {
-  const query = ML.query(
-    SampleDatabase.SAMPLE_DATABASE.id,
-    createMetadataProvider(),
-    {
-      database: SampleDatabase.SAMPLE_DATABASE.id,
-      type: "query",
-      query: {
-        "source-table": SampleDatabase.ORDERS.id,
-      },
-    },
-  );
-
-  it("can create an MLv2 query", () => {
-    expect(query).toBeTruthy();
-    expect(ML.suggestedName(query)).toBe("Orders");
-  });
-
-  return query;
-};
-
-describe("orderableColumns", () => {
-  const query = createQuery();
-  const orderableColumns = ML.orderable_columns(query);
-
-  it("returns an array", () => {
-    expect(orderableColumns).toBeInstanceOf(Array);
-  });
-
-  describe("returns metadata for columns in the source table", () => {
-    it("contains ORDERS.ID", () => {
-      const ordersID = _.find(
-        orderableColumns,
-        ({ id }) => id === SampleDatabase.ORDERS.ID.id,
-      );
-
-      expect(ordersID).toEqual(
-        expect.objectContaining({
-          table_id: SampleDatabase.ORDERS.id,
-          name: "ID",
-          id: SampleDatabase.ORDERS.ID.id,
-          display_name: "ID",
-          base_type: "type/BigInteger",
-        }),
-      );
-    });
-  });
-
-  describe("returns metadata for columns in implicitly joinable tables", () => {
-    it("contains PRODUCTS.TITLE", () => {
-      const productsTitle = _.find(
-        orderableColumns,
-        ({ id }) => id === SampleDatabase.PRODUCTS.TITLE.id,
-      );
-
-      expect(productsTitle).toEqual(
-        expect.objectContaining({
-          table_id: SampleDatabase.PRODUCTS.id,
-          name: "TITLE",
-          id: SampleDatabase.PRODUCTS.TITLE.id,
-          display_name: "Title",
-          base_type: "type/Text",
-        }),
-      );
-    });
-  });
-});
-
-describe("add order by", () => {
-  const query = createQuery();
-
-  const orderBys = ML.order_bys(query);
-
-  it("should not have order bys yet", () => {
-    expect(orderBys).toBeNull();
-  });
-
-  const orderableColumns = ML.orderable_columns(query);
-  const productsTitle = _.find(
-    orderableColumns,
-    ({ id }) => id === SampleDatabase.PRODUCTS.TITLE.id,
-  );
-
-  it("should include PRODUCTS.TITLE in orderableColumns", () => {
-    expect(productsTitle).toBeTruthy();
-  });
-
-  it("should update the query", () => {
-    const updatedQuery = ML.order_by(query, productsTitle);
-    // This name isn't GREAT but it's ok for now, we can update this if we improve MLv2.
-    expect(ML.suggestedName(updatedQuery)).toBe(
-      "Orders, Sorted by Title ascending",
-    );
-
-    const updatedOrderBys = ML.order_bys(updatedQuery);
-
-    expect(updatedOrderBys).toHaveLength(1);
-    const orderBy = updatedOrderBys[0];
-
-    expect(ML_MetadataCalculation.display_name(query, orderBy)).toBe(
-      "Title ascending",
-    );
-  });
-});
diff --git a/frontend/src/metabase-lib/order_by.ts b/frontend/src/metabase-lib/order_by.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cad71031d30aba4986acdde503527e2d12e3d2e2
--- /dev/null
+++ b/frontend/src/metabase-lib/order_by.ts
@@ -0,0 +1,21 @@
+import * as ML from "cljs/metabase.lib.js";
+import type { Field } from "metabase-types/api";
+import type { OrderByClause, Query } from "./types";
+
+export function orderableColumns(query: Query): Field[] {
+  return ML.orderable_columns(query);
+}
+
+export function orderBys(query: Query): OrderByClause[] {
+  const result = ML.order_bys(query);
+  return result === null ? [] : result;
+}
+
+declare function OrderByFn(query: Query, field: Field): Query;
+declare function OrderByFn(
+  query: Query,
+  stageIndex: number,
+  field: Field,
+): Query;
+
+export const orderBy: typeof OrderByFn = ML.order_by;
diff --git a/frontend/src/metabase-lib/order_by.unit.spec.ts b/frontend/src/metabase-lib/order_by.unit.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7dc1a0905e023a570ef127546b5364cd7e9d38c2
--- /dev/null
+++ b/frontend/src/metabase-lib/order_by.unit.spec.ts
@@ -0,0 +1,62 @@
+import type { Field } from "metabase-types/api";
+import { createQuery, SAMPLE_DATABASE } from "./test-helpers";
+import * as ML from "./v2";
+
+describe("order by", () => {
+  describe("orderableColumns", () => {
+    const query = createQuery();
+    const columns = ML.orderableColumns(query);
+
+    it("returns metadata for columns in the source table", () => {
+      const ordersID = columns.find(
+        ({ id }) => id === SAMPLE_DATABASE.ORDERS.ID.id,
+      );
+
+      expect(ordersID).toEqual(
+        expect.objectContaining({
+          table_id: SAMPLE_DATABASE.ORDERS.id,
+          name: "ID",
+          id: SAMPLE_DATABASE.ORDERS.ID.id,
+          display_name: "ID",
+          base_type: "type/BigInteger",
+        }),
+      );
+    });
+
+    it("returns metadata for columns in implicitly joinable tables", () => {
+      const productsTitle = columns.find(
+        ({ id }) => id === SAMPLE_DATABASE.PRODUCTS.TITLE.id,
+      );
+
+      expect(productsTitle).toEqual(
+        expect.objectContaining({
+          table_id: SAMPLE_DATABASE.PRODUCTS.id,
+          name: "TITLE",
+          id: SAMPLE_DATABASE.PRODUCTS.TITLE.id,
+          display_name: "Title",
+          base_type: "type/Text",
+        }),
+      );
+    });
+  });
+
+  describe("add order by", () => {
+    const query = createQuery();
+
+    it("should handle no order by clauses", () => {
+      expect(ML.orderBys(query)).toHaveLength(0);
+    });
+
+    it("should update the query", () => {
+      const columns = ML.orderableColumns(query);
+      const productTitle = columns.find(
+        column => column.id === SAMPLE_DATABASE.PRODUCTS.TITLE.id,
+      );
+      const nextQuery = ML.orderBy(query, productTitle as Field);
+      const orderBys = ML.orderBys(nextQuery);
+
+      expect(orderBys).toHaveLength(1);
+      expect(ML.displayName(nextQuery, orderBys[0])).toBe("Title ascending");
+    });
+  });
+});
diff --git a/frontend/src/metabase-lib/queries/StructuredQuery.ts b/frontend/src/metabase-lib/queries/StructuredQuery.ts
index bf4e1d3941be4500c7c856d28dc3ce472cbbe317..320e05524e5751c3d3aef3c06aa0eb08a0c84fb1 100644
--- a/frontend/src/metabase-lib/queries/StructuredQuery.ts
+++ b/frontend/src/metabase-lib/queries/StructuredQuery.ts
@@ -49,7 +49,7 @@ import Dimension, {
 } from "metabase-lib/Dimension";
 import DimensionOptions from "metabase-lib/DimensionOptions";
 
-import * as MLv2 from "..";
+import * as ML from "../v2";
 import type { Limit, Query } from "../types";
 
 import Segment from "../metadata/Segment";
@@ -134,7 +134,7 @@ class StructuredQueryInner extends AtomicQuery {
   }
 
   private updateWithMLv2(nextQuery: Query) {
-    const nextMLv1Query = MLv2.toLegacyQuery(nextQuery);
+    const nextMLv1Query = ML.toLegacyQuery(nextQuery);
     return this.setDatasetQuery(nextMLv1Query);
   }
 
@@ -562,7 +562,7 @@ class StructuredQueryInner extends AtomicQuery {
 
   hasLimit() {
     const query = this.getMLv2Query();
-    return MLv2.hasLimit(query);
+    return ML.hasLimit(query);
   }
 
   hasFields() {
@@ -1105,12 +1105,12 @@ class StructuredQueryInner extends AtomicQuery {
   // LIMIT
   limit(): Limit {
     const query = this.getMLv2Query();
-    return MLv2.currentLimit(query);
+    return ML.currentLimit(query);
   }
 
   updateLimit(limit: Limit) {
     const query = this.getMLv2Query();
-    const nextQuery = MLv2.limit(query, limit);
+    const nextQuery = ML.limit(query, limit);
     return this.updateWithMLv2(nextQuery);
   }
 
diff --git a/frontend/src/metabase-lib/query.ts b/frontend/src/metabase-lib/query.ts
index f50bfcf8835dd18348897c8234592fb79cb541c3..d8bb3b3a2690df1992e70b6e9a36f299bdc88232 100644
--- a/frontend/src/metabase-lib/query.ts
+++ b/frontend/src/metabase-lib/query.ts
@@ -1,7 +1,19 @@
 import * as ML from "cljs/metabase.lib.js";
-import type { DatasetQuery } from "metabase-types/api";
-import type { Query } from "./types";
+import type { DatabaseId, DatasetQuery } from "metabase-types/api";
+import type { MetadataProvider, Query } from "./types";
+
+export function fromLegacyQuery(
+  databaseId: DatabaseId,
+  metadata: MetadataProvider,
+  datasetQuery: DatasetQuery,
+): Query {
+  return ML.query(databaseId, metadata, datasetQuery);
+}
 
 export function toLegacyQuery(query: Query): DatasetQuery {
   return ML.legacy_query(query);
 }
+
+export function suggestedName(query: Query): string {
+  return ML.suggestedName(query);
+}
diff --git a/frontend/src/metabase-lib/query.unit.spec.ts b/frontend/src/metabase-lib/query.unit.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0d4ba1eb1769b72ff061c3ba7f47c5452dbc9f27
--- /dev/null
+++ b/frontend/src/metabase-lib/query.unit.spec.ts
@@ -0,0 +1,14 @@
+import { createQuery, DEFAULT_QUERY } from "./test-helpers";
+import * as ML from "./v2";
+
+describe("query", () => {
+  it("should create a query", () => {
+    const query = createQuery();
+    expect(ML.toLegacyQuery(query)).toEqual(DEFAULT_QUERY);
+  });
+
+  it("should suggest a name", () => {
+    const query = createQuery();
+    expect(ML.suggestedName(query)).toBe("Orders");
+  });
+});
diff --git a/frontend/src/metabase-lib/test-helpers.ts b/frontend/src/metabase-lib/test-helpers.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a257b62e9971cdfbe305a6c995e52a6b67ff13d9
--- /dev/null
+++ b/frontend/src/metabase-lib/test-helpers.ts
@@ -0,0 +1,44 @@
+/* istanbul ignore file */
+
+import {
+  SAMPLE_DATABASE,
+  metadata as SAMPLE_METADATA,
+} from "__support__/sample_database_fixture";
+import type { DatabaseId, DatasetQuery } from "metabase-types/api";
+import type Metadata from "./metadata/Metadata";
+import * as ML from "./v2";
+
+export { SAMPLE_DATABASE, SAMPLE_METADATA };
+
+type MetadataProviderOpts = {
+  databaseId?: DatabaseId;
+  metadata?: Metadata;
+};
+
+function createMetadataProvider({
+  databaseId = SAMPLE_DATABASE.id,
+  metadata = SAMPLE_METADATA,
+}: MetadataProviderOpts = {}) {
+  return ML.metadataProvider(databaseId, metadata);
+}
+
+export const DEFAULT_QUERY: DatasetQuery = {
+  database: SAMPLE_DATABASE.id,
+  type: "query",
+  query: {
+    "source-table": SAMPLE_DATABASE.ORDERS.id,
+  },
+};
+
+type QueryOpts = MetadataProviderOpts & {
+  query?: DatasetQuery;
+};
+
+export function createQuery({
+  databaseId = SAMPLE_DATABASE.id,
+  metadata = SAMPLE_METADATA,
+  query = DEFAULT_QUERY,
+}: QueryOpts = {}) {
+  const metadataProvider = createMetadataProvider({ databaseId, metadata });
+  return ML.fromLegacyQuery(databaseId, metadataProvider, query);
+}
diff --git a/frontend/src/metabase-lib/types.ts b/frontend/src/metabase-lib/types.ts
index 32a483d5f306c8493b5d2cee0221554ee038b23c..8a71a53ddb47909c6a0b4f42f06fe5b5d2f236aa 100644
--- a/frontend/src/metabase-lib/types.ts
+++ b/frontend/src/metabase-lib/types.ts
@@ -5,4 +5,12 @@
 declare const Query: unique symbol;
 export type Query = unknown & { _opaque: typeof Query };
 
+declare const MetadataProvider: unique symbol;
+export type MetadataProvider = unknown & { _opaque: typeof MetadataProvider };
+
 export type Limit = number | null;
+
+declare const OrderByClause: unique symbol;
+export type OrderByClause = unknown & { _opaque: typeof OrderByClause };
+
+export type Clause = OrderByClause;
diff --git a/frontend/src/metabase-lib/v2.ts b/frontend/src/metabase-lib/v2.ts
new file mode 100644
index 0000000000000000000000000000000000000000..103f3ee7ac1cd8a97f49a07519c2cd5acf488034
--- /dev/null
+++ b/frontend/src/metabase-lib/v2.ts
@@ -0,0 +1,7 @@
+// Note: only metabase-lib v2 exports should be added here
+
+export * from "./metadata";
+export * from "./limit";
+export * from "./order_by";
+export * from "./query";
+export * from "./types";