Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2a0eb4c
Update bun.lockb
chrispader Nov 3, 2025
ad478a3
feat: implement locks for `executeBatch`
chrispader Nov 3, 2025
ee33215
feat: add better errors
chrispader Nov 3, 2025
3c85b86
feat: improve concurrency features
chrispader Nov 3, 2025
d1d6f99
refactor: rename databaseQueue variables
chrispader Nov 3, 2025
7f6e1ed
feat: add `isExcusive` parameter to `transaction`
chrispader Nov 3, 2025
1dffc8b
Merge branch 'main' into fix/add-transaction-locks
chrispader Nov 3, 2025
099cae9
Merge branch 'main' into fix/add-transaction-locks
chrispader Nov 3, 2025
9ec4284
fix: styling
chrispader Nov 4, 2025
96fb734
Merge branch 'main' into fix/add-transaction-locks
chrispader Nov 4, 2025
4fb573b
fix: handle errors in sync `executeBatch`
chrispader Nov 4, 2025
158565f
Merge branch 'main' into fix/add-transaction-locks
chrispader Nov 4, 2025
dcc59f2
fix: add NitroSQLite prefix to custom error
chrispader Nov 4, 2025
04cb33b
Merge branch 'main' into fix/add-transaction-locks
chrispader Nov 4, 2025
a34dfe6
refactor: remove type exports
chrispader Nov 5, 2025
db99dad
fix: database queue promise chaining
chrispader Nov 5, 2025
3730594
refactor: remove redundant session functions
chrispader Nov 5, 2025
943bd18
refactor: move db name to constants
chrispader Nov 5, 2025
d3ab2b4
chore: add package sources to example ts files
chrispader Nov 5, 2025
6d356f1
refactor: restructure test db setup
chrispader Nov 5, 2025
16a4a9c
feat: add prefix to native C++ exception
chrispader Nov 5, 2025
27840fb
Merge branch 'main' into fix/add-transaction-locks
chrispader Nov 6, 2025
f867ac4
fix: add back line
chrispader Nov 6, 2025
dc25ef3
fix: transaction rollback result
chrispader Nov 6, 2025
4606806
test: add transaction queuing test
chrispader Nov 6, 2025
e26d85e
feat: adapt tests to new error types
chrispader Nov 6, 2025
a43d4b7
fix: make all transaction callbacks async
chrispader Nov 6, 2025
3f361f7
refactor: export NitroSQLiteError class from package
chrispader Nov 6, 2025
f6d80e7
fix: transaction type
chrispader Nov 6, 2025
d92be3b
Merge branch 'main' into fix/add-transaction-locks
chrispader Nov 6, 2025
e0a84b5
fix: make another transaction callback async
chrispader Nov 6, 2025
72cfc74
fix: always throw error on rollback
chrispader Nov 6, 2025
91abc20
refactor: rename variable
chrispader Nov 6, 2025
ad583bd
test: add more DatabaseQueue tests
chrispader Nov 6, 2025
9bcae9c
refactor: re-arrange tests and extract error messages and strings
chrispader Nov 6, 2025
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
22 changes: 19 additions & 3 deletions example/src/tests/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,40 @@ import type {
BatchQueryCommand,
} from 'react-native-nitro-sqlite'
import { open } from 'react-native-nitro-sqlite'
import {
getDatabaseQueue,
type DatabaseQueue,
} from '../../../package/src/DatabaseQueue'

const chance = new Chance()

export let testDb: NitroSQLiteConnection | undefined
export const TEST_DB_NAME = 'test'

export let testDb: NitroSQLiteConnection
export let testDbQueue: DatabaseQueue
export function resetTestDb() {
try {
if (testDb != null) {
testDb.close()
testDb.delete()
}

testDb = open({
name: 'test',
name: TEST_DB_NAME,
})
testDbQueue = getDatabaseQueue(TEST_DB_NAME)

testDb.execute('DROP TABLE IF EXISTS User;')
testDb.execute(
'CREATE TABLE User ( id REAL PRIMARY KEY, name TEXT NOT NULL, age REAL, networth REAL) STRICT;',
)
} catch (e) {
console.warn('Error resetting user database', e)
}
}

const LARGE_DB_NAME = 'large'

// Copyright 2024 Oscar Franco
// Taken from "op-sqlite" example project.
// Used to demonstrate the performance of NitroSQLite.
Expand All @@ -34,7 +50,7 @@ export function resetLargeDb() {
largeDb.delete()
}
largeDb = open({
name: 'large',
name: LARGE_DB_NAME,
})

