@gjsify/sqlite 0.1.0

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.
@@ -0,0 +1,31 @@
1
+ export interface DatabaseSyncOptions {
2
+ open?: boolean;
3
+ readOnly?: boolean;
4
+ timeout?: number;
5
+ enableForeignKeyConstraints?: boolean;
6
+ enableDoubleQuotedStringLiterals?: boolean;
7
+ readBigInts?: boolean;
8
+ returnArrays?: boolean;
9
+ allowBareNamedParameters?: boolean;
10
+ allowUnknownNamedParameters?: boolean;
11
+ defensive?: boolean;
12
+ allowExtension?: boolean;
13
+ }
14
+ export interface StatementSyncOptions {
15
+ readBigInts?: boolean;
16
+ returnArrays?: boolean;
17
+ allowBareNamedParameters?: boolean;
18
+ allowUnknownNamedParameters?: boolean;
19
+ }
20
+ export interface RunResult {
21
+ changes: number | bigint;
22
+ lastInsertRowid: number | bigint;
23
+ }
24
+ export interface ColumnInfo {
25
+ column: string | null;
26
+ database: string | null;
27
+ name: string;
28
+ table: string | null;
29
+ type: string | null;
30
+ }
31
+ export type SQLiteValue = null | number | bigint | string | Uint8Array;
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@gjsify/sqlite",
3
+ "version": "0.1.0",
4
+ "description": "Node.js sqlite module for Gjs using libgda",
5
+ "type": "module",
6
+ "module": "lib/esm/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/esm/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
16
+ "check": "tsc --noEmit",
17
+ "build": "yarn build:gjsify && yarn build:types",
18
+ "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
19
+ "build:types": "tsc",
20
+ "build:test": "yarn build:test:gjs && yarn build:test:node",
21
+ "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
22
+ "build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
23
+ "test": "yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
24
+ "test:gjs": "gjs -m test.gjs.mjs",
25
+ "test:node": "node test.node.mjs"
26
+ },
27
+ "keywords": [
28
+ "gjs",
29
+ "node",
30
+ "sqlite",
31
+ "libgda"
32
+ ],
33
+ "devDependencies": {
34
+ "@gjsify/cli": "^0.1.0",
35
+ "@gjsify/unit": "^0.1.0",
36
+ "@types/node": "^25.5.0",
37
+ "typescript": "^6.0.2"
38
+ },
39
+ "dependencies": {
40
+ "@girs/gda-6.0": "^6.0.0-4.0.0-beta.42",
41
+ "@girs/glib-2.0": "^2.88.0-4.0.0-beta.42",
42
+ "@girs/gobject-2.0": "^2.88.0-4.0.0-beta.42"
43
+ }
44
+ }
@@ -0,0 +1,101 @@
1
+ // SQLite constants matching Node.js node:sqlite constants
2
+ // Reference: Node.js lib/sqlite.js
3
+
4
+ // Changeset conflict constants
5
+ export const SQLITE_CHANGESET_OMIT = 0;
6
+ export const SQLITE_CHANGESET_REPLACE = 1;
7
+ export const SQLITE_CHANGESET_ABORT = 2;
8
+ export const SQLITE_CHANGESET_DATA = 1;
9
+ export const SQLITE_CHANGESET_NOTFOUND = 2;
10
+ export const SQLITE_CHANGESET_CONFLICT = 3;
11
+ export const SQLITE_CHANGESET_CONSTRAINT = 4;
12
+ export const SQLITE_CHANGESET_FOREIGN_KEY = 5;
13
+
14
+ // Authorization result constants
15
+ export const SQLITE_OK = 0;
16
+ export const SQLITE_DENY = 1;
17
+ export const SQLITE_IGNORE = 2;
18
+
19
+ // Authorization action code constants
20
+ export const SQLITE_CREATE_INDEX = 1;
21
+ export const SQLITE_CREATE_TABLE = 2;
22
+ export const SQLITE_CREATE_TEMP_INDEX = 3;
23
+ export const SQLITE_CREATE_TEMP_TABLE = 4;
24
+ export const SQLITE_CREATE_TEMP_TRIGGER = 5;
25
+ export const SQLITE_CREATE_TEMP_VIEW = 6;
26
+ export const SQLITE_CREATE_TRIGGER = 7;
27
+ export const SQLITE_CREATE_VIEW = 8;
28
+ export const SQLITE_DELETE = 9;
29
+ export const SQLITE_DROP_INDEX = 10;
30
+ export const SQLITE_DROP_TABLE = 11;
31
+ export const SQLITE_DROP_TEMP_INDEX = 12;
32
+ export const SQLITE_DROP_TEMP_TABLE = 13;
33
+ export const SQLITE_DROP_TEMP_TRIGGER = 14;
34
+ export const SQLITE_DROP_TEMP_VIEW = 15;
35
+ export const SQLITE_DROP_TRIGGER = 16;
36
+ export const SQLITE_DROP_VIEW = 17;
37
+ export const SQLITE_INSERT = 18;
38
+ export const SQLITE_PRAGMA = 19;
39
+ export const SQLITE_READ = 20;
40
+ export const SQLITE_SELECT = 21;
41
+ export const SQLITE_TRANSACTION = 22;
42
+ export const SQLITE_UPDATE = 23;
43
+ export const SQLITE_ATTACH = 24;
44
+ export const SQLITE_DETACH = 25;
45
+ export const SQLITE_ALTER_TABLE = 26;
46
+ export const SQLITE_REINDEX = 27;
47
+ export const SQLITE_ANALYZE = 28;
48
+ export const SQLITE_CREATE_VTABLE = 29;
49
+ export const SQLITE_DROP_VTABLE = 30;
50
+ export const SQLITE_FUNCTION = 31;
51
+ export const SQLITE_SAVEPOINT = 32;
52
+ export const SQLITE_COPY = 0;
53
+ export const SQLITE_RECURSIVE = 33;
54
+
55
+ export const constants = {
56
+ SQLITE_CHANGESET_OMIT,
57
+ SQLITE_CHANGESET_REPLACE,
58
+ SQLITE_CHANGESET_ABORT,
59
+ SQLITE_CHANGESET_DATA,
60
+ SQLITE_CHANGESET_NOTFOUND,
61
+ SQLITE_CHANGESET_CONFLICT,
62
+ SQLITE_CHANGESET_CONSTRAINT,
63
+ SQLITE_CHANGESET_FOREIGN_KEY,
64
+ SQLITE_OK,
65
+ SQLITE_DENY,
66
+ SQLITE_IGNORE,
67
+ SQLITE_CREATE_INDEX,
68
+ SQLITE_CREATE_TABLE,
69
+ SQLITE_CREATE_TEMP_INDEX,
70
+ SQLITE_CREATE_TEMP_TABLE,
71
+ SQLITE_CREATE_TEMP_TRIGGER,
72
+ SQLITE_CREATE_TEMP_VIEW,
73
+ SQLITE_CREATE_TRIGGER,
74
+ SQLITE_CREATE_VIEW,
75
+ SQLITE_DELETE,
76
+ SQLITE_DROP_INDEX,
77
+ SQLITE_DROP_TABLE,
78
+ SQLITE_DROP_TEMP_INDEX,
79
+ SQLITE_DROP_TEMP_TABLE,
80
+ SQLITE_DROP_TEMP_TRIGGER,
81
+ SQLITE_DROP_TEMP_VIEW,
82
+ SQLITE_DROP_TRIGGER,
83
+ SQLITE_DROP_VIEW,
84
+ SQLITE_INSERT,
85
+ SQLITE_PRAGMA,
86
+ SQLITE_READ,
87
+ SQLITE_SELECT,
88
+ SQLITE_TRANSACTION,
89
+ SQLITE_UPDATE,
90
+ SQLITE_ATTACH,
91
+ SQLITE_DETACH,
92
+ SQLITE_ALTER_TABLE,
93
+ SQLITE_REINDEX,
94
+ SQLITE_ANALYZE,
95
+ SQLITE_CREATE_VTABLE,
96
+ SQLITE_DROP_VTABLE,
97
+ SQLITE_FUNCTION,
98
+ SQLITE_SAVEPOINT,
99
+ SQLITE_COPY,
100
+ SQLITE_RECURSIVE,
101
+ };
@@ -0,0 +1,106 @@
1
+ // Convert Gda.DataModel results to JavaScript objects/arrays
2
+ // Reference: Node.js lib/sqlite.js
3
+ // Reimplemented for GJS using Gda-6.0
4
+
5
+ import Gda from '@girs/gda-6.0';
6
+ import GObject from '@girs/gobject-2.0';
7
+ import { OutOfRangeError } from './errors.ts';
8
+
9
+ export interface ReadOptions {
10
+ readBigInts: boolean;
11
+ returnArrays: boolean;
12
+ }
13
+
14
+ function convertValue(value: unknown, readBigInts: boolean): unknown {
15
+ if (value === null || value === undefined) {
16
+ return null;
17
+ }
18
+ if (typeof value === 'number') {
19
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
20
+ if (!readBigInts) {
21
+ throw new OutOfRangeError(
22
+ `Value is too large to be represented as a JavaScript number: ${value}`
23
+ );
24
+ }
25
+ return BigInt(value);
26
+ }
27
+ if (readBigInts && Number.isInteger(value)) {
28
+ return BigInt(value);
29
+ }
30
+ return value;
31
+ }
32
+ if (typeof value === 'bigint') {
33
+ if (!readBigInts) {
34
+ if (value > BigInt(Number.MAX_SAFE_INTEGER) || value < BigInt(-Number.MAX_SAFE_INTEGER)) {
35
+ throw new OutOfRangeError(
36
+ `Value is too large to be represented as a JavaScript number: ${value}`
37
+ );
38
+ }
39
+ return Number(value);
40
+ }
41
+ return value;
42
+ }
43
+ if (typeof value === 'string') {
44
+ return value;
45
+ }
46
+ // Handle GLib.Bytes or Uint8Array (BLOB)
47
+ if (value instanceof Uint8Array) {
48
+ return value;
49
+ }
50
+ // GLib.Bytes from Gda
51
+ if (value && typeof (value as any).toArray === 'function') {
52
+ return new Uint8Array((value as any).toArray());
53
+ }
54
+ return value;
55
+ }
56
+
57
+ export function readRow(
58
+ model: Gda.DataModel,
59
+ row: number,
60
+ options: ReadOptions,
61
+ ): Record<string, unknown> | unknown[] | undefined {
62
+ const nCols = model.get_n_columns();
63
+
64
+ if (options.returnArrays) {
65
+ const arr: unknown[] = [];
66
+ for (let col = 0; col < nCols; col++) {
67
+ const val = model.get_value_at(col, row);
68
+ arr.push(convertValue(val, options.readBigInts));
69
+ }
70
+ return arr;
71
+ }
72
+
73
+ const obj = Object.create(null) as Record<string, unknown>;
74
+ for (let col = 0; col < nCols; col++) {
75
+ const name = model.get_column_name(col);
76
+ const val = model.get_value_at(col, row);
77
+ obj[name] = convertValue(val, options.readBigInts);
78
+ }
79
+ return obj;
80
+ }
81
+
82
+ export function readAllRows(
83
+ model: Gda.DataModel,
84
+ options: ReadOptions,
85
+ ): (Record<string, unknown> | unknown[])[] {
86
+ const nRows = model.get_n_rows();
87
+ const rows: (Record<string, unknown> | unknown[])[] = [];
88
+ for (let row = 0; row < nRows; row++) {
89
+ const r = readRow(model, row, options);
90
+ if (r !== undefined) {
91
+ rows.push(r);
92
+ }
93
+ }
94
+ return rows;
95
+ }
96
+
97
+ export function readFirstRow(
98
+ model: Gda.DataModel,
99
+ options: ReadOptions,
100
+ ): Record<string, unknown> | unknown[] | undefined {
101
+ const nRows = model.get_n_rows();
102
+ if (nRows === 0) {
103
+ return undefined;
104
+ }
105
+ return readRow(model, 0, options);
106
+ }
@@ -0,0 +1,143 @@
1
+ // Ported from refs/node-test/parallel/test-sqlite-data-types.js
2
+ // Original: MIT license, Node.js contributors
3
+
4
+ import { describe, it, expect } from '@gjsify/unit';
5
+ import { join } from 'node:path';
6
+ import { tmpdir } from 'node:os';
7
+ import { mkdirSync, rmSync } from 'node:fs';
8
+ import { DatabaseSync } from 'node:sqlite';
9
+
10
+ let cnt = 0;
11
+ const testDir = join(tmpdir(), 'gjsify-sqlite-types-test-' + Date.now());
12
+
13
+ function setup() {
14
+ try { mkdirSync(testDir, { recursive: true }); } catch {}
15
+ }
16
+
17
+ function cleanup() {
18
+ try { rmSync(testDir, { recursive: true, force: true }); } catch {}
19
+ }
20
+
21
+ function nextDb(): string {
22
+ return join(testDir, `database-${cnt++}.db`);
23
+ }
24
+
25
+ export default async () => {
26
+ setup();
27
+
28
+ await describe('sqlite data types', async () => {
29
+ await it('supports INTEGER, REAL, TEXT, BLOB, and NULL', async () => {
30
+ const u8a = new TextEncoder().encode('a☃b☃c');
31
+ const db = new DatabaseSync(nextDb());
32
+ db.exec(`
33
+ CREATE TABLE types(
34
+ key INTEGER PRIMARY KEY,
35
+ int INTEGER,
36
+ double REAL,
37
+ text TEXT,
38
+ buf BLOB
39
+ ) STRICT;
40
+ `);
41
+ const stmt = db.prepare('INSERT INTO types (key, int, double, text, buf) VALUES (?, ?, ?, ?, ?)');
42
+
43
+ const r1 = stmt.run(1, 42, 3.14159, 'foo', u8a);
44
+ expect(r1.changes).toBe(1);
45
+ expect(r1.lastInsertRowid).toBe(1);
46
+
47
+ const r2 = stmt.run(2, null, null, null, null);
48
+ expect(r2.changes).toBe(1);
49
+ expect(r2.lastInsertRowid).toBe(2);
50
+
51
+ const query = db.prepare('SELECT * FROM types WHERE key = ?');
52
+
53
+ const row1 = query.get(1) as Record<string, unknown>;
54
+ expect(row1.key).toBe(1);
55
+ expect(row1.int).toBe(42);
56
+ expect(row1.double).toBe(3.14159);
57
+ expect(row1.text).toBe('foo');
58
+
59
+ const row2 = query.get(2) as Record<string, unknown>;
60
+ expect(row2.key).toBe(2);
61
+ expect(row2.int).toBeNull();
62
+ expect(row2.double).toBeNull();
63
+ expect(row2.text).toBeNull();
64
+ expect(row2.buf).toBeNull();
65
+
66
+ db.close();
67
+ });
68
+
69
+ await it('rejects unsupported data types', async () => {
70
+ const db = new DatabaseSync(nextDb());
71
+ db.exec('CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;');
72
+
73
+ const unsupported = [
74
+ undefined,
75
+ () => {},
76
+ Symbol(),
77
+ /foo/,
78
+ Promise.resolve(),
79
+ new Map(),
80
+ new Set(),
81
+ ];
82
+
83
+ for (const val of unsupported) {
84
+ expect(() => {
85
+ db.prepare('INSERT INTO types (key, val) VALUES (?, ?)').run(1, val);
86
+ }).toThrow();
87
+ }
88
+
89
+ db.close();
90
+ });
91
+
92
+ await it('supports BigInt binding', async () => {
93
+ const db = new DatabaseSync(nextDb());
94
+ db.exec('CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;');
95
+ const stmt = db.prepare('INSERT INTO types (key, val) VALUES (?, ?)');
96
+ const result = stmt.run(4, 99n);
97
+ expect(result.changes).toBe(1);
98
+
99
+ const query = db.prepare('SELECT * FROM types WHERE key = ?');
100
+ const row = query.get(4) as Record<string, unknown>;
101
+ expect(row.val).toBe(99);
102
+ db.close();
103
+ });
104
+
105
+ await it('throws when binding a BigInt that is too large', async () => {
106
+ const max = 9223372036854775807n;
107
+ const db = new DatabaseSync(nextDb());
108
+ db.exec('CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;');
109
+ const stmt = db.prepare('INSERT INTO types (key, val) VALUES (?, ?)');
110
+
111
+ // Max should succeed
112
+ stmt.run(1, max);
113
+
114
+ // Max + 1 should throw
115
+ expect(() => {
116
+ stmt.run(2, max + 1n);
117
+ }).toThrow();
118
+ db.close();
119
+ });
120
+
121
+ await it('statements are unbound on each call', async () => {
122
+ const db = new DatabaseSync(nextDb());
123
+ db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;');
124
+ const stmt = db.prepare('INSERT INTO data (key, val) VALUES (?, ?)');
125
+
126
+ const r1 = stmt.run(1, 5);
127
+ expect(r1.changes).toBe(1);
128
+
129
+ // Second call without params — params should be unbound (NULL)
130
+ const r2 = stmt.run();
131
+ expect(r2.changes).toBe(1);
132
+
133
+ const rows = db.prepare('SELECT * FROM data ORDER BY key').all() as Record<string, unknown>[];
134
+ expect(rows.length).toBe(2);
135
+ expect(rows[0].key).toBe(1);
136
+ expect(rows[0].val).toBe(5);
137
+ expect(rows[1].val).toBeNull();
138
+ db.close();
139
+ });
140
+ });
141
+
142
+ cleanup();
143
+ };
@@ -0,0 +1,267 @@
1
+ // Ported from refs/node-test/parallel/test-sqlite-database-sync.js
2
+ // Original: MIT license, Node.js contributors
3
+
4
+ import { describe, it, expect } from '@gjsify/unit';
5
+ import { join } from 'node:path';
6
+ import { tmpdir } from 'node:os';
7
+ import { existsSync, mkdirSync, rmSync } from 'node:fs';
8
+ import { DatabaseSync } from 'node:sqlite';
9
+
10
+ let cnt = 0;
11
+ const testDir = join(tmpdir(), 'gjsify-sqlite-test-' + Date.now());
12
+
13
+ function setup() {
14
+ try { mkdirSync(testDir, { recursive: true }); } catch {}
15
+ }
16
+
17
+ function cleanup() {
18
+ try { rmSync(testDir, { recursive: true, force: true }); } catch {}
19
+ }
20
+
21
+ function nextDb(): string {
22
+ return join(testDir, `database-${cnt++}.db`);
23
+ }
24
+
25
+ export default async () => {
26
+ setup();
27
+
28
+ await describe('DatabaseSync() constructor', async () => {
29
+ await it('throws if database path is not a string, Uint8Array, or URL', async () => {
30
+ expect(() => {
31
+ new (DatabaseSync as any)();
32
+ }).toThrow();
33
+ });
34
+
35
+ await it('throws if the database location as string contains null bytes', async () => {
36
+ expect(() => {
37
+ new DatabaseSync('l\0cation');
38
+ }).toThrow();
39
+ });
40
+
41
+ await it('throws if options is provided but is not an object', async () => {
42
+ expect(() => {
43
+ new (DatabaseSync as any)('foo', null);
44
+ }).toThrow();
45
+ });
46
+
47
+ await it('throws if options.open is provided but is not a boolean', async () => {
48
+ expect(() => {
49
+ new (DatabaseSync as any)('foo', { open: 5 });
50
+ }).toThrow();
51
+ });
52
+
53
+ await it('throws if options.readOnly is provided but is not a boolean', async () => {
54
+ expect(() => {
55
+ new (DatabaseSync as any)('foo', { readOnly: 5 });
56
+ }).toThrow();
57
+ });
58
+
59
+ await it('throws if options.timeout is provided but is not an integer', async () => {
60
+ expect(() => {
61
+ new (DatabaseSync as any)('foo', { timeout: .99 });
62
+ }).toThrow();
63
+ });
64
+
65
+ await it('throws if options.enableForeignKeyConstraints is not a boolean', async () => {
66
+ expect(() => {
67
+ new (DatabaseSync as any)('foo', { enableForeignKeyConstraints: 5 });
68
+ }).toThrow();
69
+ });
70
+
71
+ await it('throws if options.enableDoubleQuotedStringLiterals is not a boolean', async () => {
72
+ expect(() => {
73
+ new (DatabaseSync as any)('foo', { enableDoubleQuotedStringLiterals: 5 });
74
+ }).toThrow();
75
+ });
76
+
77
+ await it('throws if options.readBigInts is not a boolean', async () => {
78
+ expect(() => {
79
+ new (DatabaseSync as any)('foo', { readBigInts: 42 });
80
+ }).toThrow();
81
+ });
82
+
83
+ await it('throws if options.returnArrays is not a boolean', async () => {
84
+ expect(() => {
85
+ new (DatabaseSync as any)('foo', { returnArrays: 42 });
86
+ }).toThrow();
87
+ });
88
+
89
+ await it('throws if the URL does not have the file: scheme', async () => {
90
+ expect(() => {
91
+ new DatabaseSync(new URL('http://example.com') as any);
92
+ }).toThrow();
93
+ });
94
+ });
95
+
96
+ await describe('DatabaseSync.prototype.open()', async () => {
97
+ await it('opens a database connection', async () => {
98
+ const dbPath = nextDb();
99
+ const db = new DatabaseSync(dbPath, { open: false });
100
+ expect(db.isOpen).toBe(false);
101
+ db.open();
102
+ expect(db.isOpen).toBe(true);
103
+ db.close();
104
+ });
105
+
106
+ await it('throws if database is already open', async () => {
107
+ const dbPath = nextDb();
108
+ const db = new DatabaseSync(dbPath);
109
+ expect(db.isOpen).toBe(true);
110
+ expect(() => { db.open(); }).toThrow();
111
+ db.close();
112
+ });
113
+ });
114
+
115
+ await describe('DatabaseSync.prototype.close()', async () => {
116
+ await it('closes an open database connection', async () => {
117
+ const db = new DatabaseSync(nextDb());
118
+ expect(db.isOpen).toBe(true);
119
+ db.close();
120
+ expect(db.isOpen).toBe(false);
121
+ });
122
+
123
+ await it('throws if database is not open', async () => {
124
+ const db = new DatabaseSync(nextDb(), { open: false });
125
+ expect(db.isOpen).toBe(false);
126
+ expect(() => { db.close(); }).toThrow();
127
+ });
128
+ });
129
+
130
+ await describe('DatabaseSync.prototype.exec()', async () => {
131
+ await it('runs SQL and returns undefined', async () => {
132
+ const db = new DatabaseSync(nextDb());
133
+ const result = db.exec(`
134
+ CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;
135
+ INSERT INTO data (key, val) VALUES (1, 2);
136
+ INSERT INTO data (key, val) VALUES (8, 9);
137
+ `);
138
+ expect(result).toBe(undefined);
139
+ const stmt = db.prepare('SELECT * FROM data ORDER BY key');
140
+ const rows = stmt.all();
141
+ expect(rows.length).toBe(2);
142
+ db.close();
143
+ });
144
+
145
+ await it('reports errors from SQLite', async () => {
146
+ const db = new DatabaseSync(nextDb());
147
+ expect(() => {
148
+ db.exec('CREATE TABLEEEE');
149
+ }).toThrow();
150
+ db.close();
151
+ });
152
+
153
+ await it('throws if database is not open', async () => {
154
+ const db = new DatabaseSync(nextDb(), { open: false });
155
+ expect(() => { db.exec('SELECT 1'); }).toThrow();
156
+ });
157
+
158
+ await it('throws if sql is not a string', async () => {
159
+ const db = new DatabaseSync(nextDb());
160
+ expect(() => { (db as any).exec(); }).toThrow();
161
+ db.close();
162
+ });
163
+ });
164
+
165
+ await describe('DatabaseSync.prototype.prepare()', async () => {
166
+ await it('returns a prepared statement', async () => {
167
+ const db = new DatabaseSync(nextDb());
168
+ const stmt = db.prepare('CREATE TABLE webstorage(key TEXT)');
169
+ expect(stmt).toBeDefined();
170
+ db.close();
171
+ });
172
+
173
+ await it('throws if database is not open', async () => {
174
+ const db = new DatabaseSync(nextDb(), { open: false });
175
+ expect(() => { (db as any).prepare(); }).toThrow();
176
+ });
177
+
178
+ await it('throws if sql is not a string', async () => {
179
+ const db = new DatabaseSync(nextDb());
180
+ expect(() => { (db as any).prepare(); }).toThrow();
181
+ db.close();
182
+ });
183
+ });
184
+
185
+ await describe('DatabaseSync :memory: database', async () => {
186
+ await it('works with :memory: path', async () => {
187
+ const db = new DatabaseSync(':memory:');
188
+ expect(db.isOpen).toBe(true);
189
+ db.exec('CREATE TABLE t(id INTEGER PRIMARY KEY, val TEXT)');
190
+ db.exec("INSERT INTO t (id, val) VALUES (1, 'hello')");
191
+ const row = db.prepare('SELECT * FROM t').get();
192
+ expect(row).toBeDefined();
193
+ db.close();
194
+ });
195
+ });
196
+
197
+ await describe('DatabaseSync.prototype.isTransaction', async () => {
198
+ await it('correctly detects a committed transaction', async () => {
199
+ const db = new DatabaseSync(':memory:');
200
+ expect(db.isTransaction).toBe(false);
201
+ db.exec('BEGIN');
202
+ expect(db.isTransaction).toBe(true);
203
+ db.exec('CREATE TABLE foo (id INTEGER PRIMARY KEY)');
204
+ expect(db.isTransaction).toBe(true);
205
+ db.exec('COMMIT');
206
+ expect(db.isTransaction).toBe(false);
207
+ db.close();
208
+ });
209
+
210
+ await it('correctly detects a rolled back transaction', async () => {
211
+ const db = new DatabaseSync(':memory:');
212
+ expect(db.isTransaction).toBe(false);
213
+ db.exec('BEGIN');
214
+ expect(db.isTransaction).toBe(true);
215
+ db.exec('ROLLBACK');
216
+ expect(db.isTransaction).toBe(false);
217
+ db.close();
218
+ });
219
+ });
220
+
221
+ await describe('DatabaseSync.prototype.location()', async () => {
222
+ await it('throws if database is not open', async () => {
223
+ const db = new DatabaseSync(nextDb(), { open: false });
224
+ expect(() => { db.location(); }).toThrow();
225
+ });
226
+
227
+ await it('returns null for in-memory database', async () => {
228
+ const db = new DatabaseSync(':memory:');
229
+ expect(db.location()).toBeNull();
230
+ db.close();
231
+ });
232
+
233
+ await it('returns db path for persistent database', async () => {
234
+ const dbPath = nextDb();
235
+ const db = new DatabaseSync(dbPath);
236
+ expect(db.location()).toBe(dbPath);
237
+ db.close();
238
+ });
239
+ });
240
+
241
+ await describe('DatabaseSync foreign key constraints', async () => {
242
+ await it('enables foreign key constraints by default', async () => {
243
+ const db = new DatabaseSync(nextDb());
244
+ db.exec(`
245
+ CREATE TABLE foo (id INTEGER PRIMARY KEY);
246
+ CREATE TABLE bar (foo_id INTEGER REFERENCES foo(id));
247
+ `);
248
+ expect(() => {
249
+ db.exec('INSERT INTO bar (foo_id) VALUES (1)');
250
+ }).toThrow();
251
+ db.close();
252
+ });
253
+
254
+ await it('allows disabling foreign key constraints', async () => {
255
+ const db = new DatabaseSync(nextDb(), { enableForeignKeyConstraints: false });
256
+ db.exec(`
257
+ CREATE TABLE foo (id INTEGER PRIMARY KEY);
258
+ CREATE TABLE bar (foo_id INTEGER REFERENCES foo(id));
259
+ `);
260
+ // Should not throw
261
+ db.exec('INSERT INTO bar (foo_id) VALUES (1)');
262
+ db.close();
263
+ });
264
+ });
265
+
266
+ cleanup();
267
+ };