• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

typeorm / typeorm / 15558174361

10 Jun 2025 11:24AM UTC coverage: 48.469% (-27.9%) from 76.328%
15558174361

Pull #11422

github

web-flow
Merge 817e6d91c into 86f12c922
Pull Request #11422: fix(tree-entity): closure junction table primary key definition should match parent table

5632 of 12872 branches covered (43.75%)

Branch coverage included in aggregate %.

12323 of 24172 relevant lines covered (50.98%)

35101.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.52
/src/driver/sqljs/SqljsDriver.ts
1
import { AbstractSqliteDriver } from "../sqlite-abstract/AbstractSqliteDriver"
8✔
2
import { SqljsConnectionOptions } from "./SqljsConnectionOptions"
3
import { SqljsQueryRunner } from "./SqljsQueryRunner"
8✔
4
import { QueryRunner } from "../../query-runner/QueryRunner"
5
import { DataSource } from "../../data-source/DataSource"
6
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
8✔
7
import { DriverOptionNotSetError } from "../../error/DriverOptionNotSetError"
8✔
8
import { PlatformTools } from "../../platform/PlatformTools"
8✔
9
import { EntityMetadata } from "../../metadata/EntityMetadata"
10
import { OrmUtils } from "../../util/OrmUtils"
8✔
11
import { ObjectLiteral } from "../../common/ObjectLiteral"
12
import { ReplicationMode } from "../types/ReplicationMode"
13
import { TypeORMError } from "../../error"
8✔
14

15
// This is needed to satisfy the typescript compiler.
16
interface Window {
17
    SQL: any
18
    localforage: any
19
}
20
declare let window: Window
21

