diff --git a/.github/ci-paths.json b/.github/ci-paths.json
index c0faedd9..dbc8a432 100644
--- a/.github/ci-paths.json
+++ b/.github/ci-paths.json
@@ -33,6 +33,10 @@
"packages/react-native-nitro-sqlite-vec/**/*.jsx",
"packages/react-native-nitro-sqlite-vec/tsconfig.json",
"packages/react-native-nitro-sqlite-vec/package.json",
+ "packages/react-native-nitro-sqlite-drizzle/**/*.ts",
+ "packages/react-native-nitro-sqlite-drizzle/**/*.js",
+ "packages/react-native-nitro-sqlite-drizzle/tsconfig.json",
+ "packages/react-native-nitro-sqlite-drizzle/package.json",
"scripts/**/*.ts",
"example/jest*.js"
],
@@ -75,6 +79,8 @@
"packages/react-native-nitro-sqlite/package.json",
"packages/react-native-nitro-sqlite-vec/src/**",
"packages/react-native-nitro-sqlite-vec/package.json",
+ "packages/react-native-nitro-sqlite-drizzle/src/**",
+ "packages/react-native-nitro-sqlite-drizzle/package.json",
"config/tsconfig.json",
"package.json",
"bun.lock"
@@ -110,9 +116,14 @@
"scripts/android-sqlite-flags/**",
"example/index.js",
"example/babel.config*.js",
+ "example/metro.config.js",
"example/app.json",
"packages/react-native-nitro-sqlite-vec/src/**",
- "!packages/react-native-nitro-sqlite-vec/src/__tests__/**"
+ "!packages/react-native-nitro-sqlite-vec/src/__tests__/**",
+ "packages/react-native-nitro-sqlite-drizzle/src/**",
+ "packages/react-native-nitro-sqlite-drizzle/package.json",
+ "!packages/react-native-nitro-sqlite-drizzle/src/__tests__/**",
+ "packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/**"
],
"ios": [
".github/workflows/**",
@@ -148,9 +159,14 @@
"scripts/test-podspec-threadsafe.rb",
"example/index.js",
"example/babel.config*.js",
+ "example/metro.config.js",
"example/app.json",
"packages/react-native-nitro-sqlite-vec/src/**",
- "!packages/react-native-nitro-sqlite-vec/src/__tests__/**"
+ "!packages/react-native-nitro-sqlite-vec/src/__tests__/**",
+ "packages/react-native-nitro-sqlite-drizzle/src/**",
+ "packages/react-native-nitro-sqlite-drizzle/package.json",
+ "!packages/react-native-nitro-sqlite-drizzle/src/__tests__/**",
+ "packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/**"
],
"macos": [
".github/workflows/**",
@@ -187,6 +203,10 @@
"example/react-native.config.js",
"example/macos/react-native.config.js",
"!packages/react-native-nitro-sqlite/src/__tests__/**",
- "!packages/react-native-nitro-sqlite-vec/src/__tests__/**"
+ "!packages/react-native-nitro-sqlite-vec/src/__tests__/**",
+ "packages/react-native-nitro-sqlite-drizzle/src/**",
+ "packages/react-native-nitro-sqlite-drizzle/package.json",
+ "!packages/react-native-nitro-sqlite-drizzle/src/__tests__/**",
+ "packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/**"
]
}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 44ad266d..4117fb3e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -121,12 +121,14 @@ jobs:
bun --cwd example lint
bun --cwd packages/react-native-nitro-sqlite lint
bun --cwd packages/react-native-nitro-sqlite-vec lint
+ bun --cwd packages/react-native-nitro-sqlite-drizzle lint
git diff --exit-code HEAD -- . ':(exclude)bun.lock'
- name: Test JavaScript wrappers and example runners
if: steps.select.outputs.typescript == 'true'
run: |
(cd packages/react-native-nitro-sqlite && ../../node_modules/.bin/jest --coverage --runInBand)
(cd packages/react-native-nitro-sqlite-vec && ../../node_modules/.bin/jest --coverage --runInBand)
+ (cd packages/react-native-nitro-sqlite-drizzle && ../../node_modules/.bin/jest --coverage --runInBand)
(cd example && ../node_modules/.bin/jest --config jest.mocha.config.js --runInBand --watchman=false)
node --test example/macos/scripts/test-macos.test.mjs
- name: Format C++
diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml
index 9f498d99..283d4bca 100644
--- a/.github/workflows/test-macos.yml
+++ b/.github/workflows/test-macos.yml
@@ -13,7 +13,7 @@ jobs:
test:
name: macOS integration tests
runs-on: macos-26
- timeout-minutes: 30
+ timeout-minutes: 40
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
diff --git a/.prettierignore b/.prettierignore
index 79e92d52..a037f45e 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -7,6 +7,7 @@
*.markdown
packages/react-native-nitro-sqlite/lib
+packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle
.well-known
android
diff --git a/bun.lock b/bun.lock
index 359c96f5..65759395 100644
--- a/bun.lock
+++ b/bun.lock
@@ -77,12 +77,14 @@
"@react-navigation/native-stack": "^7.21.0",
"chai": "^4.3.7",
"chance": "^1.1.9",
+ "drizzle-orm": "0.45.4",
"events": "^3.3.0",
"expo-status-bar": "^1.12.1",
"react": "19.3.1",
"react-native": "0.88.0",
"react-native-nitro-modules": "*",
"react-native-nitro-sqlite": "10.2.0",
+ "react-native-nitro-sqlite-drizzle": "10.2.0",
"react-native-nitro-sqlite-vec": "10.2.0",
"react-native-quick-base64": "^3.0.1",
"react-native-quick-crypto": "^1.1.8",
@@ -116,6 +118,7 @@
"@types/node": "^22.7.4",
"@types/react": "^19.3.0",
"@types/react-test-renderer": "^19.3.0",
+ "babel-plugin-inline-import": "3.0.0",
"mocha": "^12.0.2",
"postinstall-postinstall": "^2.1.0",
"react-native-harness": "1.6.0",
@@ -130,6 +133,7 @@
"buffer": "^6.0.3",
"chai": "^4.3.7",
"chance": "^1.1.9",
+ "drizzle-orm": "0.45.4",
"events": "^3.3.0",
"expo-status-bar": "^1.12.1",
"mocha": "^10.1.0",
@@ -139,6 +143,7 @@
"react-native-macos-local": "npm:react-native-macos@0.81.9",
"react-native-nitro-modules": "0.37.1",
"react-native-nitro-sqlite": "workspace:*",
+ "react-native-nitro-sqlite-drizzle": "workspace:*",
"react-native-nitro-sqlite-vec": "workspace:*",
"readable-stream": "^4.7.0",
"reflect-metadata": "^0.2.2",
@@ -154,6 +159,7 @@
"@react-native/babel-preset": "0.81.6",
"@react-native/community-cli-plugin": "0.81.6",
"@react-native/metro-config": "0.81.6",
+ "babel-plugin-inline-import": "3.0.0",
},
},
"packages/react-native-nitro-sqlite": {
@@ -183,6 +189,23 @@
"better-sqlite3",
],
},
+ "packages/react-native-nitro-sqlite-drizzle": {
+ "name": "react-native-nitro-sqlite-drizzle",
+ "version": "10.2.0",
+ "devDependencies": {
+ "babel-plugin-inline-import": "3.0.0",
+ "drizzle-kit": "0.31.11",
+ "drizzle-orm": "0.45.4",
+ "react": "19.3.1",
+ "react-native-nitro-sqlite": "10.2.0",
+ "typescript": "^5.8.3",
+ },
+ "peerDependencies": {
+ "drizzle-orm": ">=0.45.4 <1.0.0",
+ "react": ">=17.0.0",
+ "react-native-nitro-sqlite": ">=10.3.0",
+ },
+ },
"packages/react-native-nitro-sqlite-vec": {
"name": "react-native-nitro-sqlite-vec",
"version": "10.2.0",
@@ -460,12 +483,18 @@
"@craftzdog/react-native-buffer": ["@craftzdog/react-native-buffer@6.1.2", "", { "dependencies": { "ieee754": "^1.2.1", "react-native-quick-base64": "^3.0.0" } }, "sha512-KV1HitN05FHLLDG7Zb/yftDsa+mKBYBzFMQ0PMldvUicq6vWOtAvz9mDavt7Fzozh+WNqORE+yFDkkdWysZ/SA=="],
+ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
+
"@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="],
"@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
+ "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
+
+ "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
+
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
@@ -1274,6 +1303,8 @@
"babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="],
+ "babel-plugin-inline-import": ["babel-plugin-inline-import@3.0.0", "", { "dependencies": { "require-resolve": "0.0.2" } }, "sha512-thnykl4FMb8QjMjVCuZoUmAM7r2mnTn5qJwrryCvDv6rugbJlTHZMctdjDtEgD0WBAXJOLJSGXN3loooEwx7UQ=="],
+
"babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="],
"babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="],
@@ -1542,6 +1573,10 @@
"dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="],
+ "drizzle-kit": ["drizzle-kit@0.31.11", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-YCYqxTLIB2OCqf6w9/Vef13baBVbwQIPcbT4Y56AfO6B9ajZhDhD8Jkveg/hJvT/iuMloKD/IYYkyMMNhr7Kqg=="],
+
+ "drizzle-orm": ["drizzle-orm@0.45.4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@netlify/db": ">=0.4.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@netlify/db", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-NiM1YZpJ2UxyeE67DpDDZ+Tsw4YLX3trSlkNT7Tm9eKoUNb1LvaAaQzXgYQsWs+AQ9TUmmwMmBOCXuqqxUWKKQ=="],
+
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
@@ -1776,6 +1811,8 @@
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
+ "get-tsconfig": ["get-tsconfig@4.14.3", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="],
+
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
@@ -2484,6 +2521,8 @@
"path-expression-matcher": ["path-expression-matcher@1.6.2", "", {}, "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ=="],
+ "path-extra": ["path-extra@1.0.3", "", {}, "sha512-vYm3+GCkjUlT1rDvZnDVhNLXIRvwFPaN8ebHAFcuMJM/H0RBOPD7JrcldiNLd9AS3dhAyUHLa4Hny5wp1A+Ffw=="],
+
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
@@ -2586,6 +2625,8 @@
"react-native-nitro-sqlite": ["react-native-nitro-sqlite@workspace:packages/react-native-nitro-sqlite"],
+ "react-native-nitro-sqlite-drizzle": ["react-native-nitro-sqlite-drizzle@workspace:packages/react-native-nitro-sqlite-drizzle"],
+
"react-native-nitro-sqlite-example": ["react-native-nitro-sqlite-example@workspace:example"],
"react-native-nitro-sqlite-macos-example": ["react-native-nitro-sqlite-macos-example@workspace:example/macos"],
@@ -2674,6 +2715,8 @@
"require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="],
+ "require-resolve": ["require-resolve@0.0.2", "", { "dependencies": { "x-path": "^0.0.2" } }, "sha512-eafQVaxdQsWUB8HybwognkdcIdKdQdQBwTxH48FuE6WI0owZGKp63QYr1MRp73PoX0AcyB7MDapZThYUY8FD0A=="],
+
"reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="],
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
@@ -2682,6 +2725,8 @@
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
+ "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
+
"restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
"retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
@@ -2900,6 +2945,8 @@
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+ "tsx": ["tsx@4.23.15", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw=="],
+
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
@@ -3044,6 +3091,8 @@
"ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
+ "x-path": ["x-path@0.0.2", "", { "dependencies": { "path-extra": "^1.0.2" } }, "sha512-zQ4WFI0XfJN1uEkkrB19Y4TuXOlHqKSxUJo0Yt+axPjRm8tCG6SJ6+Wo3/+Kjg4c2c8IvBXuJ0uYoshxNn4qMw=="],
+
"xml-naming": ["xml-naming@0.3.0", "", {}, "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ=="],
"xpath": ["xpath@0.0.34", "", {}, "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA=="],
@@ -3074,6 +3123,10 @@
"@babel/eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@2.1.0", "", {}, "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw=="],
+ "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
+
+ "@esbuild-kit/core-utils/source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
+
"@eslint/eslintrc/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"@google-automations/git-file-utils/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
@@ -3348,6 +3401,8 @@
"del/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+ "drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
+
"errorhandler/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
"eslint/@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
@@ -3728,6 +3783,8 @@
"react-native-macos-local/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+ "react-native-nitro-sqlite-drizzle/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+
"react-native-nitro-sqlite-example/@types/node": ["@types/node@22.19.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA=="],
"react-native-nitro-sqlite-macos-example/@react-native-community/cli": ["@react-native-community/cli@19.1.2", "", { "dependencies": { "@react-native-community/cli-clean": "19.1.2", "@react-native-community/cli-config": "19.1.2", "@react-native-community/cli-doctor": "19.1.2", "@react-native-community/cli-server-api": "19.1.2", "@react-native-community/cli-tools": "19.1.2", "@react-native-community/cli-types": "19.1.2", "chalk": "^4.1.2", "commander": "^9.4.1", "deepmerge": "^4.3.0", "execa": "^5.0.0", "find-up": "^5.0.0", "fs-extra": "^8.1.0", "graceful-fs": "^4.1.3", "prompts": "^2.4.2", "semver": "^7.5.2" }, "bin": { "rnc-cli": "build/bin.js" } }, "sha512-b28TLqODMgQRx6f4gbHoHYpnKyFbWzJkIk3+Ggpad/at493KfGQ+WvKg1sts/st8mxzmbk0T6lCc/9A3QoFKkQ=="],
@@ -3870,6 +3927,52 @@
"yargs-unparser/is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="],
+ "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
+
+ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
+
+ "@esbuild-kit/core-utils/source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
"@google-automations/git-file-utils/minimatch/brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="],
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
@@ -4072,6 +4175,58 @@
"connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+ "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
+
+ "drizzle-kit/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
+
+ "drizzle-kit/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
+
+ "drizzle-kit/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
+
+ "drizzle-kit/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
+
+ "drizzle-kit/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
+
+ "drizzle-kit/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
+
+ "drizzle-kit/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
+
+ "drizzle-kit/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
+
+ "drizzle-kit/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
+
+ "drizzle-kit/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
+
+ "drizzle-kit/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
+
+ "drizzle-kit/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
+
+ "drizzle-kit/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
+
+ "drizzle-kit/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
+
+ "drizzle-kit/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
+
+ "drizzle-kit/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
+
+ "drizzle-kit/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
+
+ "drizzle-kit/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
+
+ "drizzle-kit/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
+
+ "drizzle-kit/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
+
+ "drizzle-kit/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
+
+ "drizzle-kit/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
+
+ "drizzle-kit/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
+
+ "drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
+
+ "drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
+
"errorhandler/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"errorhandler/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
diff --git a/docs/content/docs/integrations/drizzle.mdx b/docs/content/docs/integrations/drizzle.mdx
new file mode 100644
index 00000000..73d3041e
--- /dev/null
+++ b/docs/content/docs/integrations/drizzle.mdx
@@ -0,0 +1,121 @@
+---
+title: Drizzle ORM
+description: Use Drizzle ORM with Nitro SQLite through the react-native-nitro-sqlite-drizzle driver.
+---
+
+`react-native-nitro-sqlite-drizzle` runs [Drizzle ORM](https://orm.drizzle.team) queries on a Nitro SQLite connection. Pass an open connection to `drizzle()` and query it with Drizzle's API. Every query is asynchronous.
+
+## Install
+
+Install the driver with Drizzle and Nitro SQLite. Drizzle Kit and the Babel plugin are only needed for migrations.
+
+```bash
+npm install react-native-nitro-sqlite react-native-nitro-sqlite-drizzle drizzle-orm
+npm install --save-dev drizzle-kit babel-plugin-inline-import
+```
+
+The driver supports `drizzle-orm` 0.45.4 and later 0.x releases.
+
+## Create a database
+
+```ts
+import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
+import { open } from 'react-native-nitro-sqlite'
+import { drizzle } from 'react-native-nitro-sqlite-drizzle'
+
+export const users = sqliteTable('users', {
+ id: integer('id').primaryKey(),
+ name: text('name').notNull(),
+})
+
+export const db = drizzle(open({ name: 'app.sqlite' }))
+
+await db.insert(users).values({ id: 1, name: 'Ada' })
+const rows = await db.select().from(users)
+```
+
+Create `db` once, outside your components. `drizzle(connection, config)` accepts Drizzle's `schema`, `logger`, and `casing` options. `db.$client` is the connection passed to `drizzle()`.
+
+SQLite stores binary values as BLOBs. When you pass a `Buffer` or another typed-array view through Drizzle, the driver binds its bytes as an `ArrayBuffer`, preserving the view's byte range.
+
+## Migrations
+
+Drizzle Kit generates SQL migration files from your schema. Set `driver: 'expo'` so it also writes a `migrations.js` file that bundles them for React Native:
+
+```ts title="drizzle.config.ts"
+import { defineConfig } from 'drizzle-kit'
+
+export default defineConfig({
+ schema: './db/schema.ts',
+ out: './drizzle',
+ dialect: 'sqlite',
+ driver: 'expo',
+})
+```
+
+Run `npx drizzle-kit generate` after each schema change.
+
+`migrations.js` imports `.sql` files. Add the Babel plugin so they load as strings:
+
+```js title="babel.config.js"
+module.exports = {
+ presets: ['module:@react-native/babel-preset'],
+ plugins: [['inline-import', { extensions: ['.sql'] }]],
+}
+```
+
+Add `sql` to Metro's source extensions:
+
+```js title="metro.config.js"
+const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config')
+
+const defaultConfig = getDefaultConfig(__dirname)
+
+module.exports = mergeConfig(defaultConfig, {
+ resolver: { sourceExts: [...defaultConfig.resolver.sourceExts, 'sql'] },
+})
+```
+
+Run the migrations when the app starts with `useMigrations`. It applies the migrations this database has not run yet, in one transaction, and records them in the `__drizzle_migrations` table:
+
+```tsx
+import { Text } from 'react-native'
+import { useMigrations } from 'react-native-nitro-sqlite-drizzle'
+import migrations from './drizzle/migrations'
+import { db } from './db'
+
+export function App() {
+ const { success, error } = useMigrations(db, migrations)
+
+ if (error) return Migration failed: {error.message}
+ if (!success) return Migrating...
+ return Ready
+}
+```
+
+To run migrations outside a component, such as in startup code or a test, call `migrate` and await it:
+
+```ts
+import { migrate } from 'react-native-nitro-sqlite-drizzle'
+
+await migrate(db, migrations)
+```
+
+Calls using the same connection wait for earlier migration calls before checking which migrations are pending. React Strict Mode can replay `useMigrations` without applying the same migration twice. If a migration fails, its transaction rolls back and a later call can retry it.
+
+## Transactions
+
+Run every query inside a transaction through `tx`:
+
+```ts
+import { eq } from 'drizzle-orm'
+
+await db.transaction(async (tx) => {
+ await tx.insert(users).values({ id: 2, name: 'Grace' })
+ await tx.update(users).set({ name: 'Ada L.' }).where(eq(users.id, 1))
+})
+```
+
+A transaction holds the connection until its callback finishes. A query awaited through `db` inside the callback, including a query prepared from `db`, waits for the transaction and never completes. Nested `tx.transaction()` calls use savepoints.
+
+`tx.rollback()` rolls back the transaction and rejects it with a `NitroSQLiteError` whose message is `Rollback`, not Drizzle's `TransactionRollbackError`. The `behavior` option of `db.transaction()` is ignored: transactions start with `BEGIN`. See [transactions](/docs/guides/transactions) for how Nitro SQLite runs them.
diff --git a/docs/content/docs/integrations/meta.json b/docs/content/docs/integrations/meta.json
index db829fc7..489578a1 100644
--- a/docs/content/docs/integrations/meta.json
+++ b/docs/content/docs/integrations/meta.json
@@ -1,4 +1,4 @@
{
"title": "Integrations",
- "pages": ["typeorm", "vector-search"]
+ "pages": ["drizzle", "typeorm", "vector-search"]
}
diff --git a/docs/scripts/check-api.mjs b/docs/scripts/check-api.mjs
index d094e1d7..ea0a0163 100644
--- a/docs/scripts/check-api.mjs
+++ b/docs/scripts/check-api.mjs
@@ -43,6 +43,18 @@ const requiredExports = {
'createVectorTable',
'knnSearch',
],
+ 'react-native-nitro-sqlite-drizzle': [
+ 'drizzle',
+ 'migrate',
+ 'useMigrations',
+ 'MigrationConfig',
+ 'MigrationState',
+ 'NitroSQLiteDatabase',
+ 'NitroSQLiteSessionOptions',
+ 'NitroSQLiteSession',
+ 'NitroSQLiteTransaction',
+ 'NitroSQLitePreparedQuery',
+ ],
}
for (const [packageName, exports] of Object.entries(requiredExports)) {
diff --git a/docs/tsconfig.typedoc.json b/docs/tsconfig.typedoc.json
index 85341fd1..a7ada0b1 100644
--- a/docs/tsconfig.typedoc.json
+++ b/docs/tsconfig.typedoc.json
@@ -12,6 +12,7 @@
"files": [
"../packages/react-native-nitro-sqlite/src/index.ts",
"../packages/react-native-nitro-sqlite/src/mock.ts",
- "../packages/react-native-nitro-sqlite-vec/src/index.ts"
+ "../packages/react-native-nitro-sqlite-vec/src/index.ts",
+ "../packages/react-native-nitro-sqlite-drizzle/src/index.ts"
]
}
diff --git a/docs/typedoc.json b/docs/typedoc.json
index 58f712bc..211c1002 100644
--- a/docs/typedoc.json
+++ b/docs/typedoc.json
@@ -2,6 +2,7 @@
"entryPoints": [
"../packages/react-native-nitro-sqlite",
"../packages/react-native-nitro-sqlite-vec",
+ "../packages/react-native-nitro-sqlite-drizzle",
"./mock-api"
],
"entryPointStrategy": "packages",
diff --git a/example/babel.config.shared.js b/example/babel.config.shared.js
index bc29acbe..0563b5b7 100644
--- a/example/babel.config.shared.js
+++ b/example/babel.config.shared.js
@@ -41,6 +41,7 @@ function createExampleBabelConfig(projectRoot, { buffer, crypto } = {}) {
],
'babel-plugin-transform-typescript-metadata',
['@babel/plugin-proposal-decorators', { legacy: true }],
+ ['inline-import', { extensions: ['.sql'] }],
],
}
}
diff --git a/example/macos/metro.config.js b/example/macos/metro.config.js
index 2a125816..f9e645a4 100644
--- a/example/macos/metro.config.js
+++ b/example/macos/metro.config.js
@@ -14,17 +14,21 @@ const extraNodeModules = Object.keys(libraryPackage.peerDependencies).reduce(
},
{},
)
+const defaultConfig = getDefaultConfig(__dirname)
const config = {
projectRoot: __dirname,
watchFolders: [exampleRoot, root],
resolver: {
+ // The example shares migration fixtures with the Drizzle package tests.
+ blockList: /\/__tests__\/(?!fixtures\/).*/,
disableHierarchicalLookup: true,
nodeModulesPaths: [
path.join(__dirname, 'node_modules'),
path.join(root, 'node_modules'),
],
platforms: ['macos', 'ios', 'android'],
+ sourceExts: [...defaultConfig.resolver.sourceExts, 'sql'],
extraNodeModules,
resolveRequest: (context, moduleName, platform) => {
// TypeORM imports the workspace package without going through Babel.
@@ -78,4 +82,4 @@ const config = {
},
}
-module.exports = mergeConfig(getDefaultConfig(__dirname), config)
+module.exports = mergeConfig(defaultConfig, config)
diff --git a/example/macos/package.json b/example/macos/package.json
index 62d39c4c..2a7954cd 100644
--- a/example/macos/package.json
+++ b/example/macos/package.json
@@ -12,6 +12,7 @@
"buffer": "^6.0.3",
"chai": "^4.3.7",
"chance": "^1.1.9",
+ "drizzle-orm": "0.45.4",
"events": "^3.3.0",
"expo-status-bar": "^1.12.1",
"mocha": "^10.1.0",
@@ -22,6 +23,7 @@
"react-native-macos-local": "npm:react-native-macos@0.81.9",
"react-native-nitro-modules": "0.37.1",
"react-native-nitro-sqlite": "workspace:*",
+ "react-native-nitro-sqlite-drizzle": "workspace:*",
"react-native-nitro-sqlite-vec": "workspace:*",
"reflect-metadata": "^0.2.2",
"typeorm": "1.1.1",
@@ -34,6 +36,7 @@
"@react-native-community/cli-platform-apple": "19.1.2",
"@react-native-community/cli-platform-ios": "19.1.2",
"@react-native/babel-preset": "0.81.6",
+ "babel-plugin-inline-import": "3.0.0",
"@react-native/community-cli-plugin": "0.81.6",
"@react-native/metro-config": "0.81.6"
}
diff --git a/example/macos/scripts/test-macos.mjs b/example/macos/scripts/test-macos.mjs
index 43374187..119cd532 100644
--- a/example/macos/scripts/test-macos.mjs
+++ b/example/macos/scripts/test-macos.mjs
@@ -183,22 +183,23 @@ function watchProcessFailure(child, name, getOutput) {
}
}
-async function waitForReport(report, failures) {
+async function waitForReport(report, failures, getOutput) {
let timeout
try {
return await Promise.race([
report,
...failures,
new Promise((_, reject) => {
- timeout = setTimeout(
- () =>
- reject(
- new Error(
- `Timed out waiting for macOS test results after ${testTimeoutMs}ms`,
- ),
+ timeout = setTimeout(() => {
+ const output = getOutput()
+ reject(
+ new Error(
+ `Timed out waiting for macOS test results after ${testTimeoutMs}ms${
+ output.length > 0 ? `\n${output}` : ''
+ }`,
),
- testTimeoutMs,
- )
+ )
+ }, testTimeoutMs)
}),
])
} finally {
@@ -325,10 +326,11 @@ async function main() {
.join('\n\n')
}
appFailure = watchProcessFailure(app, 'The macOS app', getAppAndMetroOutput)
- const result = await waitForReport(resultServer.report, [
- appFailure.failure,
- metroFailure.failure,
- ])
+ const result = await waitForReport(
+ resultServer.report,
+ [appFailure.failure, metroFailure.failure],
+ getAppAndMetroOutput,
+ )
appFailure.stopWatching()
appFailure = undefined
metroFailure.stopWatching()
diff --git a/example/macos/scripts/test-macos.test.mjs b/example/macos/scripts/test-macos.test.mjs
index 159d1c61..c23ba880 100644
--- a/example/macos/scripts/test-macos.test.mjs
+++ b/example/macos/scripts/test-macos.test.mjs
@@ -65,6 +65,11 @@ test('times out when the app does not report results', async () => {
result.output,
/Timed out waiting for macOS test results after 100ms/,
)
+ assert.match(
+ result.output,
+ /App output:[\s\S]*fake app did not report results/,
+ )
+ assert.match(result.output, /Metro output:[\s\S]*fake Metro is ready/)
})
test('fails promptly when Metro exits during startup', async () => {
@@ -136,11 +141,15 @@ const server = createServer((_request, response) => {
response.end('packager-status:running')
})
-server.listen(port, '127.0.0.1')
+server.listen(port, '127.0.0.1', () => console.log('fake Metro is ready'))
process.on('SIGTERM', () => server.close(() => process.exit(0)))
`
const fakeAppSource = `#!/usr/bin/env node
+if (process.env.FAKE_APP_SCENARIO === 'no-report') {
+ console.error('fake app did not report results')
+}
+
if (process.env.FAKE_APP_SCENARIO === 'early-exit') {
console.error('fake app failed')
process.exit(23)
diff --git a/example/metro.config.js b/example/metro.config.js
index 46f2eb69..56e800b4 100644
--- a/example/metro.config.js
+++ b/example/metro.config.js
@@ -4,6 +4,7 @@ const pak = require('../package.json')
const root = path.resolve(__dirname, '..')
const modules = Object.keys({ ...pak.peerDependencies })
+const defaultConfig = getDefaultConfig(__dirname)
const config = {
projectRoot: __dirname,
@@ -16,6 +17,7 @@ const config = {
acc[name] = path.join(__dirname, 'node_modules', name)
return acc
}, {}),
+ sourceExts: [...defaultConfig.resolver.sourceExts, 'sql'],
},
transformer: {
@@ -28,4 +30,4 @@ const config = {
},
}
-module.exports = mergeConfig(getDefaultConfig(__dirname), config)
+module.exports = mergeConfig(defaultConfig, config)
diff --git a/example/package.json b/example/package.json
index 2f54b362..62d2821a 100644
--- a/example/package.json
+++ b/example/package.json
@@ -28,12 +28,14 @@
"@react-navigation/native-stack": "^7.21.0",
"chai": "^4.3.7",
"chance": "^1.1.9",
+ "drizzle-orm": "0.45.4",
"events": "^3.3.0",
"expo-status-bar": "^1.12.1",
"react": "19.3.1",
"react-native": "0.88.0",
"react-native-nitro-modules": "*",
"react-native-nitro-sqlite": "10.2.0",
+ "react-native-nitro-sqlite-drizzle": "10.2.0",
"react-native-nitro-sqlite-vec": "10.2.0",
"react-native-quick-base64": "^3.0.1",
"react-native-quick-crypto": "^1.1.8",
@@ -67,6 +69,7 @@
"@types/node": "^22.7.4",
"@types/react": "^19.3.0",
"@types/react-test-renderer": "^19.3.0",
+ "babel-plugin-inline-import": "3.0.0",
"mocha": "^12.0.2",
"postinstall-postinstall": "^2.1.0",
"react-native-harness": "1.6.0",
diff --git a/example/tests/harness/drizzle.harness.tsx b/example/tests/harness/drizzle.harness.tsx
new file mode 100644
index 00000000..1925ada0
--- /dev/null
+++ b/example/tests/harness/drizzle.harness.tsx
@@ -0,0 +1,33 @@
+import '@/initGlobals'
+import React from 'react'
+import { describe, expect, it, render, waitUntil } from 'react-native-harness'
+import { open } from 'react-native-nitro-sqlite'
+import { drizzle, useMigrations } from 'react-native-nitro-sqlite-drizzle'
+import migrations from 'react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle/migrations'
+import { registerDrizzleUnitTests } from '@tests/unit'
+import init from './init'
+
+init()
+registerDrizzleUnitTests()
+
+describe('useMigrations', () => {
+ it('reports success once the migrations ran', async () => {
+ const connection = open({ name: 'test_drizzle_hook' })
+ const db = drizzle(connection)
+ let state: ReturnType | undefined
+ function Migrations() {
+ state = useMigrations(db, migrations)
+ return null
+ }
+
+ await render()
+ await waitUntil(() => state?.success)
+
+ expect(
+ connection.execute("SELECT name FROM sqlite_master WHERE name = 'users'")
+ .results,
+ ).toEqual([{ name: 'users' }])
+ connection.close()
+ connection.delete()
+ })
+})
diff --git a/example/tests/runAll.ts b/example/tests/runAll.ts
index 428a4acc..b75b1aad 100644
--- a/example/tests/runAll.ts
+++ b/example/tests/runAll.ts
@@ -1,6 +1,7 @@
import type { MochaTestResult } from './MochaSetup'
import { runTests } from './MochaSetup'
import {
+ registerDrizzleUnitTests,
registerSqliteVecUnitTests,
registerTypeORMUnitTests,
registerUnitTests,
@@ -14,6 +15,7 @@ export async function runAllTests(): Promise {
registerUnitTests,
registerTypeORMUnitTests,
registerSqliteVecUnitTests,
+ registerDrizzleUnitTests,
)
return results
diff --git a/example/tests/unit/index.ts b/example/tests/unit/index.ts
index 977ca3cf..c5b00969 100644
--- a/example/tests/unit/index.ts
+++ b/example/tests/unit/index.ts
@@ -9,6 +9,7 @@ import registerPreparedStatementUnitTests from './specs/operations/preparedState
import registerTypeORMUnitTestsSpecs from './specs/typeorm.spec'
import registerDatabaseQueueUnitTests from './specs/DatabaseQueue.spec'
import registerSqliteVecUnitTestsSpecs from './specs/sqlite-vec.spec'
+import registerDrizzleUnitTestsSpecs from './specs/drizzle.spec'
import registerConnectionUnitTests from './specs/Connections.spec'
import registerBenchmarkDatabaseUnitTests from './specs/benchmarkDatabase.spec'
@@ -36,3 +37,7 @@ export function registerTypeORMUnitTests() {
export function registerSqliteVecUnitTests() {
describe('sqlite-vec.spec.ts', registerSqliteVecUnitTestsSpecs)
}
+
+export function registerDrizzleUnitTests() {
+ describe('drizzle.spec.ts', registerDrizzleUnitTestsSpecs)
+}
diff --git a/example/tests/unit/specs/DatabaseQueue.spec.ts b/example/tests/unit/specs/DatabaseQueue.spec.ts
index 930e11f2..135588b3 100644
--- a/example/tests/unit/specs/DatabaseQueue.spec.ts
+++ b/example/tests/unit/specs/DatabaseQueue.spec.ts
@@ -106,50 +106,49 @@ export default function registerDatabaseQueueUnitTests() {
expect(testDbQueue.inProgress).toBe(false)
})
- it('mixed transactions and executeBatchAsync operations are queued', async () => {
+ it('runs mixed transactions and async batches in submission order', async () => {
+ testDb.execute('CREATE TABLE MixedQueueOrder (value INTEGER)')
const transaction1Promise = testDb.transaction(async (tx) => {
- tx.execute('SELECT * FROM [User];')
+ tx.execute('INSERT INTO MixedQueueOrder VALUES (1)')
})
expect(testDbQueue.queue.length).toBe(0)
expect(testDbQueue.inProgress).toBe(true)
- const executeBatch1Promise = testDb.executeBatchAsync(TEST_BATCH_COMMANDS)
+ const executeBatch1Promise = testDb.executeBatchAsync([
+ { query: 'INSERT INTO MixedQueueOrder VALUES (2)' },
+ ])
expect(testDbQueue.queue.length).toBe(1)
expect(testDbQueue.inProgress).toBe(true)
const transaction2Promise = testDb.transaction(async (tx) => {
- tx.execute(TEST_QUERY)
+ tx.execute('INSERT INTO MixedQueueOrder VALUES (3)')
})
expect(testDbQueue.queue.length).toBe(2)
expect(testDbQueue.inProgress).toBe(true)
- const executeBatch2Promise = testDb.executeBatchAsync(TEST_BATCH_COMMANDS)
+ const executeBatch2Promise = testDb.executeBatchAsync([
+ { query: 'INSERT INTO MixedQueueOrder VALUES (4)' },
+ ])
expect(testDbQueue.queue.length).toBe(3)
expect(testDbQueue.inProgress).toBe(true)
- await transaction1Promise
-
- expect(testDbQueue.queue.length).toBe(2)
- expect(testDbQueue.inProgress).toBe(true)
-
- await executeBatch1Promise
-
- expect(testDbQueue.queue.length).toBe(1)
- expect(testDbQueue.inProgress).toBe(true)
-
- await transaction2Promise
-
- expect(testDbQueue.queue.length).toBe(0)
- expect(testDbQueue.inProgress).toBe(true)
-
- await executeBatch2Promise
+ await Promise.all([
+ transaction1Promise,
+ executeBatch1Promise,
+ transaction2Promise,
+ executeBatch2Promise,
+ ])
expect(testDbQueue.queue.length).toBe(0)
expect(testDbQueue.inProgress).toBe(false)
+ expect(
+ testDb.execute('SELECT value FROM MixedQueueOrder ORDER BY rowid')
+ .results,
+ ).toEqual([{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }])
})
it('errors are thrown through DatabaseQueue', async () => {
diff --git a/example/tests/unit/specs/drizzle.spec.ts b/example/tests/unit/specs/drizzle.spec.ts
new file mode 100644
index 00000000..a1d487e3
--- /dev/null
+++ b/example/tests/unit/specs/drizzle.spec.ts
@@ -0,0 +1,126 @@
+import { afterEach, beforeEach, it } from '@tests/TestApi'
+import { expect } from '@tests/unit/common'
+import { eq, sql } from 'drizzle-orm'
+import { blob, sqliteTable } from 'drizzle-orm/sqlite-core'
+import { open } from 'react-native-nitro-sqlite'
+import type { NitroSQLiteConnection } from 'react-native-nitro-sqlite'
+import { drizzle, migrate } from 'react-native-nitro-sqlite-drizzle'
+import type { NitroSQLiteDatabase } from 'react-native-nitro-sqlite-drizzle'
+import migrations from 'react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle/migrations'
+import {
+ pets,
+ users,
+} from 'react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/schema'
+
+const DB_NAME = 'test_drizzle'
+let connection: NitroSQLiteConnection
+let db: NitroSQLiteDatabase
+
+export default function registerDrizzleUnitTests() {
+ beforeEach(async () => {
+ connection = open({ name: DB_NAME })
+ db = drizzle(connection)
+ await migrate(db, migrations)
+ })
+
+ afterEach(() => {
+ connection.close()
+ connection.delete()
+ })
+
+ it('applies drizzle-kit migrations once', async () => {
+ await migrate(db, migrations)
+
+ expect(
+ connection.execute('SELECT created_at FROM __drizzle_migrations').results,
+ ).toEqual(
+ migrations.journal.entries.map((entry) => ({ created_at: entry.when })),
+ )
+ })
+
+ it('round trips blob views and JSON through native bindings', async () => {
+ connection.execute('CREATE TABLE files (content BLOB, metadata BLOB)')
+ const files = sqliteTable('files', {
+ content: blob('content', { mode: 'buffer' }).$type(),
+ metadata: blob('metadata', { mode: 'json' }),
+ })
+ const bytes = Buffer.from([99, 1, 2, 88]).subarray(1, 3)
+ const metadata = { name: 'receipt.jpg' }
+
+ await db.insert(files).values({ content: bytes, metadata })
+
+ expect(await db.select().from(files)).toEqual([
+ { content: Buffer.from([1, 2]), metadata },
+ ])
+ })
+
+ it('keeps both sides of a join whose columns share names', async () => {
+ await db.insert(users).values([
+ { id: 1, name: 'Dave' },
+ { id: 2, name: 'Ritesh' },
+ ])
+ await db.insert(pets).values({ id: 7, name: 'Rex', ownerId: 1 })
+
+ expect(
+ await db
+ .select()
+ .from(users)
+ .leftJoin(pets, eq(pets.ownerId, users.id))
+ .orderBy(users.id),
+ ).toEqual([
+ {
+ users: { id: 1, name: 'Dave' },
+ pets: { id: 7, name: 'Rex', ownerId: 1 },
+ },
+ { users: { id: 2, name: 'Ritesh' }, pets: null },
+ ])
+ })
+
+ it('runs raw SQL and prepared queries', async () => {
+ await db.run(sql`INSERT INTO users (id, name) VALUES (1, 'Dave')`)
+
+ expect(await db.all(sql`SELECT name FROM users`)).toEqual([
+ { name: 'Dave' },
+ ])
+ expect(await db.values(sql`SELECT id, name FROM users`)).toEqual([
+ [1, 'Dave'],
+ ])
+
+ const byId = db
+ .select()
+ .from(users)
+ .where(eq(users.id, sql.placeholder('id')))
+ .prepare()
+ expect(await byId.get({ id: 1 })).toEqual({ id: 1, name: 'Dave' })
+ expect(await byId.get({ id: 2 })).toBe(undefined)
+ })
+
+ it('commits transactions and rolls back savepoints', async () => {
+ await db.transaction(async (tx) => {
+ await tx.insert(users).values({ id: 1, name: 'Dave' })
+ await tx
+ .transaction(async (nested) => {
+ await nested.insert(users).values({ id: 2, name: 'Ritesh' })
+ nested.rollback()
+ })
+ .catch(() => undefined)
+ })
+
+ expect(await db.select().from(users)).toEqual([{ id: 1, name: 'Dave' }])
+ })
+
+ it('rolls back when tx.rollback() is called', async () => {
+ let error: unknown
+ try {
+ await db.transaction(async (tx) => {
+ await tx.insert(users).values({ id: 1, name: 'Dave' })
+ tx.rollback()
+ })
+ } catch (e) {
+ error = e
+ }
+
+ expect((error as Error).message).toBe('Rollback')
+ expect(await db.select().from(users)).toEqual([])
+ })
+}
diff --git a/example/tsconfig.json b/example/tsconfig.json
index 91542814..79e27c6c 100644
--- a/example/tsconfig.json
+++ b/example/tsconfig.json
@@ -1,12 +1,21 @@
{
"extends": "../config/tsconfig.json",
"include": ["src", "tests", "index.js", "rn-harness.config.mjs"],
- "references": [{ "path": "../packages/react-native-nitro-sqlite" }],
+ "references": [
+ { "path": "../packages/react-native-nitro-sqlite" },
+ { "path": "../packages/react-native-nitro-sqlite-drizzle" }
+ ],
"compilerOptions": {
"paths": {
"react-native-nitro-sqlite": [
"../packages/react-native-nitro-sqlite/src/index.ts"
],
+ "react-native-nitro-sqlite-drizzle": [
+ "../packages/react-native-nitro-sqlite-drizzle/src/index.ts"
+ ],
+ "react-native-nitro-sqlite-drizzle/*": [
+ "../packages/react-native-nitro-sqlite-drizzle/*"
+ ],
"@nitro-sqlite/*": ["../packages/react-native-nitro-sqlite/src/*"],
"@/*": ["./src/*"],
"@tests/*": ["./tests/*"]
diff --git a/packages/react-native-nitro-sqlite-drizzle/.eslintrc.js b/packages/react-native-nitro-sqlite-drizzle/.eslintrc.js
new file mode 100644
index 00000000..07a980a5
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/.eslintrc.js
@@ -0,0 +1,9 @@
+module.exports = {
+ root: true,
+ extends: '../../config/.eslintrc.js',
+ ignorePatterns: ['src/__tests__/fixtures/drizzle'],
+ parserOptions: {
+ project: true,
+ tsconfigRootDir: __dirname,
+ },
+}
diff --git a/packages/react-native-nitro-sqlite-drizzle/README.md b/packages/react-native-nitro-sqlite-drizzle/README.md
new file mode 100644
index 00000000..c0dd4a07
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/README.md
@@ -0,0 +1,31 @@
+# react-native-nitro-sqlite-drizzle
+
+[Drizzle ORM](https://orm.drizzle.team) driver for [`react-native-nitro-sqlite`](https://github.com/margelo/react-native-nitro-sqlite).
+
+## Installation
+
+```bash
+npm install react-native-nitro-sqlite react-native-nitro-sqlite-drizzle drizzle-orm
+```
+
+## Usage
+
+```ts
+import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
+import { open } from 'react-native-nitro-sqlite'
+import { drizzle } from 'react-native-nitro-sqlite-drizzle'
+
+const users = sqliteTable('users', {
+ id: integer('id').primaryKey(),
+ name: text('name').notNull(),
+})
+
+const db = drizzle(open({ name: 'app.sqlite' }))
+
+await db.insert(users).values({ id: 1, name: 'Ada' })
+const rows = await db.select().from(users)
+```
+
+Every query is asynchronous. Inside `db.transaction()`, run queries through `tx`: a query awaited through `db` waits for the transaction and never completes.
+
+See the [Drizzle ORM guide](https://sqlite.margelo.com/docs/integrations/drizzle) for migrations with Drizzle Kit, `useMigrations`, and transactions.
diff --git a/packages/react-native-nitro-sqlite-drizzle/babel.config.js b/packages/react-native-nitro-sqlite-drizzle/babel.config.js
new file mode 100644
index 00000000..6eb8c4d0
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/babel.config.js
@@ -0,0 +1,4 @@
+module.exports = {
+ presets: ['module:@react-native/babel-preset'],
+ plugins: [['inline-import', { extensions: ['.sql'] }]],
+}
diff --git a/packages/react-native-nitro-sqlite-drizzle/package.json b/packages/react-native-nitro-sqlite-drizzle/package.json
new file mode 100644
index 00000000..ea7fb1c0
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "react-native-nitro-sqlite-drizzle",
+ "version": "10.2.0",
+ "description": "Drizzle ORM driver for react-native-nitro-sqlite.",
+ "source": "./src/index",
+ "react-native": "./src/index",
+ "main": "./src/index",
+ "types": "./src/index",
+ "files": [
+ "src",
+ "!**/__tests__",
+ "README.md"
+ ],
+ "scripts": {
+ "test": "jest",
+ "typecheck": "tsc --build",
+ "lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
+ "prepublishOnly": "bun typecheck"
+ },
+ "keywords": [
+ "react-native",
+ "sqlite",
+ "drizzle",
+ "drizzle-orm",
+ "orm",
+ "nitro-modules",
+ "ios",
+ "android"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/margelo/react-native-nitro-sqlite.git"
+ },
+ "author": "Christoph Pader",
+ "license": "MIT",
+ "publishConfig": {
+ "registry": "https://registry.npmjs.org/",
+ "access": "public"
+ },
+ "peerDependencies": {
+ "drizzle-orm": ">=0.45.4 <1.0.0",
+ "react": ">=17.0.0",
+ "react-native-nitro-sqlite": ">=10.3.0"
+ },
+ "devDependencies": {
+ "babel-plugin-inline-import": "3.0.0",
+ "drizzle-kit": "0.31.11",
+ "drizzle-orm": "0.45.4",
+ "react": "19.3.1",
+ "react-native-nitro-sqlite": "10.2.0",
+ "typescript": "^5.8.3"
+ },
+ "jest": {
+ "preset": "@react-native/jest-preset",
+ "watchman": false,
+ "moduleNameMapper": {
+ "^react-native-nitro-sqlite/mock$": "/../react-native-nitro-sqlite/src/mock.ts"
+ },
+ "testMatch": [
+ "/src/__tests__/**/*.test.ts"
+ ]
+ }
+}
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/__tests__/driver.test.ts b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/driver.test.ts
new file mode 100644
index 00000000..bb6c5bbe
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/driver.test.ts
@@ -0,0 +1,137 @@
+import { eq, sql } from 'drizzle-orm'
+import { blob, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
+import { open, resetAllDatabases } from 'react-native-nitro-sqlite/mock'
+import type { NitroSQLiteConnection } from 'react-native-nitro-sqlite'
+import { drizzle } from '../index'
+
+const users = sqliteTable('users', {
+ id: integer('id').primaryKey(),
+ name: text('name').notNull(),
+})
+
+const pets = sqliteTable('pets', {
+ id: integer('id').primaryKey(),
+ name: text('name').notNull(),
+ ownerId: integer('owner_id'),
+})
+
+function createDatabase() {
+ const connection = open({ name: 'drizzle' }) as NitroSQLiteConnection
+ connection.execute(
+ 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)',
+ )
+ connection.execute(
+ 'CREATE TABLE pets (id INTEGER PRIMARY KEY, name TEXT NOT NULL, owner_id INTEGER)',
+ )
+ return drizzle(connection)
+}
+
+afterEach(resetAllDatabases)
+
+describe('drizzle', () => {
+ it('binds blob views as ArrayBuffers containing only their bytes', async () => {
+ const db = createDatabase()
+ db.$client.execute('CREATE TABLE files (content BLOB, metadata BLOB)')
+ const files = sqliteTable('files', {
+ content: blob('content', { mode: 'buffer' }),
+ metadata: blob('metadata', { mode: 'json' }),
+ })
+ const execute = jest.spyOn(db.$client, 'executeRawAsync')
+ const bytes = Buffer.from([99, 1, 2, 88]).subarray(1, 3)
+ const metadata = { name: 'receipt.jpg' }
+
+ await db.insert(files).values({ content: bytes, metadata })
+
+ const params = execute.mock.calls[0]?.[1]
+ expect(params?.[0]).toBeInstanceOf(ArrayBuffer)
+ expect(params?.[1]).toBeInstanceOf(ArrayBuffer)
+ expect(params?.[0]).toEqual(Uint8Array.from([1, 2]).buffer)
+ expect(await db.select().from(files)).toEqual([
+ { content: Buffer.from([1, 2]), metadata },
+ ])
+ execute.mockRestore()
+ })
+
+ it('inserts and selects rows', async () => {
+ const db = createDatabase()
+ await db.insert(users).values([
+ { id: 1, name: 'Dave' },
+ { id: 2, name: 'Ritesh' },
+ ])
+
+ expect(await db.select().from(users)).toEqual([
+ { id: 1, name: 'Dave' },
+ { id: 2, name: 'Ritesh' },
+ ])
+ expect(await db.select().from(users).where(eq(users.id, 2)).get()).toEqual({
+ id: 2,
+ name: 'Ritesh',
+ })
+ })
+
+ it('keeps both sides of a join whose columns share names', async () => {
+ const db = createDatabase()
+ await db.insert(users).values([
+ { id: 1, name: 'Dave' },
+ { id: 2, name: 'Ritesh' },
+ ])
+ await db.insert(pets).values({ id: 7, name: 'Rex', ownerId: 1 })
+
+ expect(
+ await db
+ .select()
+ .from(users)
+ .leftJoin(pets, eq(pets.ownerId, users.id))
+ .orderBy(users.id),
+ ).toEqual([
+ {
+ users: { id: 1, name: 'Dave' },
+ pets: { id: 7, name: 'Rex', ownerId: 1 },
+ },
+ { users: { id: 2, name: 'Ritesh' }, pets: null },
+ ])
+ })
+
+ it('runs raw SQL and prepared queries', async () => {
+ const db = createDatabase()
+ await db.run(sql`INSERT INTO users (id, name) VALUES (1, 'Dave')`)
+
+ expect(await db.all(sql`SELECT name FROM users`)).toEqual([
+ { name: 'Dave' },
+ ])
+ expect(await db.get(sql`SELECT name FROM users`)).toEqual({ name: 'Dave' })
+ expect(await db.values(sql`SELECT id, name FROM users`)).toEqual([
+ [1, 'Dave'],
+ ])
+
+ const byId = db
+ .select()
+ .from(users)
+ .where(eq(users.id, sql.placeholder('id')))
+ .prepare()
+ expect(await byId.get({ id: 1 })).toEqual({ id: 1, name: 'Dave' })
+ expect(await byId.get({ id: 2 })).toBeUndefined()
+ })
+
+ it('commits transactions and rolls back savepoints and failures', async () => {
+ const db = createDatabase()
+ await db.transaction(async (tx) => {
+ await tx.insert(users).values({ id: 1, name: 'Dave' })
+ await tx
+ .transaction(async (nested) => {
+ await nested.insert(users).values({ id: 2, name: 'Ritesh' })
+ nested.rollback()
+ })
+ .catch(() => undefined)
+ })
+ expect(await db.select().from(users)).toEqual([{ id: 1, name: 'Dave' }])
+
+ await expect(
+ db.transaction(async (tx) => {
+ await tx.insert(users).values({ id: 3, name: 'Ada' })
+ tx.rollback()
+ }),
+ ).rejects.toThrow('Rollback')
+ expect(await db.select().from(users)).toEqual([{ id: 1, name: 'Dave' }])
+ })
+})
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle.config.ts b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle.config.ts
new file mode 100644
index 00000000..42f0aab3
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from 'drizzle-kit'
+
+export default defineConfig({
+ schema: './src/__tests__/fixtures/schema.ts',
+ out: './src/__tests__/fixtures/drizzle',
+ dialect: 'sqlite',
+ driver: 'expo',
+})
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle/0000_init.sql b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle/0000_init.sql
new file mode 100644
index 00000000..c07176fa
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle/0000_init.sql
@@ -0,0 +1,12 @@
+CREATE TABLE `pets` (
+ `id` integer PRIMARY KEY NOT NULL,
+ `name` text NOT NULL,
+ `owner_id` integer,
+ FOREIGN KEY (`owner_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
+);
+--> statement-breakpoint
+CREATE INDEX `pets_owner_id` ON `pets` (`owner_id`);--> statement-breakpoint
+CREATE TABLE `users` (
+ `id` integer PRIMARY KEY NOT NULL,
+ `name` text NOT NULL
+);
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle/meta/_journal.json b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle/meta/_journal.json
new file mode 100644
index 00000000..6802c430
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle/meta/_journal.json
@@ -0,0 +1,13 @@
+{
+ "version": "7",
+ "dialect": "sqlite",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "6",
+ "when": 1792274745571,
+ "tag": "0000_init",
+ "breakpoints": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle/migrations.js b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle/migrations.js
new file mode 100644
index 00000000..87020f29
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/drizzle/migrations.js
@@ -0,0 +1,12 @@
+// This file is required for Expo/React Native SQLite migrations - https://orm.drizzle.team/quick-sqlite/expo
+
+import journal from './meta/_journal.json';
+import m0000 from './0000_init.sql';
+
+ export default {
+ journal,
+ migrations: {
+ m0000
+ }
+ }
+
\ No newline at end of file
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/schema.ts b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/schema.ts
new file mode 100644
index 00000000..2fcc915a
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/fixtures/schema.ts
@@ -0,0 +1,16 @@
+import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
+
+export const users = sqliteTable('users', {
+ id: integer('id').primaryKey(),
+ name: text('name').notNull(),
+})
+
+export const pets = sqliteTable(
+ 'pets',
+ {
+ id: integer('id').primaryKey(),
+ name: text('name').notNull(),
+ ownerId: integer('owner_id').references(() => users.id),
+ },
+ (table) => [index('pets_owner_id').on(table.ownerId)],
+)
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/__tests__/migrator.test.ts b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/migrator.test.ts
new file mode 100644
index 00000000..21dcf2f5
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/migrator.test.ts
@@ -0,0 +1,95 @@
+import { eq } from 'drizzle-orm'
+import { open, resetAllDatabases } from 'react-native-nitro-sqlite/mock'
+import type { NitroSQLiteConnection } from 'react-native-nitro-sqlite'
+import { drizzle, migrate } from '../index'
+import migrations from './fixtures/drizzle/migrations'
+import { pets, users } from './fixtures/schema'
+
+afterEach(resetAllDatabases)
+
+describe('migrate', () => {
+ it('serializes concurrent migrations across wrappers sharing a connection', async () => {
+ const connection = open({
+ name: 'concurrent-migrate',
+ }) as NitroSQLiteConnection
+ const db = drizzle(connection)
+
+ await Promise.all([
+ migrate(db, migrations),
+ migrate(drizzle(connection), migrations),
+ migrate(db, migrations),
+ ])
+
+ expect(
+ connection.execute('SELECT created_at FROM __drizzle_migrations').results,
+ ).toEqual(
+ migrations.journal.entries.map((entry) => ({ created_at: entry.when })),
+ )
+ })
+
+ it('rolls back a failed migration and lets the next queued call retry', async () => {
+ const connection = open({ name: 'retry-migrate' }) as NitroSQLiteConnection
+ const db = drizzle(connection)
+ const failed = {
+ ...migrations,
+ migrations: {
+ m0000:
+ 'CREATE TABLE rollback_probe (id integer);--> statement-breakpoint SELECT FROM invalid',
+ },
+ }
+
+ const results = await Promise.allSettled([
+ migrate(db, failed),
+ migrate(db, migrations),
+ ])
+
+ expect(results.map((result) => result.status)).toEqual([
+ 'rejected',
+ 'fulfilled',
+ ])
+ expect(
+ connection.execute(
+ "SELECT name FROM sqlite_master WHERE name = 'rollback_probe'",
+ ).results,
+ ).toEqual([])
+ expect(
+ connection.execute('SELECT created_at FROM __drizzle_migrations').results,
+ ).toHaveLength(1)
+ })
+
+ it('applies each drizzle-kit migration once', async () => {
+ const connection = open({ name: 'migrate' }) as NitroSQLiteConnection
+ const db = drizzle(connection)
+
+ await migrate(db, migrations)
+ await migrate(db, migrations)
+
+ expect(
+ connection.execute('SELECT created_at FROM __drizzle_migrations').results,
+ ).toEqual(
+ migrations.journal.entries.map((entry) => ({ created_at: entry.when })),
+ )
+ expect(
+ connection.execute(
+ "SELECT name FROM sqlite_master WHERE name = 'pets_owner_id'",
+ ).results,
+ ).toEqual([{ name: 'pets_owner_id' }])
+
+ await db.insert(users).values({ id: 1, name: 'Dave' })
+ await db.insert(pets).values({ id: 7, name: 'Rex', ownerId: 1 })
+ expect(
+ await db
+ .select({ owner: users.name, pet: pets.name })
+ .from(pets)
+ .innerJoin(users, eq(pets.ownerId, users.id)),
+ ).toEqual([{ owner: 'Dave', pet: 'Rex' }])
+ })
+
+ it('rejects when a journal entry has no migration', async () => {
+ const db = drizzle(open({ name: 'migrate' }) as NitroSQLiteConnection)
+
+ await expect(
+ migrate(db, { ...migrations, migrations: {} }),
+ ).rejects.toThrow('Missing migration: 0000_init')
+ })
+})
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/__tests__/useMigrations.test.ts b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/useMigrations.test.ts
new file mode 100644
index 00000000..cf40fac3
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/__tests__/useMigrations.test.ts
@@ -0,0 +1,86 @@
+import { createElement, StrictMode } from 'react'
+import { act, create } from 'react-test-renderer'
+import type { ReactTestRenderer } from 'react-test-renderer'
+import { open, resetAllDatabases } from 'react-native-nitro-sqlite/mock'
+import type { NitroSQLiteConnection } from 'react-native-nitro-sqlite'
+import { drizzle, useMigrations } from '../index'
+import type { MigrationState, NitroSQLiteDatabase } from '../index'
+import migrations from './fixtures/drizzle/migrations'
+
+let renderer: ReactTestRenderer | undefined
+
+afterEach(async () => {
+ await act(async () => renderer?.unmount())
+ renderer = undefined
+ jest.restoreAllMocks()
+ resetAllDatabases()
+})
+
+it('finishes migrations after a Strict Mode effect replay', async () => {
+ const connection = open({ name: 'strict-migrate' }) as NitroSQLiteConnection
+ const db = drizzle(connection)
+ let state: MigrationState | undefined
+ function Migrations() {
+ state = useMigrations(db, migrations)
+ return null
+ }
+
+ await act(async () => {
+ renderer = create(
+ createElement(StrictMode, null, createElement(Migrations)),
+ )
+ })
+
+ expect(state).toEqual({ success: true })
+ expect(
+ connection.execute('SELECT created_at FROM __drizzle_migrations').results,
+ ).toHaveLength(1)
+})
+
+it('ignores an earlier database result while the current migration is pending', async () => {
+ const firstDb = drizzle(
+ open({ name: 'first-migrate' }) as NitroSQLiteConnection,
+ )
+ const nextDb = drizzle(
+ open({ name: 'next-migrate' }) as NitroSQLiteConnection,
+ )
+ const first = Promise.withResolvers()
+ const next = Promise.withResolvers()
+ jest.spyOn(firstDb.dialect, 'migrate').mockReturnValue(first.promise)
+ jest.spyOn(nextDb.dialect, 'migrate').mockReturnValue(next.promise)
+ let state: MigrationState | undefined
+ function Migrations({ db }: { db: NitroSQLiteDatabase }) {
+ state = useMigrations(db, migrations)
+ return null
+ }
+
+ await act(async () => {
+ renderer = create(createElement(Migrations, { db: firstDb }))
+ })
+ await act(async () => {
+ renderer?.update(createElement(Migrations, { db: nextDb }))
+ })
+ await act(async () => first.resolve())
+
+ expect(state).toEqual({ success: false })
+
+ await act(async () => next.resolve())
+ expect(state).toEqual({ success: true })
+})
+
+it('reports a migration failure', async () => {
+ const db = drizzle(open({ name: 'failed-migrate' }) as NitroSQLiteConnection)
+ const missing = { ...migrations, migrations: {} }
+ let state: MigrationState | undefined
+ function Migrations() {
+ state = useMigrations(db, missing)
+ return null
+ }
+
+ await act(async () => {
+ renderer = create(createElement(Migrations))
+ })
+
+ expect(state?.success).toBe(false)
+ expect(state?.error?.message).toBe('Missing migration: 0000_init')
+})
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/driver.ts b/packages/react-native-nitro-sqlite-drizzle/src/driver.ts
new file mode 100644
index 00000000..136c4201
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/driver.ts
@@ -0,0 +1,87 @@
+import {
+ createTableRelationsHelpers,
+ DefaultLogger,
+ entityKind,
+ extractTablesRelationalConfig,
+} from 'drizzle-orm'
+import type {
+ DrizzleConfig,
+ RelationalSchemaConfig,
+ TablesRelationalConfig,
+} from 'drizzle-orm'
+import { BaseSQLiteDatabase, SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core'
+import type {
+ NitroSQLiteConnection,
+ NitroSQLiteRawQueryResult,
+} from 'react-native-nitro-sqlite'
+import { NitroSQLiteSession } from './session'
+
+/**
+ * Asynchronous Drizzle database backed by an open Nitro SQLite connection.
+ * Create it with {@link drizzle}; queries and prepared queries remain usable
+ * until you close the connection. The database does not own or close it.
+ * Use the callback's transaction handle for every query inside a transaction.
+ * @typeParam TSchema Tables and relations supplied in the Drizzle configuration.
+ */
+export class NitroSQLiteDatabase<
+ TSchema extends Record = Record,
+> extends BaseSQLiteDatabase<'async', NitroSQLiteRawQueryResult, TSchema> {
+ /** @internal Drizzle entity discriminator. */
+ static override readonly [entityKind]: string = 'NitroSQLiteDatabase'
+
+ /** Connection attached by {@link drizzle}. Close it after all queries finish. */
+ $client?: NitroSQLiteConnection
+}
+
+/**
+ * Create a Drizzle database whose queries run asynchronously on a Nitro SQLite connection.
+ * Creating the wrapper does not open, migrate, or close the connection.
+ * @typeParam TSchema Tables and relations supplied in `config.schema`.
+ * @param connection Connection returned by `open()`.
+ * @param config Drizzle options such as `schema`, `logger`, and `casing`.
+ * @returns A database whose `$client` is the supplied connection.
+ * @throws If Drizzle cannot read the supplied schema configuration.
+ * Query promises reject on binding, SQLite, or closed-connection errors.
+ */
+export function drizzle<
+ TSchema extends Record = Record,
+>(
+ connection: NitroSQLiteConnection,
+ config: DrizzleConfig = {},
+): NitroSQLiteDatabase & { $client: NitroSQLiteConnection } {
+ const dialect = new SQLiteAsyncDialect({ casing: config.casing })
+ let logger
+ if (config.logger === true) {
+ logger = new DefaultLogger()
+ } else if (config.logger !== false) {
+ logger = config.logger
+ }
+
+ let schema: RelationalSchemaConfig | undefined
+ if (config.schema) {
+ const tablesConfig = extractTablesRelationalConfig(
+ config.schema,
+ createTableRelationsHelpers,
+ )
+ schema = {
+ fullSchema: config.schema,
+ schema: tablesConfig.tables,
+ tableNamesMap: tablesConfig.tableNamesMap,
+ }
+ }
+
+ const session = new NitroSQLiteSession(
+ connection,
+ connection,
+ dialect,
+ schema,
+ { logger },
+ )
+ const db = new NitroSQLiteDatabase(
+ 'async',
+ dialect,
+ session,
+ schema,
+ ) as NitroSQLiteDatabase
+ return Object.assign(db, { $client: connection })
+}
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/drizzle-orm.ts b/packages/react-native-nitro-sqlite-drizzle/src/drizzle-orm.ts
new file mode 100644
index 00000000..3bc1e376
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/drizzle-orm.ts
@@ -0,0 +1,60 @@
+// drizzle-orm 0.45 ships these at runtime but marks them internal, so its type declarations omit them.
+/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-shadow -- merged declarations must repeat drizzle-orm's type parameters */
+import type {
+ AnyColumn,
+ ExtractTablesWithRelations,
+ SelectedFieldsOrdered,
+ TablesRelationalConfig,
+} from 'drizzle-orm'
+import type {
+ SQLiteAsyncDialect,
+ SQLiteSession,
+ SQLiteSyncDialect,
+} from 'drizzle-orm/sqlite-core'
+
+declare module 'drizzle-orm/utils' {
+ export function mapResultRow(
+ columns: SelectedFieldsOrdered,
+ row: unknown[],
+ joinsNotNullableMap: Record | undefined,
+ ): TResult
+}
+
+declare module 'drizzle-orm/sqlite-core/session' {
+ interface SQLitePreparedQuery {
+ joinsNotNullableMap?: Record
+ }
+
+ interface SQLiteSession<
+ TResultKind extends 'sync' | 'async',
+ TRunResult,
+ TFullSchema extends Record,
+ TSchema extends TablesRelationalConfig,
+ > {
+ readonly dialect: {
+ sync: SQLiteSyncDialect
+ async: SQLiteAsyncDialect
+ }[TResultKind]
+ }
+}
+
+declare module 'drizzle-orm/sqlite-core/db' {
+ interface BaseSQLiteDatabase<
+ TResultKind extends 'sync' | 'async',
+ TRunResult,
+ TFullSchema extends Record = Record,
+ TSchema extends
+ TablesRelationalConfig = ExtractTablesWithRelations,
+ > {
+ readonly dialect: {
+ sync: SQLiteSyncDialect
+ async: SQLiteAsyncDialect
+ }[TResultKind]
+ readonly session: SQLiteSession<
+ TResultKind,
+ TRunResult,
+ TFullSchema,
+ TSchema
+ >
+ }
+}
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/index.ts b/packages/react-native-nitro-sqlite-drizzle/src/index.ts
new file mode 100644
index 00000000..c971bad2
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/index.ts
@@ -0,0 +1,9 @@
+export { drizzle, NitroSQLiteDatabase } from './driver'
+export { migrate, useMigrations } from './migrator'
+export type { MigrationConfig, MigrationState } from './migrator'
+export {
+ NitroSQLitePreparedQuery,
+ NitroSQLiteSession,
+ NitroSQLiteTransaction,
+} from './session'
+export type { NitroSQLiteSessionOptions } from './session'
diff --git a/packages/react-native-nitro-sqlite-drizzle/src/migrator.ts b/packages/react-native-nitro-sqlite-drizzle/src/migrator.ts
new file mode 100644
index 00000000..a54af385
--- /dev/null
+++ b/packages/react-native-nitro-sqlite-drizzle/src/migrator.ts
@@ -0,0 +1,112 @@
+import { useEffect, useState } from 'react'
+import type { MigrationMeta } from 'drizzle-orm/migrator'
+import type { NitroSQLiteDatabase } from './driver'
+import './drizzle-orm'
+
+const pendingMigrations = new WeakMap