@zukhruf/sqlite-transaction 0.0.0-stage → 0.3.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ezzabuzaid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,102 @@
1
- # Temporary Holding Version
1
+ # @zukhruf/sqlite-transaction
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `node:sqlite` has no function that runs work in a transaction. Thus each store writes `BEGIN`, `COMMIT` and `ROLLBACK` itself. After some errors, SQLite ends the transaction itself. Then a `ROLLBACK` fails with `cannot rollback - no transaction is active`, and that error replaces the original error. This package gives one function that runs work in a transaction, and that keeps the original error. This function is also known as a transaction function or a transaction wrapper.
4
+
5
+ The words in these documents have one meaning each. See the glossary in [CONTEXT.md](./CONTEXT.md).
6
+
7
+ ## Use it
8
+
9
+ This project is an experiment. It uses only `node:sqlite`.
10
+
11
+ ```sh
12
+ npm install @zukhruf/sqlite-transaction
13
+ ```
14
+
15
+ ```ts
16
+ import { mkdtempDisposable } from 'node:fs/promises';
17
+ import { tmpdir } from 'node:os';
18
+ import { join } from 'node:path';
19
+ import { DatabaseSync } from 'node:sqlite';
20
+
21
+ import { transaction } from '@zukhruf/sqlite-transaction';
22
+
23
+ await using scratch = await mkdtempDisposable(join(tmpdir(), 'transaction-'));
24
+ using database = new DatabaseSync(join(scratch.path, 'app.sqlite'));
25
+ database.exec('CREATE TABLE notes (body TEXT) STRICT');
26
+
27
+ const rows = transaction(
28
+ database,
29
+ () => {
30
+ database.exec("INSERT INTO notes VALUES ('kept')");
31
+ try {
32
+ transaction(
33
+ database,
34
+ () => {
35
+ database.exec("INSERT INTO notes VALUES ('undone')");
36
+ throw new Error('stop');
37
+ },
38
+ { mode: 'immediate' },
39
+ );
40
+ } catch (error) {
41
+ console.log((error as Error).message);
42
+ }
43
+ return database.prepare('SELECT body FROM notes').all().length;
44
+ },
45
+ { mode: 'immediate' },
46
+ );
47
+ console.log(rows);
48
+ ```
49
+
50
+ Output:
51
+
52
+ ```
53
+ stop
54
+ 1
55
+ ```
56
+
57
+ ## The rules
58
+
59
+ - `transaction(database, work, { mode })` runs `work`. When `work` returns, the transaction commits, and `transaction` gives the value of `work`. When `work` throws, the transaction rolls back, and `transaction` throws the same error.
60
+ - When the connection has no transaction, the call is the owner. It starts the transaction with `BEGIN DEFERRED`, `BEGIN IMMEDIATE` or `BEGIN EXCLUSIVE`, as `mode` tells.
61
+ - When the connection has a transaction, the call works in a savepoint. When `work` throws, only the changes of `work` are undone, and the outer transaction continues. The mode does not apply.
62
+ - The rollback runs only while SQLite holds the transaction. When SQLite ended the transaction itself, `transaction` throws the original error. [ADR 0001](./docs/adr/0001-roll-back-only-while-sqlite-holds-the-transaction.md) tells why.
63
+ - `work` must be synchronous. `node:sqlite` is synchronous, and a transaction cannot stay open across an `await`.
64
+ - `transaction` does not wait for a lock, and it does not try again. The busy timeout of the connection sets how long SQLite waits.
65
+
66
+ ## Errors
67
+
68
+ | Error | When |
69
+ | -------------------- | --------------------------------------------------------------------------------------------------------------------- |
70
+ | `TypeError` | `work` gives a promise. The transaction rolls back. TypeScript also refuses an `async` function. |
71
+ | The original error | `work` throws. The transaction rolls back, or SQLite ended it before. |
72
+ | `database is locked` | `BEGIN IMMEDIATE` or `BEGIN EXCLUSIVE` waits for the busy timeout of the connection, then fails. `work` does not run. |
73
+
74
+ ## The same concepts in other systems
75
+
76
+ | System | What it does |
77
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
78
+ | better-sqlite3 | `db.transaction(fn)` gives a function that runs `fn` in a transaction. A call inside a transaction uses a savepoint. `.deferred`, `.immediate` and `.exclusive` set the mode. It refuses a function that gives a promise. |
79
+ | Python `sqlite3` | `with conn:` commits when the block ends, and rolls back when the block raises an exception. |
80
+ | SQLite automatic rollback | After some errors, for example `SQLITE_IOERR` or `RAISE(ROLLBACK)` in a trigger, SQLite rolls back the transaction itself. See "Response To Errors Within A Transaction" in the SQLite documentation. |
81
+
82
+ ## Documentation
83
+
84
+ - [0001 Roll back only while SQLite holds the transaction](./docs/adr/0001-roll-back-only-while-sqlite-holds-the-transaction.md)
85
+
86
+ ## Development
87
+
88
+ This package is part of the [zukhruf](../../../README.md) workspace. Run the commands from the workspace root.
89
+
90
+ ```sh
91
+ npm install
92
+ npx nx run sqlite-transaction:test # builds, then runs the tests in src/
93
+ npx nx run sqlite-transaction:typecheck # formats, lints, then type checks
94
+ npx nx run sqlite-transaction:build # compiles src/ to dist/
95
+ ```
96
+
97
+ ```
98
+ src/
99
+ index.ts the exports
100
+ transaction.ts transaction and TransactionMode
101
+ transaction.test.ts the tests, on real SQLite files
102
+ ```
@@ -0,0 +1,2 @@
1
+ export { transaction, type TransactionMode } from './transaction.ts';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { transaction } from "./transaction.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAwB,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ export type TransactionMode = 'deferred' | 'immediate' | 'exclusive';
3
+ export declare function transaction<T>(database: Pick<DatabaseSync, 'exec' | 'isTransaction'>, work: () => T extends PromiseLike<unknown> ? never : T, options: {
4
+ readonly mode: TransactionMode;
5
+ }): T;
6
+ //# sourceMappingURL=transaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC;AAUrE,wBAAgB,WAAW,CAAC,CAAC,EAC3B,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,eAAe,CAAC,EACtD,IAAI,EAAE,MAAM,CAAC,SAAS,WAAW,CAAC,OAAO,CAAC,GAAG,KAAK,GAAG,CAAC,EACtD,OAAO,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;CAAE,GAC1C,CAAC,CAkBH"}
@@ -0,0 +1,30 @@
1
+ // SQLite lets one name nest: ROLLBACK TO and RELEASE find the innermost.
2
+ const SAVEPOINT = 'zukhruf_transaction';
3
+ // Runs work in a transaction: commits when it returns, rolls back when it
4
+ // throws. Inside a transaction, work runs in a savepoint and the mode does not
5
+ // apply. The rollback runs only while SQLite still holds the transaction:
6
+ // after some errors SQLite ends the transaction itself, and a ROLLBACK then
7
+ // would replace the original error.
8
+ export function transaction(database, work, options) {
9
+ const owns = !database.isTransaction;
10
+ database.exec(owns ? `BEGIN ${options.mode.toUpperCase()}` : `SAVEPOINT ${SAVEPOINT}`);
11
+ try {
12
+ const result = work();
13
+ if (thenable(result))
14
+ throw new TypeError('A transaction function cannot return a promise');
15
+ database.exec(owns ? 'COMMIT' : `RELEASE ${SAVEPOINT}`);
16
+ return result;
17
+ }
18
+ catch (error) {
19
+ if (database.isTransaction)
20
+ database.exec(owns ? 'ROLLBACK' : `ROLLBACK TO ${SAVEPOINT}; RELEASE ${SAVEPOINT}`);
21
+ throw error;
22
+ }
23
+ }
24
+ function thenable(value) {
25
+ return (typeof value === 'object' &&
26
+ value !== null &&
27
+ 'then' in value &&
28
+ typeof value.then === 'function');
29
+ }
30
+ //# sourceMappingURL=transaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction.js","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAKA,yEAAyE;AACzE,MAAM,SAAS,GAAG,qBAAqB,CAAC;AAExC,0EAA0E;AAC1E,+EAA+E;AAC/E,0EAA0E;AAC1E,4EAA4E;AAC5E,oCAAoC;AACpC,MAAM,UAAU,WAAW,CACzB,QAAsD,EACtD,IAAsD,EACtD,OAA2C;IAE3C,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC;IACrC,QAAQ,CAAC,IAAI,CACX,IAAI,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,SAAS,EAAE,CACxE,CAAC;IACF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,EAAE,CAAC;QACtB,IAAI,QAAQ,CAAC,MAAM,CAAC;YAClB,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;QACxE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,SAAS,EAAE,CAAC,CAAC;QACxD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,QAAQ,CAAC,aAAa;YACxB,QAAQ,CAAC,IAAI,CACX,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe,SAAS,aAAa,SAAS,EAAE,CACrE,CAAC;QACJ,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACf,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CACjC,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,42 @@
1
1
  {
2
2
  "name": "@zukhruf/sqlite-transaction",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.32",
4
+ "description": "A transaction function for node:sqlite. It runs a function in a transaction, commits when the function returns, and rolls back when it throws. A call inside a transaction works in a savepoint. After SQLite ends the transaction itself, the caller gets the original error, not \"cannot rollback\".",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/JanuaryLabs/zukhruf",
9
+ "directory": "packages/sqlite/transaction"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/JanuaryLabs/zukhruf/issues"
13
+ },
14
+ "homepage": "https://github.com/JanuaryLabs/zukhruf/tree/main/packages/sqlite/transaction#readme",
15
+ "keywords": [
16
+ "sqlite",
17
+ "node-sqlite",
18
+ "transaction",
19
+ "savepoint",
20
+ "rollback",
21
+ "nested-transaction"
22
+ ],
23
+ "type": "module",
24
+ "exports": {
25
+ "./package.json": "./package.json",
26
+ ".": {
27
+ "@zukhruf/source": "./src/index.ts",
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "default": "./dist/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "!dist/spec/**",
36
+ "!**/*.tsbuildinfo",
37
+ "!**/*.test.*"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ }
42
+ }