largeDb.execute(
Expand Down
34 changes: 13 additions & 21 deletions example/src/tests/unit/common.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,27 @@
import { Chance } from 'chance'
import {
NitroSQLiteConnection,
enableSimpleNullHandling,
NitroSQLiteError,
} from 'react-native-nitro-sqlite'
import { testDb as testDbInternal, resetTestDb } from '../db'
import { resetTestDb } from '../db'
import chai from 'chai'

export function isError(e: unknown): e is Error {
return e instanceof Error
export const TEST_ERROR_CODES = {
EXPECT_NITRO_SQLITE_ERROR: 'Should have thrown a valid NitroSQLiteError',
EXPECT_PROMISE_REJECTION: 'Should have thrown a promise rejection',
} as const

export const TEST_ERROR_MESSAGE = 'Error from callback'
export const TEST_ERROR = new Error(TEST_ERROR_MESSAGE)

export function isNitroSQLiteError(e: unknown): e is NitroSQLiteError {
return e instanceof NitroSQLiteError
}

export const expect = chai.expect
export const chance = new Chance()

export let testDb: NitroSQLiteConnection

export function setupTestDb() {
enableSimpleNullHandling(false)

try {
resetTestDb()

if (testDbInternal == null) throw new Error('Failed to reset test database')

testDbInternal.execute('DROP TABLE IF EXISTS User;')
testDbInternal.execute(
'CREATE TABLE User ( id REAL PRIMARY KEY, name TEXT NOT NULL, age REAL, networth REAL) STRICT;',
)

testDb = testDbInternal!
} catch (e) {
console.warn('Error resetting user database', e)
}
resetTestDb()
}
9 changes: 6 additions & 3 deletions example/src/tests/unit/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { beforeEach, describe } from '../MochaRNAdapter'
import { setupTestDb } from './common'
import registerExecuteUnitTests from './specs/execute.spec'
import registerTransactionUnitTests from './specs/transaction.spec'
import registerExecuteBatchUnitTests from './specs/executeBatch.spec'
import registerExecuteUnitTests from './specs/operations/execute.spec'
import registerTransactionUnitTests from './specs/operations/transaction.spec'
import registerExecuteBatchUnitTests from './specs/operations/executeBatch.spec'
import registerTypeORMUnitTestsSpecs from './specs/typeorm.spec'
import registerDatabaseQueueUnitTests from './specs/DatabaseQueue.spec'

export function registerUnitTests() {
beforeEach(setupTestDb)
Expand All @@ -13,6 +14,8 @@ export function registerUnitTests() {
registerTransactionUnitTests()
registerExecuteBatchUnitTests()
})

registerDatabaseQueueUnitTests()
}

export function registerTypeORMUnitTests() {
Expand Down
182 changes: 182 additions & 0 deletions example/src/tests/unit/specs/DatabaseQueue.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import {
expect,
isNitroSQLiteError,
TEST_ERROR,
TEST_ERROR_CODES,
TEST_ERROR_MESSAGE,
} from '../common'
import { describe, it } from '../../MochaRNAdapter'
import { testDb, testDbQueue } from '../../db'
import type { BatchQueryCommand } from 'react-native-nitro-sqlite'

const TEST_QUERY = 'SELECT * FROM [User];'

const TEST_BATCH_COMMANDS: BatchQueryCommand[] = [{ query: TEST_QUERY }]

export default function registerDatabaseQueueUnitTests() {
describe('Database Queue', () => {
it('multiple transactions are queued', async () => {
const transaction1Promise = testDb.transaction(async (tx) => {
tx.execute(TEST_QUERY)

expect(testDbQueue.queue.length).to.equal(2)
expect(testDbQueue.inProgress).to.equal(true)

await new Promise<void>((resolve) => setTimeout(resolve, 100))

tx.execute(TEST_QUERY)

expect(testDbQueue.queue.length).to.equal(2)
expect(testDbQueue.inProgress).to.equal(true)
})

expect(testDbQueue.inProgress).to.equal(true)
expect(testDbQueue.queue.length).to.equal(0)

const transaction2Promise = testDb.transaction(async (tx) => {
tx.execute(TEST_QUERY)
})

expect(testDbQueue.queue.length).to.equal(1)
expect(testDbQueue.inProgress).to.equal(true)

const transaction3Promise = testDb.transaction(async (tx) => {
tx.execute(TEST_QUERY)
})

await transaction1Promise

expect(testDbQueue.queue.length).to.equal(1)
expect(testDbQueue.inProgress).to.equal(true)

await transaction2Promise

expect(testDbQueue.queue.length).to.equal(0)
expect(testDbQueue.inProgress).to.equal(true)

await transaction3Promise

expect(testDbQueue.queue.length).to.equal(0)
expect(testDbQueue.inProgress).to.equal(false)
})

it('multiple executeBatchAsync operations are queued', async () => {
const executeBatch1Promise = testDb.executeBatchAsync(TEST_BATCH_COMMANDS)

expect(testDbQueue.queue.length).to.equal(0)
expect(testDbQueue.inProgress).to.equal(true)

const executeBatch2Promise = testDb.executeBatchAsync(TEST_BATCH_COMMANDS)

expect(testDbQueue.queue.length).to.equal(1)
expect(testDbQueue.inProgress).to.equal(true)

const executeBatch3Promise = testDb.executeBatchAsync(TEST_BATCH_COMMANDS)

expect(testDbQueue.queue.length).to.equal(2)
expect(testDbQueue.inProgress).to.equal(true)

await executeBatch1Promise

expect(testDbQueue.queue.length).to.equal(1)
expect(testDbQueue.inProgress).to.equal(true)

await executeBatch2Promise

expect(testDbQueue.queue.length).to.equal(0)
expect(testDbQueue.inProgress).to.equal(true)

await executeBatch3Promise

expect(testDbQueue.queue.length).to.equal(0)
expect(testDbQueue.inProgress).to.equal(false)
})

it('mixed transactions and executeBatchAsync operations are queued', async () => {
const transaction1Promise = testDb.transaction(async (tx) => {
tx.execute('SELECT * FROM [User];')
})

expect(testDbQueue.queue.length).to.equal(0)
expect(testDbQueue.inProgress).to.equal(true)

const executeBatch1Promise = testDb.executeBatchAsync(TEST_BATCH_COMMANDS)

expect(testDbQueue.queue.length).to.equal(1)
expect(testDbQueue.inProgress).to.equal(true)

const transaction2Promise = testDb.transaction(async (tx) => {
tx.execute(TEST_QUERY)
})

expect(testDbQueue.queue.length).to.equal(2)
expect(testDbQueue.inProgress).to.equal(true)

const executeBatch2Promise = testDb.executeBatchAsync(TEST_BATCH_COMMANDS)

expect(testDbQueue.queue.length).to.equal(3)
expect(testDbQueue.inProgress).to.equal(true)

await transaction1Promise

expect(testDbQueue.queue.length).to.equal(2)
expect(testDbQueue.inProgress).to.equal(true)

await executeBatch1Promise

expect(testDbQueue.queue.length).to.equal(1)
expect(testDbQueue.inProgress).to.equal(true)

await transaction2Promise

expect(testDbQueue.queue.length).to.equal(0)
expect(testDbQueue.inProgress).to.equal(true)

await executeBatch2Promise

expect(testDbQueue.queue.length).to.equal(0)
expect(testDbQueue.inProgress).to.equal(false)
})

it('errors are thrown through DatabaseQueue', async () => {
const transaction1Promise = testDb.transaction(async (tx) => {
tx.execute('SELECT * FROM [User];')
throw TEST_ERROR
})

expect(testDbQueue.queue.length).to.equal(0)
expect(testDbQueue.inProgress).to.equal(true)

const executeBatch1Promise = testDb.executeBatchAsync(TEST_BATCH_COMMANDS)

expect(testDbQueue.queue.length).to.equal(1)
expect(testDbQueue.inProgress).to.equal(true)

try {
await transaction1Promise

expect(testDbQueue.queue.length).to.equal(0)
expect(testDbQueue.inProgress).to.equal(true)
} catch (e) {
if (isNitroSQLiteError(e)) {
expect(e.message).to.include(TEST_ERROR_MESSAGE)
} else {
expect.fail(TEST_ERROR_CODES.EXPECT_NITRO_SQLITE_ERROR)
}
}

try {
await executeBatch1Promise

expect(testDbQueue.queue.length).to.equal(0)
expect(testDbQueue.inProgress).to.equal(false)
} catch (e) {
if (isNitroSQLiteError(e)) {
expect(e.message).to.include(TEST_ERROR_MESSAGE)
} else {
expect.fail(TEST_ERROR_CODES.EXPECT_NITRO_SQLITE_ERROR)
}
}
})
})
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { chance, expect, isError, testDb } from '../common'
import { chance, expect, isNitroSQLiteError } from '../../common'
import {
enableSimpleNullHandling,
NITRO_SQLITE_NULL,
} from 'react-native-nitro-sqlite'
import { describe, it } from '../../MochaRNAdapter'
import { describe, it } from '../../../MochaRNAdapter'
import { testDb } from '../../../db'

export default function registerExecuteUnitTests() {
describe('execute', () => {
Expand Down Expand Up @@ -93,7 +94,7 @@ export default function registerExecuteUnitTests() {
[id, name, age, networth],
)
} catch (e: unknown) {
if (isError(e)) {
if (isNitroSQLiteError(e)) {
expect(e.message).to.include(
'cannot store TEXT value in REAL column User.age',
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { chance, expect, testDb } from '../common'
import { chance, expect } from '../../common'
import type { BatchQueryCommand } from 'react-native-nitro-sqlite'
import { describe, it } from '../../MochaRNAdapter'
import { describe, it } from '../../../MochaRNAdapter'
import { testDb } from '../../../db'

export default function registerExecuteBatchUnitTests() {
describe('executeBatch', () => {
Expand Down
Loading
Loading