diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 68c3cf2..c26566e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,7 +39,7 @@ jobs: - name: Upload Code Coverage uses: codecov/codecov-action@v4 env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} examples-test: needs: code-test @@ -123,21 +123,13 @@ jobs: exit 1 - name: remove with-javascript-express - run: rm -rf examples/with-javascript-express + run: rm -rf examples/with-javascript-express/* - name: node with-plain-javascript working-directory: examples/with-plain-javascript run: | npm i - app_log=$(mktemp) - npm start > "$app_log" 2>&1 & - - app_pid=$! - sleep 3 - kill "$app_pid" 2>/dev/null - - node_test=$(<"$app_log") - rm "$app_log" + node_test=$(npm start) if [[ "$node_test" == *"Composer: 'Angus Young, Malcolm Young, Brian Johnson',"* ]]; then echo "✅ node with-plain-javascript test passed" @@ -152,15 +144,8 @@ jobs: if [ "$RUNNER_OS" != "Windows" ]; then bun i #re-installing dependencies in windows with bash causes a panic fi - app_log=$(mktemp) - bun start > "$app_log" 2>&1 & - - app_pid=$! - sleep 3 - kill "$app_pid" 2>/dev/null - - bun_test=$(<"$app_log") - rm "$app_log" + + bun_test=$(bun start) if [[ "$bun_test" == *"Composer: 'Angus Young, Malcolm Young, Brian Johnson',"* ]]; then echo "✅ bun with-plain-javascript test passed" @@ -172,15 +157,7 @@ jobs: - name: deno with-plain-javascript working-directory: examples/with-plain-javascript run: | - app_log=$(mktemp) - deno run start > "$app_log" 2>&1 & - - app_pid=$! - sleep 3 - kill "$app_pid" 2>/dev/null - - deno_test=$(<"$app_log") - rm "$app_log" + deno_test=$(deno run start) if [[ "$deno_test" == *"Composer: 'Angus Young, Malcolm Young, Brian Johnson',"* ]]; then echo "✅ deno with-plain-javascript test passed" @@ -191,7 +168,7 @@ jobs: - name: remove with-plain-javascript if: matrix.os != 'windows-latest' #kill command doesn not work on windows with bash, we can skip this step - run: rm -rf examples/with-plain-javascript + run: rm -rf examples/with-plain-javascript/* - name: node with-typescript-knex working-directory: examples/with-typescript-knex @@ -222,7 +199,7 @@ jobs: exit 1 - name: remove with-typescript-knex - run: rm -rf examples/with-typescript-knex + run: rm -rf examples/with-typescript-knex/* - name: node with-typescript-nextjs working-directory: examples/with-typescript-nextjs @@ -275,8 +252,7 @@ jobs: exit 1 - name: remove with-typescript-nextjs - if: matrix.os != 'ubuntu-latest' #rm: cannot remove examples/with-typescript-nextjs: Directory not empty - run: rm -rf examples/with-typescript-nextjs + run: rm -rf examples/with-typescript-nextjs/* - name: node with-javascript-vite if: matrix.os != 'LinuxARM64' @@ -305,7 +281,7 @@ jobs: PW_DISABLE_TS_ESM: true - name: remove with-javascript-vite - run: rm -rf examples/with-javascript-vite + run: rm -rf examples/with-javascript-vite/* - name: node with-javascript-browser if: matrix.os != 'LinuxARM64' @@ -326,7 +302,7 @@ jobs: command: cd examples/with-javascript-browser && deno --allow-all test.cjs - name: remove with-javascript-browser - run: rm -rf examples/with-javascript-browser + run: rm -rf examples/with-javascript-browser/* rn-ios-test: needs: code-test @@ -362,7 +338,8 @@ jobs: xcode-version: 14.3 - name: build driver - run: npm i && npm run build && echo "DRIVER=$(npm pack --json | jq '.[0].filename')" >> $GITHUB_ENV + # distutils is required for the driver build (dep. `node-gyp`) and it was removed since python 3.13 + run: brew install python-setuptools && npm i && npm run build && echo "DRIVER=$(npm pack --json | jq '.[0].filename')" >> $GITHUB_ENV - name: install driver working-directory: examples/with-typescript-react-native @@ -489,7 +466,8 @@ jobs: xcode-version: 14.3 - name: build driver - run: npm i && npm run build && echo "DRIVER=$(npm pack --json | jq '.[0].filename')" >> $GITHUB_ENV + # distutils is required for the driver build (dep. `node-gyp`) and it was removed since python 3.13 + run: brew install python-setuptools && npm i && npm run build && echo "DRIVER=$(npm pack --json | jq '.[0].filename')" >> $GITHUB_ENV - name: install driver working-directory: examples/with-javascript-expo diff --git a/examples/with-javascript-browser/index.html b/examples/with-javascript-browser/index.html index e5b9656..e4d1bc4 100644 --- a/examples/with-javascript-browser/index.html +++ b/examples/with-javascript-browser/index.html @@ -44,33 +44,28 @@

Results:

messages.prepend(item); }; - // socket is connected the first time the sql button is clicked and stays connected until the page is refreshed - var database = null; - sendButton.addEventListener('click', () => { - if (!database || !database.isConnected()) { - // Get the input element by ID - var connectionStringinputElement = document.getElementById('connectionStringInput'); - var connectionstring = connectionStringinputElement.value; - // connect via websocket to the gateway on the same server - const connectionConfig = { - gatewayUrl: `${window.location.protocol === 'https:' ? 'wss' : 'ws' - }://${window.location.hostname}:4000`, - connectionstring: connectionstring, - }; - database = new window.sqlitecloud.Database( - connectionConfig, - (error) => { - if (error) { - database = null; - appendMessage(`connection error: ${error}`); - } else { - console.log('connected'); - appendMessage(`connected`); - } + // Get the input element by ID + var connectionStringinputElement = document.getElementById('connectionStringInput'); + var connectionstring = connectionStringinputElement.value; + // connect via websocket to the gateway on the same server + const connectionConfig = { + gatewayUrl: `${window.location.protocol === 'https:' ? 'wss' : 'ws' + }://${window.location.hostname}:4000`, + connectionstring: connectionstring, + }; + var database = new window.sqlitecloud.Database( + connectionConfig, + (error) => { + if (error) { + database = null; + appendMessage(`connection error: ${error}`); + } else { + console.log('connected'); + appendMessage(`connected`); } - ); - } + } + ); var messageInputElement = document.getElementById('messageInput'); const sql = messageInputElement.value; diff --git a/examples/with-javascript-expo/components/AddTaskModal.js b/examples/with-javascript-expo/components/AddTaskModal.js index 57b6e47..757ccf1 100644 --- a/examples/with-javascript-expo/components/AddTaskModal.js +++ b/examples/with-javascript-expo/components/AddTaskModal.js @@ -29,11 +29,15 @@ export default AddTaskModal = ({ }; const getTags = async () => { + let db = null; try { - const tags = await getDbConnection().sql("SELECT * FROM tags"); + db = getDbConnection(); + const tags = await db.sql("SELECT * FROM tags"); setTagsList(tags); } catch (error) { console.error("Error getting tags", error); + } finally { + db?.close(); } }; diff --git a/examples/with-javascript-expo/db/dbConnection.js b/examples/with-javascript-expo/db/dbConnection.js index 26958e4..4d0b8dd 100644 --- a/examples/with-javascript-expo/db/dbConnection.js +++ b/examples/with-javascript-expo/db/dbConnection.js @@ -1,14 +1,7 @@ import { DATABASE_URL } from "@env"; import { Database } from "@sqlitecloud/drivers"; -/** - * @type {Database} - */ -let database = null; export default function getDbConnection() { - if (!database || !database.isConnected()) { - database = new Database(DATABASE_URL); - } - return database; + return new Database(DATABASE_URL); } \ No newline at end of file diff --git a/examples/with-javascript-expo/hooks/useCategories.js b/examples/with-javascript-expo/hooks/useCategories.js index db425b0..fe0502d 100644 --- a/examples/with-javascript-expo/hooks/useCategories.js +++ b/examples/with-javascript-expo/hooks/useCategories.js @@ -1,78 +1,73 @@ -import { useState, useEffect } from "react"; -import getDbConnection from "../db/dbConnection"; +import { useState, useEffect } from 'react' +import getDbConnection from '../db/dbConnection' const useCategories = () => { - const [moreCategories, setMoreCategories] = useState(["Work", "Personal"]); + const [moreCategories, setMoreCategories] = useState(['Work', 'Personal']) const getCategories = async () => { + let db = null; try { - const tags = await getDbConnection().sql("SELECT * FROM tags"); - const filteredTags = tags.filter((tag) => { - return tag["name"] !== "Work" && tag["name"] !== "Personal"; - }); - setMoreCategories((prevCategories) => [ - ...prevCategories, - ...filteredTags.map((tag) => tag.name), - ]); + db = getDbConnection(); + const tags = await db.sql('SELECT * FROM tags') + const filteredTags = tags.filter(tag => { + return tag['name'] !== 'Work' && tag['name'] !== 'Personal' + }) + setMoreCategories(prevCategories => [...prevCategories, ...filteredTags.map(tag => tag.name)]) } catch (error) { - console.error("Error getting tags/categories", error); + console.error('Error getting tags/categories', error) } - }; + } - const addCategory = async (newCategory) => { + const addCategory = async newCategory => { + let db = null; try { - await getDbConnection().sql( - "INSERT INTO tags (name) VALUES (?) RETURNING *", - newCategory - ); - setMoreCategories((prevCategories) => [...prevCategories, newCategory]); + db = getDbConnection(); + await db.sql('INSERT INTO tags (name) VALUES (?) RETURNING *', newCategory) + setMoreCategories(prevCategories => [...prevCategories, newCategory]) } catch (error) { - console.error("Error adding category", error); + console.error('Error adding category', error) + } finally { + db?.close(); } - }; + } const initializeTables = async () => { + let db = null; try { - const createTasksTable = await getDbConnection().sql( - "CREATE TABLE IF NOT EXISTS tasks (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, isCompleted INT NOT NULL);" - ); + db = getDbConnection(); + const createTasksTable = await db.sql( + 'CREATE TABLE IF NOT EXISTS tasks (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, isCompleted INT NOT NULL);' + ) - const createTagsTable = await getDbConnection().sql( - "CREATE TABLE IF NOT EXISTS tags (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, UNIQUE(name));" - ); + const createTagsTable = await db.sql('CREATE TABLE IF NOT EXISTS tags (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, UNIQUE(name));') - const createTagsTasksTable = await getDbConnection().sql( - "CREATE TABLE IF NOT EXISTS tasks_tags (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, task_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, FOREIGN KEY (task_id) REFERENCES tasks(id), FOREIGN KEY (tag_id) REFERENCES tags(id));" - ); + const createTagsTasksTable = await db.sql( + 'CREATE TABLE IF NOT EXISTS tasks_tags (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, task_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, FOREIGN KEY (task_id) REFERENCES tasks(id), FOREIGN KEY (tag_id) REFERENCES tags(id));' + ) - if ( - createTasksTable === "OK" && - createTagsTable === "OK" && - createTagsTasksTable === "OK" - ) { - console.log("Successfully created tables"); + if (createTasksTable === 'OK' && createTagsTable === 'OK' && createTagsTasksTable === 'OK') { + console.log('Successfully created tables') - await getDbConnection().sql("INSERT OR IGNORE INTO tags (name) VALUES (?)", "Work"); - await getDbConnection().sql( - "INSERT OR IGNORE INTO tags (name) VALUES (?)", - "Personal" - ); - getCategories(); + await db.sql('INSERT OR IGNORE INTO tags (name) VALUES (?)', 'Work') + await db.sql('INSERT OR IGNORE INTO tags (name) VALUES (?)', 'Personal') + getCategories() } } catch (error) { - console.error("Error creating tables", error); + console.error('Error creating tables', error) + } finally { + db?.close(); } - }; + } useEffect(() => { - initializeTables(); - }, []); + initializeTables() + }, []) return { moreCategories, addCategory, - getCategories, - }; -}; + getCategories + } +} -export default useCategories; +export default useCategories diff --git a/examples/with-javascript-expo/hooks/useTasks.js b/examples/with-javascript-expo/hooks/useTasks.js index 0a7ccad..6e7906e 100644 --- a/examples/with-javascript-expo/hooks/useTasks.js +++ b/examples/with-javascript-expo/hooks/useTasks.js @@ -5,10 +5,12 @@ const useTasks = (tag = null) => { const [taskList, setTaskList] = useState([]); const getTasks = useCallback(async () => { + let db = null; try { let result; + db = getDbConnection(); if (tag) { - result = await getDbConnection().sql( + result = await db.sql( ` SELECT tasks.*, tags.id AS tag_id, tags.name AS tag_name FROM tasks @@ -19,7 +21,7 @@ const useTasks = (tag = null) => { ); setTaskList(result); } else { - result = await getDbConnection().sql(` + result = await db.sql(` SELECT tasks.*, tags.id AS tag_id, tags.name AS tag_name FROM tasks JOIN tasks_tags ON tasks.id = tasks_tags.task_id @@ -28,12 +30,16 @@ const useTasks = (tag = null) => { } } catch (error) { console.error("Error getting tasks", error); + } finally { + db?.close(); } }, [tag]); const updateTask = async (completedStatus, taskId) => { + let db = null; try { - await getDbConnection().sql( + db = getDbConnection(); + await db.sql( "UPDATE tasks SET isCompleted=? WHERE id=? RETURNING *", completedStatus, taskId @@ -41,13 +47,17 @@ const useTasks = (tag = null) => { getTasks(); } catch (error) { console.error("Error updating tasks", error); + } finally { + db?.close(); } }; const addTaskTag = async (newTask, tag) => { + let db = null; try { + db = getDbConnection(); if (tag.id) { - const addNewTask = await getDbConnection().sql( + const addNewTask = await db.sql( "INSERT INTO tasks (title, isCompleted) VALUES (?, ?) RETURNING *", newTask.title, newTask.isCompleted @@ -55,13 +65,13 @@ const useTasks = (tag = null) => { addNewTask[0].tag_id = tag.id; addNewTask[0].tag_name = tag.name; setTaskList([...taskList, addNewTask[0]]); - await getDbConnection().sql( + await db.sql( "INSERT INTO tasks_tags (task_id, tag_id) VALUES (?, ?)", addNewTask[0].id, tag.id ); } else { - const addNewTaskNoTag = await getDbConnection().sql( + const addNewTaskNoTag = await db.sql( "INSERT INTO tasks (title, isCompleted) VALUES (?, ?) RETURNING *", newTask.title, newTask.isCompleted @@ -70,17 +80,23 @@ const useTasks = (tag = null) => { } } catch (error) { console.error("Error adding task to database", error); + } finally { + db?.close(); } }; const deleteTask = async (taskId) => { + let db = null; try { - await getDbConnection().sql("DELETE FROM tasks_tags WHERE task_id=?", taskId); - const result = await getDbConnection().sql("DELETE FROM tasks WHERE id=?", taskId); + db = getDbConnection(); + await db.sql("DELETE FROM tasks_tags WHERE task_id=?", taskId); + const result = await db.sql("DELETE FROM tasks WHERE id=?", taskId); console.log(`Deleted ${result.totalChanges} task`); getTasks(); } catch (error) { console.error("Error deleting task", error); + } finally { + db?.close(); } }; diff --git a/examples/with-javascript-express/app.js b/examples/with-javascript-express/app.js index ead1269..80af967 100644 --- a/examples/with-javascript-express/app.js +++ b/examples/with-javascript-express/app.js @@ -14,9 +14,18 @@ app.use(express.json()) /* http://localhost:3001/ returns chinook tracks as json */ app.get('/', async function (req, res, next) { - var database = new sqlitecloud.Database(DATABASE_URL) - var tracks = await database.sql('USE DATABASE chinook.sqlite; SELECT * FROM tracks LIMIT 20;') - res.send({ tracks }) + var database = null + try { + database = new sqlitecloud.Database(DATABASE_URL) + var tracks = await database.sql('USE DATABASE chinook.sqlite; SELECT * FROM tracks LIMIT 20;') + res.send({ tracks }) + } catch (error) { + res.send({ error: error.message }) + } finally { + if (database) { + database.close() + } + } }) const port = process.env.PORT || 3000 diff --git a/examples/with-javascript-vite/src/App.jsx b/examples/with-javascript-vite/src/App.jsx index cb79747..2d05d69 100644 --- a/examples/with-javascript-vite/src/App.jsx +++ b/examples/with-javascript-vite/src/App.jsx @@ -1,30 +1,28 @@ import { useEffect, useState } from "react"; import { Database } from "@sqlitecloud/drivers"; -let db = null - -function getDatabase() { - if (!db || !db.isConnected()) { - db = new Database(import.meta.env.VITE_DATABASE_URL); - } - - return db; -} - function App() { const [data, setData] = useState([]); const getAlbums = async () => { - const result = await getDatabase().sql(` - USE DATABASE chinook.sqlite; - SELECT albums.AlbumId as id, albums.Title as title, artists.name as artist - FROM albums - INNER JOIN artists - WHERE artists.ArtistId = albums.ArtistId - LIMIT 20; - `); - setData(result); + let database = null; + try { + database = new Database(import.meta.env.VITE_DATABASE_URL) + const result = await database.sql(` + USE DATABASE chinook.sqlite; + SELECT albums.AlbumId as id, albums.Title as title, artists.name as artist + FROM albums + INNER JOIN artists + WHERE artists.ArtistId = albums.ArtistId + LIMIT 20; + `); + setData(result); + } catch (error) { + console.error("Error getting albums", error); + } finally { + database.close(); + } }; useEffect(() => { diff --git a/examples/with-plain-javascript/app.js b/examples/with-plain-javascript/app.js index f0e44c4..14040e5 100644 --- a/examples/with-plain-javascript/app.js +++ b/examples/with-plain-javascript/app.js @@ -9,12 +9,19 @@ var DATABASE_URL = process.env.DATABASE_URL console.assert(DATABASE_URL, 'DATABASE_URL environment variable not set in .env') async function selectTracks() { - // create a connection with sqlitecloud - var database = new sqlitecloud.Database(DATABASE_URL) + var database = null + try { + // create a connection with sqlitecloud + database = new sqlitecloud.Database(DATABASE_URL) - // run async query - var tracks = await database.sql('USE DATABASE chinook.sqlite; SELECT * FROM tracks LIMIT 20;') - console.log(`selectTracks returned:`, tracks) + // run async query + var tracks = await database.sql('USE DATABASE chinook.sqlite; SELECT * FROM tracks LIMIT 20;') + console.log(`selectTracks returned:`, tracks) + } catch (error) { + console.error(`selectTracks error:`, error) + } finally { + database.close() + } // You can also use all the regular sqlite3 api with callbacks, see: // https://docs.sqlitecloud.io/docs/sdk/js/intro diff --git a/examples/with-typescript-nextjs/app/api/hello/route.ts b/examples/with-typescript-nextjs/app/api/hello/route.ts index a191eff..983841f 100644 --- a/examples/with-typescript-nextjs/app/api/hello/route.ts +++ b/examples/with-typescript-nextjs/app/api/hello/route.ts @@ -12,11 +12,18 @@ console.assert(DATABASE_URL, 'Please configure a .env file with DATABASE_URL poi // route for /api/hello export async function GET(request: NextRequest) { // connect to database using connection string provided in https://dashboard.sqlitecloud.io/ - const database = new Database(DATABASE_URL) + let database + try { + database = new Database(DATABASE_URL) - // retrieve rows from chinook database using a plain SQL query - const tracks = await database.sql('USE DATABASE chinook.sqlite; SELECT * FROM tracks LIMIT 20;') + // retrieve rows from chinook database using a plain SQL query + const tracks = await database.sql('USE DATABASE chinook.sqlite; SELECT * FROM tracks LIMIT 20;') - // return as json response - return NextResponse.json<{ data: any }>({ data: tracks }) + // return as json response + return NextResponse.json<{ data: any }>({ data: tracks }) + } catch (error) { + return NextResponse.json({ error }, { status: 500 }) + } finally { + database?.close() + } } diff --git a/examples/with-typescript-react-native/App.tsx b/examples/with-typescript-react-native/App.tsx index b71df81..454e2dd 100644 --- a/examples/with-typescript-react-native/App.tsx +++ b/examples/with-typescript-react-native/App.tsx @@ -8,12 +8,19 @@ export default function App() { useEffect(() => { async function getAlbums() { - const db = new Database(`${DATABASE_URL}`); + let db = null; + try { + db = new Database(`${DATABASE_URL}`); - const result = - await db.sql('USE DATABASE chinook.sqlite; SELECT albums.AlbumId as id, albums.Title as title, artists.name as artist FROM albums INNER JOIN artists WHERE artists.ArtistId = albums.ArtistId LIMIT 20;'); + const result = + await db.sql('USE DATABASE chinook.sqlite; SELECT albums.AlbumId as id, albums.Title as title, artists.name as artist FROM albums INNER JOIN artists WHERE artists.ArtistId = albums.ArtistId LIMIT 20;'); - setAlbums(result); + setAlbums(result); + } catch (error) { + console.error(error); + } finally { + db?.close(); + } } getAlbums(); diff --git a/package-lock.json b/package-lock.json index 4ed0005..b1e7e3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sqlitecloud/drivers", - "version": "1.0.422", + "version": "1.0.438", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@sqlitecloud/drivers", - "version": "1.0.422", + "version": "1.0.438", "license": "MIT", "dependencies": { "buffer": "^6.0.3", @@ -18,20 +18,20 @@ "devDependencies": { "@types/jest": "^29.5.14", "@types/lz4": "^0.6.4", - "@types/node": "^22.13.4", + "@types/node": "^22.13.8", "@types/whatwg-url": "^13.0.0", "dotenv": "^16.4.7", "dotenv-cli": "^8.0.0", "husky": "^9.1.7", "jest": "^29.7.0", "jest-html-reporter": "^4.0.1", - "prettier": "^3.5.2", + "prettier": "^3.5.3", "sqlite3": "^5.1.7", - "ts-jest": "^29.2.5", + "ts-jest": "^29.2.6", "ts-node": "^10.9.2", - "typedoc": "^0.27.7", + "typedoc": "^0.27.9", "typedoc-plugin-markdown": "^4.4.2", - "typescript": "^5.7.3", + "typescript": "^5.8.2", "webpack": "^5.98.0", "webpack-cli": "^6.0.1" }, @@ -8345,9 +8345,9 @@ } }, "node_modules/@types/node": { - "version": "22.13.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", - "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", + "version": "22.13.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.8.tgz", + "integrity": "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -13858,9 +13858,9 @@ } }, "node_modules/prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "dev": true, "license": "MIT", "bin": { @@ -14589,9 +14589,9 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -15384,9 +15384,9 @@ } }, "node_modules/ts-jest": { - "version": "29.2.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", - "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "version": "29.2.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.6.tgz", + "integrity": "sha512-yTNZVZqc8lSixm+QGVFcPe6+yj7+TWZwIesuOWvfcn4B9bz5x4NDzVCQQjOs7Hfouu36aEqfEbo9Qpo+gq8dDg==", "dev": true, "license": "MIT", "dependencies": { @@ -15397,7 +15397,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.6.3", + "semver": "^7.7.1", "yargs-parser": "^21.1.1" }, "bin": { @@ -15498,9 +15498,9 @@ } }, "node_modules/typedoc": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.7.tgz", - "integrity": "sha512-K/JaUPX18+61W3VXek1cWC5gwmuLvYTOXJzBvD9W7jFvbPnefRnCHQCEPw7MSNrP/Hj7JJrhZtDDLKdcYm6ucg==", + "version": "0.27.9", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.9.tgz", + "integrity": "sha512-/z585740YHURLl9DN2jCWe6OW7zKYm6VoQ93H0sxZ1cwHQEQrUn5BJrEnkWhfzUdyO+BLGjnKUZ9iz9hKloFDw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -15517,7 +15517,7 @@ "node": ">= 18" }, "peerDependencies": { - "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x" + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" } }, "node_modules/typedoc-plugin-markdown": { @@ -15556,9 +15556,9 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "devOptional": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 0b2ff65..df49d59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sqlitecloud/drivers", - "version": "1.0.422", + "version": "1.0.438", "description": "SQLiteCloud drivers for Typescript/Javascript in edge, web and node clients", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -57,20 +57,20 @@ "devDependencies": { "@types/jest": "^29.5.14", "@types/lz4": "^0.6.4", - "@types/node": "^22.13.4", + "@types/node": "^22.13.8", "@types/whatwg-url": "^13.0.0", "dotenv": "^16.4.7", "dotenv-cli": "^8.0.0", "husky": "^9.1.7", "jest": "^29.7.0", "jest-html-reporter": "^4.0.1", - "prettier": "^3.5.2", + "prettier": "^3.5.3", "sqlite3": "^5.1.7", - "ts-jest": "^29.2.5", + "ts-jest": "^29.2.6", "ts-node": "^10.9.2", - "typedoc": "^0.27.7", + "typedoc": "^0.27.9", "typedoc-plugin-markdown": "^4.4.2", - "typescript": "^5.7.3", + "typescript": "^5.8.2", "webpack": "^5.98.0", "webpack-cli": "^6.0.1" }, diff --git a/src/drivers/database.ts b/src/drivers/database.ts index 797a221..80aa1f5 100644 --- a/src/drivers/database.ts +++ b/src/drivers/database.ts @@ -96,6 +96,7 @@ export class Database extends EventEmitter { }) .catch(error => { this.handleError(error, callback) + this.close() done(error) }) }) @@ -116,6 +117,7 @@ export class Database extends EventEmitter { }) .catch(error => { this.handleError(error, callback) + this.close() done(error) }) }) @@ -128,21 +130,20 @@ export class Database extends EventEmitter { // we don't wont to silently open a new connection after a disconnession if (this.connection && this.connection.connected) { - this.connection.sendCommands(command, callback) + this.connection.sendCommands(command, (error, results) => { + callback?.call(this, error, results) + done(error) + }) } else { error = new SQLiteCloudError('Connection unavailable. Maybe it got disconnected?', { errorCode: 'ERR_CONNECTION_NOT_ESTABLISHED' }) - this.handleError(error, callback) + callback?.call(this, error, null) + done(error) } - - done(error) }) } /** Handles an error by closing the connection, calling the callback and/or emitting an error event */ private handleError(error: Error, callback?: ConnectionCallback): void { - // an errored connection is thrown out - this.connection?.close() - if (callback) { callback.call(this, error) } else { @@ -382,11 +383,15 @@ export class Database extends EventEmitter { * parameters is emitted, regardless of whether a callback was provided or not. */ public close(callback?: ConnectionCallback): void { - this.operations.clear() - this.connection?.close() + this.operations.enqueue(done => { + this.connection?.close() + + callback?.call(this, null) + this.emitEvent('close') - callback?.call(this, null) - this.emitEvent('close') + this.operations.clear() + done(null) + }) } /** diff --git a/src/drivers/utilities.ts b/src/drivers/utilities.ts index d0fe859..b7075ab 100644 --- a/src/drivers/utilities.ts +++ b/src/drivers/utilities.ts @@ -219,11 +219,26 @@ export function parseconnectionstring(connectionstring: string): SQLiteCloudConf }) const config: SQLiteCloudConfig = { + ...options, username: decodeURIComponent(url.username), password: decodeURIComponent(url.password), + password_hashed: options.password_hashed ? parseBoolean(options.password_hashed) : undefined, host: url.hostname, + // type cast values port: url.port ? parseInt(url.port) : undefined, - ...options + insecure: options.insecure ? parseBoolean(options.insecure) : undefined, + timeout: options.timeout ? parseInt(options.timeout) : undefined, + zerotext: options.zerotext ? parseBoolean(options.zerotext) : undefined, + create: options.create ? parseBoolean(options.create) : undefined, + memory: options.memory ? parseBoolean(options.memory) : undefined, + compression: options.compression ? parseBoolean(options.compression) : undefined, + non_linearizable: options.non_linearizable ? parseBoolean(options.non_linearizable) : undefined, + noblob: options.noblob ? parseBoolean(options.noblob) : undefined, + maxdata: options.maxdata ? parseInt(options.maxdata) : undefined, + maxrows: options.maxrows ? parseInt(options.maxrows) : undefined, + maxrowset: options.maxrowset ? parseInt(options.maxrowset) : undefined, + usewebsocket: options.usewebsocket ? parseBoolean(options.usewebsocket) : undefined, + verbose: options.verbose ? parseBoolean(options.verbose) : undefined } // either you use an apikey or username and password diff --git a/test/connection-ws.test.ts b/test/connection-ws.test.ts index 2772fd3..3cf2fd5 100644 --- a/test/connection-ws.test.ts +++ b/test/connection-ws.test.ts @@ -2,19 +2,17 @@ * connection-ws.test.ts - test connection via socket.io based gateway */ -import { SQLiteCloudError } from '../src/index' import { SQLiteCloudConnection } from '../src/drivers/connection' import { SQLiteCloudWebsocketConnection } from '../src/drivers/connection-ws' +import { SQLiteCloudCommand } from '../src/drivers/types' +import { SQLiteCloudError } from '../src/index' import { - // - CHINOOK_DATABASE_URL, - LONG_TIMEOUT, + EXPECT_SPEED_MS, getChinookConfig, getChinookWebsocketConnection, - WARN_SPEED_MS, - EXPECT_SPEED_MS + LONG_TIMEOUT, + WARN_SPEED_MS } from './shared' -import { SQLiteCloudCommand } from '../src/drivers/types' describe('connection-ws', () => { let chinook: SQLiteCloudConnection diff --git a/test/database.test.ts b/test/database.test.ts index ef46f81..ace2400 100644 --- a/test/database.test.ts +++ b/test/database.test.ts @@ -246,6 +246,33 @@ describe('Database.get', () => { }) }) }) + + it('close() is executed after the previous commands', done => { + // the database enqueue the close command + const chinook = getChinookDatabase() + chinook.get('SELECT * FROM tracks', (err: Error, row?: SQLiteCloudRow) => { + expect(err).toBeNull() + expect(row).toBeDefined() + expect(row).toMatchObject({ + AlbumId: 1, + Bytes: 11170334, + Composer: 'Angus Young, Malcolm Young, Brian Johnson', + GenreId: 1, + MediaTypeId: 1, + Milliseconds: 343719, + Name: 'For Those About To Rock (We Salute You)', + TrackId: 1, + UnitPrice: 0.99 + }) + }) + + // call close() right after the execution + // of the query not in its callback + chinook.close(error => { + expect(error).toBeNull() + done() + }) + }) }) describe('Database.each', () => { diff --git a/test/utilities.test.ts b/test/utilities.test.ts index c524db7..423b3ca 100644 --- a/test/utilities.test.ts +++ b/test/utilities.test.ts @@ -57,7 +57,7 @@ describe('parseconnectionstring', () => { expect(config4).toEqual({ host: 'host', apikey: 'yyy', - maxrows: '42' // only parsing here, validation is later in validateConfiguration + maxrows: 42 // only parsing here, validation is later in validateConfiguration }) }) @@ -138,6 +138,30 @@ describe('parseconnectionstring', () => { database: 'database' }) }) + + it('should parse connection with insecure as bool or number', () => { + let connectionstring = `sqlitecloud://host:1234/database?insecure=true` + let config = parseconnectionstring(connectionstring) + + expect(config.insecure).toBe(true) + + connectionstring = `sqlitecloud://host:1234/database?insecure=1` + config = parseconnectionstring(connectionstring) + + expect(config.insecure).toBe(true) + + connectionstring = `sqlitecloud://host:1234/database?insecure=0` + config = parseconnectionstring(connectionstring) + + expect(config.insecure).toBe(false) + }) + + it('should parse connection with timeout as number', () => { + let connectionstring = `sqlitecloud://host:1234/database?timeout=123` + let config = parseconnectionstring(connectionstring) + + expect(config.timeout).toBe(123) + }) }) describe('getTestingDatabaseName', () => {