Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 13 additions & 62 deletions src/driver/expo/ExpoDriver.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,11 @@
import type { DataSource } from "../../data-source/DataSource"
import { DriverPackageNotInstalledError, TypeORMError } from "../../error"
import { PlatformTools } from "../../platform/PlatformTools"
import type { QueryRunner } from "../../query-runner/QueryRunner"
import { AbstractSqliteDriver } from "../sqlite-abstract/AbstractSqliteDriver"
import type { ExpoDataSourceOptions } from "./ExpoDataSourceOptions"
import { ExpoQueryRunner } from "./ExpoQueryRunner"

// Node raises `MODULE_NOT_FOUND` when the requested module can't be resolved,
// but the same code also fires for transitive failures (a dependency of
// `expo-sqlite` that's missing, a `require` inside the module body). Node's
// error message has the shape:
//
// Cannot find module '<name>'
// Require stack:
// - /node_modules/<caller>/index.js
// - ...
//
// The stack lists the calling modules and can include `expo-sqlite` when the
// unresolved module is a transitive dependency — so matching the whole
// message would misreport such failures as "install expo-sqlite". Only the
// first line identifies the actually-missing module, so match against that.
const isExpoSqliteNotFoundError = (err: unknown): boolean => {
if (typeof err !== "object" || err === null) return false
const e = err as { code?: string; message?: string }
if (e.code !== "MODULE_NOT_FOUND") return false
if (typeof e.message !== "string") return false
const firstLine = e.message.split("\n", 1)[0]
return firstLine.startsWith("Cannot find module 'expo-sqlite'")
}