22
export class SqljsDriver extends AbstractSqliteDriver {
8✔
23
    // The driver specific options.
24
    options: SqljsConnectionOptions
25

26
    // -------------------------------------------------------------------------
27
    // Constructor
28
    // -------------------------------------------------------------------------
29

30
    constructor(connection: DataSource) {
31
        super(connection)
×
32

33
        // If autoSave is enabled by user, location or autoSaveCallback have to be set
34
        // because either autoSave saves to location or calls autoSaveCallback.
35
        if (
×
36
            this.options.autoSave &&
×
37
            !this.options.location &&
38
            !this.options.autoSaveCallback
39
        ) {
40
            throw new DriverOptionNotSetError(`location or autoSaveCallback`)
×
41
        }
42

43
        // load sql.js package
44
        this.loadDependencies()
×
45
    }
46

47
    // -------------------------------------------------------------------------
48
    // Public Methods
49
    // -------------------------------------------------------------------------
50

51
    /**
52
     * Performs connection to the database.
53
     */
54
    async connect(): Promise<void> {
55
        this.databaseConnection = await this.createDatabaseConnection()
×
56
    }
57

58
    /**
59
     * Closes connection with database.
60
     */
61
    async disconnect(): Promise<void> {
62
        this.queryRunner = undefined
×
63
        this.databaseConnection.close()
×
64
    }
65

66
    /**
67
     * Creates a query runner used to execute database queries.
68
     */
69
    createQueryRunner(mode: ReplicationMode): QueryRunner {
70
        if (!this.queryRunner) this.queryRunner = new SqljsQueryRunner(this)
×
71

72
        return this.queryRunner
×
73
    }
74

75
    /**
76
     * Loads a database from a given file (Node.js), local storage key (browser) or array.
77
     * This will delete the current database!
78
     */
79
    async load(
80
        fileNameOrLocalStorageOrData: string | Uint8Array,
81
        checkIfFileOrLocalStorageExists: boolean = true,
×
82
    ): Promise<any> {
83
        if (typeof fileNameOrLocalStorageOrData === "string") {
×
84
            // content has to be loaded
85
            if (PlatformTools.type === "node") {
×
86
                // Node.js
87
                // fileNameOrLocalStorageOrData should be a path to the file
88
                if (PlatformTools.fileExist(fileNameOrLocalStorageOrData)) {
×
89
                    const database = PlatformTools.readFileSync(
×
90
                        fileNameOrLocalStorageOrData,
91
                    )
92
                    return this.createDatabaseConnectionWithImport(database)
×
93
                } else if (checkIfFileOrLocalStorageExists) {
×
94
                    throw new TypeORMError(
×
95
                        `File ${fileNameOrLocalStorageOrData} does not exist`,
96
                    )
97
                } else {
98
                    // File doesn't exist and checkIfFileOrLocalStorageExists is set to false.
99
                    // Therefore open a database without importing an existing file.
100
                    // File will be written on first write operation.
101
                    return this.createDatabaseConnectionWithImport()
×
102
                }
103
            } else {
104
                // browser
105
                // fileNameOrLocalStorageOrData should be a local storage / indexedDB key
106
                let localStorageContent = null
×
107
                if (this.options.useLocalForage) {
×
108
                    if (window.localforage) {
×
109
                        localStorageContent = await window.localforage.getItem(
×
110
                            fileNameOrLocalStorageOrData,
111
                        )
112
                    } else {
113
                        throw new TypeORMError(
×
114
                            `localforage is not defined - please import localforage.js into your site`,
115
                        )
116
                    }
117
                } else {
118
                    localStorageContent =
×
119
                        PlatformTools.getGlobalVariable().localStorage.getItem(
120
                            fileNameOrLocalStorageOrData,
121
                        )
122
                }
123

124
                if (localStorageContent != null) {
×
125
                    // localStorage value exists.
126
                    return this.createDatabaseConnectionWithImport(
×
127
                        JSON.parse(localStorageContent),
128
                    )
129
                } else if (checkIfFileOrLocalStorageExists) {
×
130
                    throw new TypeORMError(
×
131
                        `File ${fileNameOrLocalStorageOrData} does not exist`,
132
                    )
133
                } else {
134
                    // localStorage value doesn't exist and checkIfFileOrLocalStorageExists is set to false.
135
                    // Therefore open a database without importing anything.
136
                    // localStorage value will be written on first write operation.
137
                    return this.createDatabaseConnectionWithImport()
×
138
                }
139
            }
140
        } else {
141
            return this.createDatabaseConnectionWithImport(
×
142
                fileNameOrLocalStorageOrData,
143
            )
144
        }
145
    }
146

147
    /**
148
     * Saved the current database to the given file (Node.js), local storage key (browser) or
149
     * indexedDB key (browser with enabled useLocalForage option).
150
     * If no location path is given, the location path in the options (if specified) will be used.
151
     */
152
    async save(location?: string) {
153
        if (!location && !this.options.location) {
×
154
            throw new TypeORMError(
×
155
                `No location is set, specify a location parameter or add the location option to your configuration`,
156
            )
157
        }
158

159
        let path = ""
×
160
        if (location) {
×
161
            path = location
×
162
        } else if (this.options.location) {
×
163
            path = this.options.location
×
164
        }
165

166
        if (PlatformTools.type === "node") {
×
167
            try {
×
168
                const content = Buffer.from(this.databaseConnection.export())
×
169
                await PlatformTools.writeFile(path, content)
×
170
            } catch (e) {
171
                throw new TypeORMError(`Could not save database, error: ${e}`)
×
172
            }
173
        } else {
174
            const database: Uint8Array = this.databaseConnection.export()
×
175
            // convert Uint8Array to number array to improve local-storage storage
176
            const databaseArray = [].slice.call(database)
×
177
            if (this.options.useLocalForage) {
×
178
                if (window.localforage) {
×
179
                    await window.localforage.setItem(
×
180
                        path,
181
                        JSON.stringify(databaseArray),
182
                    )
183
                } else {
184
                    throw new TypeORMError(
×
185
                        `localforage is not defined - please import localforage.js into your site`,
186
                    )
187
                }
188
            } else {
189
                PlatformTools.getGlobalVariable().localStorage.setItem(
×
190
                    path,
191
                    JSON.stringify(databaseArray),
192
                )
193
            }
194
        }
195
    }
196

197
    /**
198
     * This gets called by the QueryRunner when a change to the database is made.
199
     * If a custom autoSaveCallback is specified, it get's called with the database as Uint8Array,
200
     * otherwise the save method is called which saves it to file (Node.js), local storage (browser)
201
     * or indexedDB (browser with enabled useLocalForage option).
202
     * Don't auto-save when in transaction as the call to export will end the current transaction
203
     */
204
    async autoSave() {
205
        if (this.options.autoSave && !this.queryRunner?.isTransactionActive) {
×
206
            if (this.options.autoSaveCallback) {
×
207
                await this.options.autoSaveCallback(this.export())
×
208
            } else {
209
                await this.save()
×
210
            }
211
        }
212
    }
213

214
    /**
215
     * Returns the current database as Uint8Array.
216
     */
217
    export(): Uint8Array {
218
        return this.databaseConnection.export()
×
219
    }
220

221
    /**
222
     * Creates generated map of values generated or returned by database after INSERT query.
223
     */
224
    createGeneratedMap(metadata: EntityMetadata, insertResult: any) {
225
        const generatedMap = metadata.generatedColumns.reduce(
×
226
            (map, generatedColumn) => {
227
                // seems to be the only way to get the inserted id, see https://github.com/kripken/sql.js/issues/77
228
                if (
×
229
                    generatedColumn.isPrimary &&
×
230
                    generatedColumn.generationStrategy === "increment"
231
                ) {
232
                    const query = "SELECT last_insert_rowid()"
×
233
                    try {
×
234
                        const result = this.databaseConnection.exec(query)
×
235
                        this.connection.logger.logQuery(query)
×
236
                        return OrmUtils.mergeDeep(
×
237
                            map,
238
                            generatedColumn.createValueMap(
239
                                result[0].values[0][0],
240
                            ),
241
                        )
242
                    } catch (e) {
243
                        this.connection.logger.logQueryError(e, query, [])
×
244
                    }
245
                }
246

247
                return map
×
248
            },
249
            {} as ObjectLiteral,
250
        )
251

252
        return Object.keys(generatedMap).length > 0 ? generatedMap : undefined
×
253
    }
254

255
    // -------------------------------------------------------------------------
256
    // Protected Methods
257
    // -------------------------------------------------------------------------
258

259
    /**
260
     * Creates connection with the database.
261
     * If the location option is set, the database is loaded first.
262
     */
263
    protected createDatabaseConnection(): Promise<any> {
264
        if (this.options.location) {
×
265
            return this.load(this.options.location, false)
×
266
        }
267

268
        return this.createDatabaseConnectionWithImport(this.options.database)
×
269
    }
270

271
    /**
272
     * Creates connection with an optional database.
273
     * If database is specified it is loaded, otherwise a new empty database is created.
274
     */
275
    protected async createDatabaseConnectionWithImport(
276
        database?: Uint8Array,
277
    ): Promise<any> {
278
        // sql.js < 1.0 exposes an object with a `Database` method.
279
        const isLegacyVersion = typeof this.sqlite.Database === "function"
×
280
        const sqlite = isLegacyVersion
×
281
            ? this.sqlite
282
            : await this.sqlite(this.options.sqlJsConfig)
283
        if (database && database.length > 0) {
×
284
            this.databaseConnection = new sqlite.Database(database)
×
285
        } else {
286
            this.databaseConnection = new sqlite.Database()
×
287
        }
288

289
        this.databaseConnection.exec(`PRAGMA foreign_keys = ON`)
×
290

291
        return this.databaseConnection
×
292
    }
293

294
    /**
295
     * If driver dependency is not given explicitly, then try to load it via "require".
296
     */
297
    protected loadDependencies(): void {
298
        if (PlatformTools.type === "browser") {
×
299
            const sqlite = this.options.driver || window.SQL
×
300
            this.sqlite = sqlite
×
301
        } else {
302
            try {
×
303
                const sqlite =
304
                    this.options.driver || PlatformTools.load("sql.js")
×
305
                this.sqlite = sqlite
×
306
            } catch (e) {
307
                throw new DriverPackageNotInstalledError("sql.js", "sql.js")
×
308
            }
309
        }
310
    }
311
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc