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
10 changes: 9 additions & 1 deletion .github/workflows/lint-typescript.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}

lint:
name: Lint TypeScript (eslint, prettier)
name: Lint and test TypeScript
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
Expand All @@ -69,3 +69,11 @@ jobs:

- name: Verify no files have changed after auto-fix
run: git diff --exit-code HEAD -- . ':(exclude)bun.lock'

- name: Test core JavaScript wrappers
working-directory: packages/react-native-nitro-sqlite
run: ../../node_modules/.bin/jest --coverage --runInBand

- name: Test sqlite-vec helpers
working-directory: packages/react-native-nitro-sqlite-vec
run: ../../node_modules/.bin/jest --coverage --runInBand
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ example/vendor/bundle
# node.js
#
node_modules/
coverage/
npm-debug.log

# BUCK
Expand Down
125 changes: 124 additions & 1 deletion example/tests/unit/specs/operations/executeBatch.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { chance, expect } from '@tests/unit/common'
import type { BatchQueryCommand } from 'react-native-nitro-sqlite'
import {
NitroSQLiteError,
type BatchQueryCommand,
} from 'react-native-nitro-sqlite'
import { describe, it } from '@tests/TestApi'
import { testDb } from '@tests/db'

Expand Down Expand Up @@ -94,5 +97,125 @@ export default function registerExecuteBatchUnitTests() {
},
])
})

it('expands nested parameters into separate statements', () => {
const result = testDb.executeBatch([
{
query:
'INSERT INTO User (id, name, age, networth) VALUES (?, ?, ?, ?)',
params: [
[1, 'first', 10, 100],
[2, 'second', 20, 200],
],
},
])

expect(result.rowsAffected).toBe(2)
expect(
testDb.execute('SELECT id, name FROM User ORDER BY id').results,
).toEqual([
{ id: 1, name: 'first' },
{ id: 2, name: 'second' },
])
})

it('expands nested parameters in asynchronous batches', async () => {
const result = await testDb.executeBatchAsync([
{
query:
'INSERT INTO User (id, name, age, networth) VALUES (?, ?, ?, ?)',
params: [
[1, 'first', 10, 100],
[2, 'second', 20, 200],
],
},
])

expect(result.rowsAffected).toBe(2)
expect(
testDb.execute('SELECT id, name FROM User ORDER BY id').results,
).toEqual([
{ id: 1, name: 'first' },
{ id: 2, name: 'second' },
])
})

it('rolls back every statement when a synchronous batch fails', () => {
let batchError: unknown
try {
testDb.executeBatch([
{
query:
'INSERT INTO User (id, name, age, networth) VALUES (?, ?, ?, ?)',
params: [1, 'first', 10, 100],
},
{ query: 'INSERT INTO MissingTable (value) VALUES (1)' },
])
} catch (error) {
batchError = error
}

expect(batchError).toBeInstanceOf(NitroSQLiteError)
expect(testDb.execute('SELECT id FROM User').results).toEqual([])
expect(
testDb.executeBatch([
{
query:
"INSERT INTO User (id, name, age, networth) VALUES (2, 'later', 20, 200)",
},
]).rowsAffected,
).toBe(1)
})

it('rolls back every statement when an asynchronous batch fails', async () => {
let batchError: unknown
try {
await testDb.executeBatchAsync([
{
query:
'INSERT INTO User (id, name, age, networth) VALUES (?, ?, ?, ?)',
params: [1, 'first', 10, 100],
},
{ query: 'INSERT INTO MissingTable (value) VALUES (1)' },
])
} catch (error) {
batchError = error
}

expect(batchError).toBeInstanceOf(NitroSQLiteError)
expect(testDb.execute('SELECT id FROM User').results).toEqual([])
expect(
(
await testDb.executeBatchAsync([
{
query:
"INSERT INTO User (id, name, age, networth) VALUES (2, 'later', 20, 200)",
},
])
).rowsAffected,
).toBe(1)
})

it('rejects empty batches without leaving a transaction open', async () => {
let syncError: unknown
try {
testDb.executeBatch([])
} catch (error) {
syncError = error
}

let asyncError: unknown
try {
await testDb.executeBatchAsync([])
} catch (error) {
asyncError = error
}

expect(syncError).toBeInstanceOf(NitroSQLiteError)
expect(asyncError).toBeInstanceOf(NitroSQLiteError)
expect(testDb.execute('SELECT 1 AS value').results).toEqual([
{ value: 1 },
])
})
})
}
13 changes: 12 additions & 1 deletion example/tests/unit/specs/operations/transaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@tests/unit/common'
import { describe, it } from '@tests/TestApi'
import type { User } from '@/model/User'
import { NitroSQLiteError } from 'react-native-nitro-sqlite'
import { testDb } from '@tests/db'

export default function registerTransactionUnitTests() {
Expand Down Expand Up @@ -145,11 +146,21 @@ export default function registerTransactionUnitTests() {

tx.commit()

let queryError: unknown
try {
tx.execute('SELECT * FROM "User"')
} catch (e) {
expect(e).not.toBe(undefined)
queryError = e
}
expect(queryError).toBeInstanceOf(NitroSQLiteError)

let asyncQueryError: unknown
try {
await tx.executeAsync('SELECT * FROM "User"')
} catch (e) {
asyncQueryError = e
}
expect(asyncQueryError).toBeInstanceOf(NitroSQLiteError)
})