export class ExpoDriver extends AbstractSqliteDriver {
declare options: ExpoDataSourceOptions

Expand Down Expand Up @@ -56,50 +34,23 @@ export class ExpoDriver extends AbstractSqliteDriver {
}

/**
* Loads the `expo-sqlite` module when `options.driver` is not set. Extracted
* as a method so tests can stub the module resolution without depending on
* whether `expo-sqlite` happens to be installed in the workspace.
*
* A literal `require("expo-sqlite")` is used so Metro's static bundler picks
* the dependency up at build time — `PlatformTools.load()` is a throwing
* stub in the browser/React-Native build and cannot be used here.
*/
protected requireExpoSqlite(): unknown {
return require("expo-sqlite")
}

/**
* If driver dependency is not given explicitly, resolve it via
* `requireExpoSqlite()` and validate that the loaded module exposes the
* modern async API introduced in Expo SDK v52.
* If driver dependency is not given explicitly, then try to load it via "require".
*/
protected loadDependencies(): void {
if (this.options.driver) {
this.sqlite = this.options.driver
} else {
try {
this.sqlite = this.requireExpoSqlite()
} catch (err) {
if (isExpoSqliteNotFoundError(err)) {
throw new DriverPackageNotInstalledError(
"Expo SQLite",
"expo-sqlite",
)
}
throw err
}
try {
this.sqlite =
this.options.driver ?? PlatformTools.load("expo-sqlite")
} catch {
throw new DriverPackageNotInstalledError(
"Expo SQLite",
"expo-sqlite",
)
}

// Expo SDK v52 removed the legacy synchronous API. The modern async API
// exposes `openDatabaseAsync` as a function — anything else (missing,
// non-callable, non-object `driver`) means the user is on a pre-v52 SDK
// or has passed something that isn't the expo-sqlite module.
if (typeof this.sqlite?.openDatabaseAsync !== "function") {
const hint = this.options.driver
? "check that the provided `driver` exposes `openDatabaseAsync` — custom overrides must match the expo-sqlite v52+ surface"
: "upgrade to Expo SDK v52 or later, which ships the modern async SQLite API"
// The modern Expo SQLite API that TypeORM supports exposes `openDatabaseAsync` as a function
if (typeof this.sqlite.openDatabaseAsync !== "function") {
throw new TypeORMError(
`Legacy Expo SQLite driver is not supported — ${hint}.`,
`The provided Expo SQLite client is not supported. Please upgrade your Expo SDK to v52 or higher!`,
)
}
}
Expand Down
11 changes: 6 additions & 5 deletions src/driver/react-native/ReactNativeDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { DataSource } from "../../data-source/DataSource"
import { DriverPackageNotInstalledError } from "../../error"
import type { ColumnMetadata } from "../../metadata/ColumnMetadata"
import type { EntityMetadata } from "../../metadata/EntityMetadata"
import { PlatformTools } from "../../platform/PlatformTools"
import type { QueryRunner } from "../../query-runner/QueryRunner"
import { RdbmsSchemaBuilder } from "../../schema-builder/RdbmsSchemaBuilder"
import type { Table } from "../../schema-builder/table/Table"
Expand All @@ -17,9 +18,9 @@ import type { Driver } from "../Driver"
import type { ColumnType } from "../types/ColumnTypes"
import type { CteCapabilities } from "../types/CteCapabilities"
import type { DataTypeDefaults } from "../types/DataTypeDefaults"
import type { IsolationLevel } from "../types/IsolationLevel"
import type { MappedColumnTypes } from "../types/MappedColumnTypes"
import type { ReplicationMode } from "../types/ReplicationMode"
import type { IsolationLevel } from "../types/IsolationLevel"
import type { UpsertType } from "../types/UpsertType"
import type { ReactNativeDataSourceOptions } from "./ReactNativeDataSourceOptions"
import { ReactNativeQueryRunner } from "./ReactNativeQueryRunner"
Expand Down Expand Up @@ -1033,10 +1034,10 @@ export class ReactNativeDriver implements Driver {
*/
protected loadDependencies(): void {
try {
const sqlite =
this.options.driver ?? require("react-native-sqlite-storage")
this.sqlite = sqlite
} catch (e) {
this.sqlite =
this.options.driver ??
PlatformTools.load("react-native-sqlite-storage")
} catch {
throw new DriverPackageNotInstalledError(
"React-Native",
"react-native-sqlite-storage",
Expand Down
6 changes: 1 addition & 5 deletions src/driver/sap/SapDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@ import type { IsolationLevel } from "../types/IsolationLevel"
import type { UpsertType } from "../types/UpsertType"
import type { SapDataSourceOptions } from "./SapDataSourceOptions"
import { SapQueryRunner } from "./SapQueryRunner"
/**
* Organizes communication with SAP Hana DBMS.
*
* todo: looks like there is no built in support for connection pooling, we need to figure out something
*/

export class SapDriver implements Driver {
// -------------------------------------------------------------------------
// Static Properties
Expand Down
10 changes: 7 additions & 3 deletions src/driver/spanner/SpannerDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import type { Driver } from "../Driver"
import type { ColumnType } from "../types/ColumnTypes"
import type { CteCapabilities } from "../types/CteCapabilities"
import type { DataTypeDefaults } from "../types/DataTypeDefaults"
import type { IsolationLevel } from "../types/IsolationLevel"
import type { MappedColumnTypes } from "../types/MappedColumnTypes"
import type { ReplicationMode } from "../types/ReplicationMode"
import type { ReturningType } from "../types/ReturningType"
import type { IsolationLevel } from "../types/IsolationLevel"
import type { UpsertType } from "../types/UpsertType"
import type { SpannerDataSourceOptions } from "./SpannerDataSourceOptions"
import { SpannerQueryRunner } from "./SpannerQueryRunner"
Expand Down Expand Up @@ -440,7 +440,9 @@ export class SpannerDriver implements Driver {
if (value === null || value === undefined) return value

if (columnMetadata.type === "numeric") {
const lib = this.options.driver ?? PlatformTools.load("spanner")
const lib =
this.options.driver ??
PlatformTools.load("@google-cloud/spanner")
return lib.Spanner.numeric(value.toString())
} else if (columnMetadata.type === "date") {
return DateUtils.mixedDateToDateString(value, {
Expand Down Expand Up @@ -804,7 +806,9 @@ export class SpannerDriver implements Driver {
*/
protected loadDependencies(): void {
try {
const lib = this.options.driver ?? PlatformTools.load("spanner")
const lib =
this.options.driver ??
PlatformTools.load("@google-cloud/spanner")

if (this.options.credentials) {
this.spanner = new lib.Spanner({
Expand Down
21 changes: 17 additions & 4 deletions src/platform/BrowserPlatformTools.template
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,25 @@ export class PlatformTools {
* This operation only supports on node platform
*/
static load(name: string): any {
if (this.type === "browser")
throw new Error(
`This option/function is not supported in the browser environment. Failed operation: require("${name}").`,
const KNOWN_MODULES = [
// Expo
"expo-sqlite",
// React Native
"react-native-sqlite-storage",
// sql.js
"sql.js",
// redis
"redis",
"ioredis",
]

if (!KNOWN_MODULES.includes(name)) {
throw new TypeError(
`Invalid Package for PlatformTools.load: ${name}`,
)
}

return ""
return require(name)
}

/**
Expand Down
134 changes: 43 additions & 91 deletions src/platform/PlatformTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,105 +38,57 @@ export class PlatformTools {
* @returns the module
*/
static load(name: string): any {
const KNOWN_MODULES = [
// AWS Aurora Data API (PostgreSQL/MySQL)
"typeorm-aurora-data-api-driver",
// better-sqlite3
"better-sqlite3",
// Expo
"expo-sqlite",
// Google Cloud Spanner
"@google-cloud/spanner",
// Microsoft SQL Server
"mssql",
// MongoDB
"mongodb",
// MySQL / MariaDB
"mysql2",
// Oracle
"oracledb",
// PostgreSQL
"pg",
"pg-native",
"pg-query-stream",
// React Native
"react-native-sqlite-storage",
// SAP HANA
"@sap/hana-client",
"@sap/hana-client/extension/Stream",
// sql.js
"sql.js",
// redis
"redis",
"ioredis",
]

if (!KNOWN_MODULES.includes(name)) {
throw new TypeError(
`Invalid Package for PlatformTools.load: ${name}`,
)
}

// if name is not absolute or relative, then try to load package from the node_modules of the directory we are currently in
// this is useful when we are using typeorm package globally installed and it accesses drivers
// that are not installed globally

try {
// switch case to explicit require statements for webpack compatibility.
switch (name) {
/**
* spanner
*/
case "spanner":
return require("@google-cloud/spanner")

/**
* mongodb
*/
case "mongodb":
return require("mongodb")

/**
* hana
*/
case "@sap/hana-client":
return require("@sap/hana-client")

case "@sap/hana-client/extension/Stream":
return require("@sap/hana-client/extension/Stream")

/**
* mysql
*/
case "mysql2":
return require("mysql2")

/**
* oracle
*/
case "oracledb":
return require("oracledb")

/**
* postgres
*/
case "pg":
return require("pg")

case "pg-native":
return require("pg-native")

case "pg-query-stream":
return require("pg-query-stream")

case "typeorm-aurora-data-api-driver":
return require("typeorm-aurora-data-api-driver")

/**
* redis
*/
case "redis":
return require("redis")

case "ioredis":
return require("ioredis")

/**
* better-sqlite3
*/
case "better-sqlite3":
return require("better-sqlite3")

/**
* sql.js
*/
case "sql.js":
return require("sql.js")

/**
* sqlserver
*/
case "mssql":
return require("mssql")

/**
* react-native-sqlite
*/
case "react-native-sqlite-storage":
return require("react-native-sqlite-storage")
}
} catch (err) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require(name)
} catch {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require(
path.resolve(process.cwd() + "/node_modules/" + name),
)
}

// If nothing above matched and we get here, the package was not listed within PlatformTools
// and is an Invalid Package. To make it explicit that this is NOT the intended use case for
// PlatformTools.load - it's not just a way to replace `require` all willy-nilly - let's throw
// an error.
throw new TypeError(`Invalid Package for PlatformTools.load: ${name}`)
}

/**
Expand Down
Loading
Loading