const res = testDb.execute('SELECT * FROM User')
Expand Down
3 changes: 3 additions & 0 deletions packages/react-native-nitro-sqlite-vec/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
}
16 changes: 16 additions & 0 deletions packages/react-native-nitro-sqlite-vec/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
"types": "./src/index",
"files": [
"src",
"!**/__tests__",
"cpp",
"RNNitroSqliteVec.podspec",
"react-native.config.js",
"README.md"
],
"scripts": {
"test": "jest",
"typecheck": "tsc --build",
"lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
"release": "release-it",
Expand Down Expand Up @@ -45,6 +47,20 @@
"react-native-nitro-sqlite": "9.8.0",
"typescript": "^5.8.3"
},
"jest": {
"preset": "@react-native/jest-preset",
"watchman": false,
"testMatch": ["<rootDir>/src/__tests__/**/*.test.ts"],
"collectCoverageFrom": ["src/index.ts"],
"coverageThreshold": {
"global": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 100
}
}
},
"release-it": {
"npm": {
"publish": true,
Expand Down
72 changes: 72 additions & 0 deletions packages/react-native-nitro-sqlite-vec/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { NitroSQLiteConnection } from 'react-native-nitro-sqlite'
import {
createVectorTable,
isVecAvailable,
knnSearch,
vecVersion,
} from '../index'

const execute = jest.fn()
const db = { execute } as unknown as NitroSQLiteConnection

beforeEach(() => execute.mockReset())

describe('sqlite-vec helpers', () => {
it('returns the linked version and reports availability', () => {
execute.mockReturnValue({ rows: { _array: [{ value: 'v0.1.9' }] } })

expect(vecVersion(db)).toBe('v0.1.9')
expect(isVecAvailable(db)).toBe(true)
expect(execute).toHaveBeenCalledWith('SELECT vec_version() AS value')
})

it('reports sqlite-vec as unavailable when the native query fails', () => {
execute.mockImplementation(() => {
throw new Error('no such function: vec_version')
})

expect(isVecAvailable(db)).toBe(false)
})

it('creates a vector table with defaults and optional storage settings', () => {
createVectorTable(db, 'documents', { dimensions: 3 })
createVectorTable(db, 'binary_documents', {
dimensions: 8,
type: 'bit',
distanceMetric: 'cosine',
column: 'features',
})

expect(execute).toHaveBeenNthCalledWith(
1,
'CREATE VIRTUAL TABLE IF NOT EXISTS documents USING vec0(embedding float[3]);',
)
expect(execute).toHaveBeenNthCalledWith(
2,
'CREATE VIRTUAL TABLE IF NOT EXISTS binary_documents USING vec0(features bit[8] distance_metric=cosine);',
)
})

it('serializes array queries and returns KNN matches', () => {
const matches = [{ rowid: 4, distance: 0.25 }]
execute.mockReturnValue({ rows: { _array: matches } })

expect(knnSearch(db, 'documents', [0.1, 0.2], 5)).toBe(matches)
expect(execute).toHaveBeenCalledWith(
'SELECT rowid, distance FROM documents WHERE embedding MATCH ? AND k = ? ORDER BY distance',
['[0.1,0.2]', 5],
)
})

it('passes JSON queries through and supports a custom column', () => {
execute.mockReturnValue({ rows: undefined })

expect(
knnSearch(db, 'documents', '[1,2]', 2, { column: 'features' }),
).toEqual([])
expect(execute).toHaveBeenCalledWith(
'SELECT rowid, distance FROM documents WHERE features MATCH ? AND k = ? ORDER BY distance',
['[1,2]', 2],
)
})
})
1 change: 1 addition & 0 deletions packages/react-native-nitro-sqlite-vec/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"include": ["src"],
"references": [{ "path": "../react-native-nitro-sqlite" }],
"compilerOptions": {
"types": ["node", "jest"],
"rootDir": "src",
"paths": {
"react-native-nitro-sqlite": ["../react-native-nitro-sqlite/src/index.ts"]
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-nitro-sqlite/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ module.exports = {
plugins: [
'babel-plugin-transform-typescript-metadata',
['@babel/plugin-proposal-decorators', { legacy: true }],
['@babel/plugin-proposal-class-properties', { loose: true }],
['@babel/plugin-transform-class-properties', { loose: true }],
],
}
17 changes: 17 additions & 0 deletions packages/react-native-nitro-sqlite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,23 @@
},
"jest": {
"preset": "@react-native/jest-preset",
"watchman": false,
"testMatch": ["<rootDir>/src/__tests__/**/*.test.ts"],
"collectCoverageFrom": [
"src/**/*.ts",
"!src/**/__tests__/**",
"!src/**/__mocks__/**",
"!src/specs/**",
"!src/types.ts"
],
"coverageThreshold": {
"global": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 100
}
},
"modulePathIgnorePatterns": [
"<rootDir>/example/node_modules",
"<rootDir>/lib/"
Expand Down
13 changes: 13 additions & 0 deletions packages/react-native-nitro-sqlite/src/__mocks__/nitro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const HybridNitroSQLite = {
open: jest.fn(),
close: jest.fn(),
drop: jest.fn(),
attach: jest.fn(),
detach: jest.fn(),
execute: jest.fn(),
executeAsync: jest.fn(),
executeBatch: jest.fn(),
executeBatchAsync: jest.fn(),
loadFile: jest.fn(),
loadFileAsync: jest.fn(),
}
Loading
Loading