@nymphjs/driver-sqlite3 1.0.0-beta.3 → 1.0.0-beta.31

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.
@@ -21,16 +21,25 @@ import {
21
21
  SQLite3DriverConfigDefaults as defaults,
22
22
  } from './conf';
23
23
 
24
+ class InternalStore {
25
+ public link: SQLite3.Database;
26
+ public linkWrite?: SQLite3.Database;
27
+ public connected: boolean = false;
28
+ public transactionsStarted = 0;
29
+
30
+ constructor(link: SQLite3.Database) {
31
+ this.link = link;
32
+ }
33
+ }
34
+
24
35
  /**
25
36
  * The SQLite3 Nymph database driver.
26
37
  */
27
38
  export default class SQLite3Driver extends NymphDriver {
28
39
  public config: SQLite3DriverConfig;
29
40
  protected prefix: string;
30
- protected connected: boolean = false;
31
41
  // @ts-ignore: this is assigned in connect(), which is called by the constructor.
32
- protected link: SQLite3.Database;
33
- protected transactionsStarted = 0;
42
+ protected store: InternalStore;
34
43
 
35
44
  static escape(input: string) {
36
45
  if (input.indexOf('\x00') !== -1) {
@@ -42,11 +51,27 @@ export default class SQLite3Driver extends NymphDriver {
42
51
  return '"' + input.replace(/"/g, () => '""') + '"';
43
52
  }
44
53
 
45
- constructor(config: Partial<SQLite3DriverConfig>) {
54
+ constructor(config: Partial<SQLite3DriverConfig>, store?: InternalStore) {
46
55
  super();
47
56
  this.config = { ...defaults, ...config };
57
+ if (this.config.filename === ':memory:') {
58
+ this.config.explicitWrite = true;
59
+ }
48
60
  this.prefix = this.config.prefix;
49
- this.connect();
61
+ if (store) {
62
+ this.store = store;
63
+ } else {
64
+ this.connect();
65
+ }
66
+ }
67
+
68
+ /**
69
+ * This is used internally by Nymph. Don't call it yourself.
70
+ *
71
+ * @returns A clone of this instance.
72
+ */
73
+ public clone() {
74
+ return new SQLite3Driver(this.config, this.store);
50
75
  }
51
76
 
52
77
  /**
@@ -54,41 +79,104 @@ export default class SQLite3Driver extends NymphDriver {
54
79
  *
55
80
  * @returns Whether this instance is connected to a SQLite3 database.
56
81
  */
57
- public async connect() {
58
- const { filename, fileMustExist, timeout, readonly, verbose } = this.config;
82
+ public connect() {
83
+ if (this.store && this.store.connected) {
84
+ return Promise.resolve(true);
85
+ }
86
+
59
87
  // Connecting
60
- if (!this.connected) {
88
+ this._connect(false);
89
+
90
+ return Promise.resolve(this.store.connected);
91
+ }
92
+
93
+ private _connect(write: boolean) {
94
+ const { filename, fileMustExist, timeout, explicitWrite, wal, verbose } =
95
+ this.config;
96
+
97
+ try {
98
+ const setOptions = (link: SQLite3.Database) => {
99
+ // Set database and connection options.
100
+ if (wal) {
101
+ link.pragma('journal_mode = WAL;');
102
+ }
103
+ link.pragma('encoding = "UTF-8";');
104
+ link.pragma('foreign_keys = 1;');
105
+ link.pragma('case_sensitive_like = 1;');
106
+ // Create the preg_match and regexp functions.
107
+ link.function('regexp', { deterministic: true }, ((
108
+ pattern: string,
109
+ subject: string
110
+ ) => (this.posixRegexMatch(pattern, subject) ? 1 : 0)) as (
111
+ ...params: any[]
112
+ ) => any);
113
+ };
114
+
115
+ let link: SQLite3.Database;
61
116
  try {
62
- this.link = new SQLite3(filename, {
63
- readonly,
117
+ link = new SQLite3(filename, {
118
+ readonly: !explicitWrite && !write,
64
119
  fileMustExist,
65
120
  timeout,
66
121
  verbose,
67
122
  });
68
- this.connected = true;
69
- // Set database and connection options.
70
- this.link.pragma('encoding = "UTF-8";');
71
- this.link.pragma('foreign_keys = 1;');
72
- this.link.pragma('case_sensitive_like = 1;');
73
- // Create the preg_match and regexp functions.
74
- this.link.function(
75
- 'regexp',
76
- { deterministic: true },
77
- (pattern: string, subject: string) =>
78
- this.posixRegexMatch(pattern, subject) ? 1 : 0
79
- );
80
123
  } catch (e: any) {
81
- this.connected = false;
82
- if (filename === ':memory:') {
83
- throw new NotConfiguredError(
84
- "It seems the config hasn't been set up correctly."
85
- );
124
+ if (
125
+ e.code === 'SQLITE_CANTOPEN' &&
126
+ !explicitWrite &&
127
+ !write &&
128
+ !this.config.fileMustExist
129
+ ) {
130
+ // This happens when the file doesn't exist and we attempt to open it
131
+ // readonly.
132
+ // First open it in write mode.
133
+ const writeLink = new SQLite3(filename, {
134
+ readonly: false,
135
+ fileMustExist,
136
+ timeout,
137
+ verbose,
138
+ });
139
+ setOptions(writeLink);
140
+ writeLink.close();
141
+ // Now open in readonly.
142
+ link = new SQLite3(filename, {
143
+ readonly: true,
144
+ fileMustExist,
145
+ timeout,
146
+ verbose,
147
+ });
86
148
  } else {
87
- throw new UnableToConnectError('Could not connect: ' + e?.message);
149
+ throw e;
88
150
  }
89
151
  }
152
+
153
+ if (!this.store) {
154
+ if (write) {
155
+ throw new Error(
156
+ 'Tried to open in write without opening in read first.'
157
+ );
158
+ }
159
+ this.store = new InternalStore(link);
160
+ } else if (write) {
161
+ this.store.linkWrite = link;
162
+ } else {
163
+ this.store.link = link;
164
+ }
165
+ this.store.connected = true;
166
+ setOptions(link);
167
+ } catch (e: any) {
168
+ if (this.store) {
169
+ this.store.connected = false;
170
+ }
171
+ if (filename === ':memory:') {
172
+ throw new NotConfiguredError(
173
+ "It seems the config hasn't been set up correctly. Could not connect: " +
174
+ e?.message
175
+ );
176
+ } else {
177
+ throw new UnableToConnectError('Could not connect: ' + e?.message);
178
+ }
90
179
  }
91
- return this.connected;
92
180
  }
93
181
 
94
182
  /**
@@ -97,16 +185,24 @@ export default class SQLite3Driver extends NymphDriver {
97
185
  * @returns Whether this instance is connected to a SQLite3 database.
98
186
  */
99
187
  public async disconnect() {
100
- if (this.connected) {
101
- this.link.exec('PRAGMA optimize;');
102
- this.link.close();
103
- this.connected = false;
188
+ if (this.store.connected) {
189
+ if (this.store.linkWrite && !this.config.explicitWrite) {
190
+ this.store.linkWrite.exec('PRAGMA optimize;');
191
+ this.store.linkWrite.close();
192
+ this.store.linkWrite = undefined;
193
+ }
194
+ if (this.config.explicitWrite) {
195
+ this.store.link.exec('PRAGMA optimize;');
196
+ }
197
+ this.store.link.close();
198
+ this.store.transactionsStarted = 0;
199
+ this.store.connected = false;
104
200
  }
105
- return this.connected;
201
+ return this.store.connected;
106
202
  }
107
203
 
108
204
  public async inTransaction() {
109
- return this.transactionsStarted > 0;
205
+ return this.store.transactionsStarted > 0;
110
206
  }
111
207
 
112
208
  /**
@@ -115,18 +211,7 @@ export default class SQLite3Driver extends NymphDriver {
115
211
  * @returns Whether this instance is connected to a SQLite3 database.
116
212
  */
117
213
  public isConnected() {
118
- return this.connected;
119
- }
120
-
121
- /**
122
- * Check if SQLite3 DB is read only and throw error if so.
123
- */
124
- private checkReadOnlyMode() {
125
- if (this.config.readonly) {
126
- throw new InvalidParametersError(
127
- 'Attempt to write to SQLite3 DB in read only mode.'
128
- );
129
- }
214
+ return this.store.connected;
130
215
  }
131
216
 
132
217
  /**
@@ -135,7 +220,6 @@ export default class SQLite3Driver extends NymphDriver {
135
220
  * @param etype The entity type to create a table for. If this is blank, the default tables are created.
136
221
  */
137
222
  private createTables(etype: string | null = null) {
138
- this.checkReadOnlyMode();
139
223
  this.startTransaction('nymph-tablecreation');
140
224
  try {
141
225
  if (etype != null) {
@@ -326,7 +410,10 @@ export default class SQLite3Driver extends NymphDriver {
326
410
  }: { etypes?: string[]; params?: { [k: string]: any } } = {}
327
411
  ) {
328
412
  return this.query(
329
- () => this.link.prepare(query).iterate(params),
413
+ () =>
414
+ (this.store.linkWrite || this.store.link)
415
+ .prepare(query)
416
+ .iterate(params),
330
417
  `${query} -- ${JSON.stringify(params)}`,
331
418
  etypes
332
419
  );
@@ -340,7 +427,8 @@ export default class SQLite3Driver extends NymphDriver {
340
427
  }: { etypes?: string[]; params?: { [k: string]: any } } = {}
341
428
  ) {
342
429
  return this.query(
343
- () => this.link.prepare(query).get(params),
430
+ () =>
431
+ (this.store.linkWrite || this.store.link).prepare(query).get(params),
344
432
  `${query} -- ${JSON.stringify(params)}`,
345
433
  etypes
346
434
  );
@@ -354,7 +442,8 @@ export default class SQLite3Driver extends NymphDriver {
354
442
  }: { etypes?: string[]; params?: { [k: string]: any } } = {}
355
443
  ) {
356
444
  return this.query(
357
- () => this.link.prepare(query).run(params),
445
+ () =>
446
+ (this.store.linkWrite || this.store.link).prepare(query).run(params),
358
447
  `${query} -- ${JSON.stringify(params)}`,
359
448
  etypes
360
449
  );
@@ -366,11 +455,22 @@ export default class SQLite3Driver extends NymphDriver {
366
455
  'Transaction commit attempted without a name.'
367
456
  );
368
457
  }
369
- if (this.transactionsStarted === 0) {
458
+ if (this.store.transactionsStarted === 0) {
370
459
  return true;
371
460
  }
372
461
  this.queryRun(`RELEASE SAVEPOINT ${SQLite3Driver.escape(name)};`);
373
- this.transactionsStarted--;
462
+ this.store.transactionsStarted--;
463
+
464
+ if (
465
+ this.store.transactionsStarted === 0 &&
466
+ this.store.linkWrite &&
467
+ !this.config.explicitWrite
468
+ ) {
469
+ this.store.linkWrite.exec('PRAGMA optimize;');
470
+ this.store.linkWrite.close();
471
+ this.store.linkWrite = undefined;
472
+ }
473
+
374
474
  return true;
375
475
  }
376
476
 
@@ -386,7 +486,6 @@ export default class SQLite3Driver extends NymphDriver {
386
486
  EntityClass = className;
387
487
  }
388
488
  const etype = EntityClass.ETYPE;
389
- this.checkReadOnlyMode();
390
489
  await this.startTransaction('nymph-delete');
391
490
  try {
392
491
  this.queryRun(
@@ -449,7 +548,7 @@ export default class SQLite3Driver extends NymphDriver {
449
548
  if (!name) {
450
549
  throw new InvalidParametersError('Name not given for UID');
451
550
  }
452
- this.checkReadOnlyMode();
551
+ await this.startTransaction('nymph-delete-uid');
453
552
  this.queryRun(
454
553
  `DELETE FROM ${SQLite3Driver.escape(
455
554
  `${this.prefix}uids`
@@ -460,6 +559,7 @@ export default class SQLite3Driver extends NymphDriver {
460
559
  },
461
560
  }
462
561
  );
562
+ await this.commit('nymph-delete-uid');
463
563
  return true;
464
564
  }
465
565
 
@@ -477,7 +577,7 @@ export default class SQLite3Driver extends NymphDriver {
477
577
  writeLine('');
478
578
 
479
579
  // Export UIDs.
480
- let uids = this.queryIter(
580
+ let uids: IterableIterator<any> = this.queryIter(
481
581
  `SELECT * FROM ${SQLite3Driver.escape(
482
582
  `${this.prefix}uids`
483
583
  )} ORDER BY "name";`
@@ -493,7 +593,7 @@ export default class SQLite3Driver extends NymphDriver {
493
593
  writeLine('');
494
594
 
495
595
  // Get the etypes.
496
- const tables = this.queryIter(
596
+ const tables: IterableIterator<any> = this.queryIter(
497
597
  "SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;"
498
598
  );
499
599
  const etypes = [];
@@ -505,7 +605,7 @@ export default class SQLite3Driver extends NymphDriver {
505
605
 
506
606
  for (const etype of etypes) {
507
607
  // Export entities.
508
- const dataIterator = this.queryIter(
608
+ const dataIterator: IterableIterator<any> = this.queryIter(
509
609
  `SELECT e.*, d."name" AS "dname", d."value" AS "dvalue", c."string", c."number" FROM ${SQLite3Driver.escape(
510
610
  `${this.prefix}entities_${etype}`
511
611
  )} e LEFT JOIN ${SQLite3Driver.escape(
@@ -574,6 +674,7 @@ export default class SQLite3Driver extends NymphDriver {
574
674
  const cTable = `c${tableSuffix}`;
575
675
  const fTable = `f${tableSuffix}`;
576
676
  const ieTable = `ie${tableSuffix}`;
677
+ const sTable = `s${tableSuffix}`;
577
678
  const sort = options.sort ?? 'cdate';
578
679
  const queryParts = this.iterateSelectorsForQuery(
579
680
  formattedSelectors,
@@ -1319,18 +1420,31 @@ export default class SQLite3Driver extends NymphDriver {
1319
1420
  );
1320
1421
 
1321
1422
  let sortBy: string;
1423
+ let sortByInner: string;
1424
+ let sortJoin = '';
1425
+ const order = options.reverse ? ' DESC' : '';
1322
1426
  switch (sort) {
1323
1427
  case 'mdate':
1324
- sortBy = '"mdate"';
1428
+ sortBy = `${eTable}."mdate"${order}`;
1429
+ sortByInner = `${ieTable}."mdate"${order}`;
1325
1430
  break;
1326
1431
  case 'cdate':
1432
+ sortBy = `${eTable}."cdate"${order}`;
1433
+ sortByInner = `${ieTable}."cdate"${order}`;
1434
+ break;
1327
1435
  default:
1328
- sortBy = '"cdate"';
1436
+ const name = `param${++count.i}`;
1437
+ sortJoin = `LEFT JOIN (
1438
+ SELECT "guid", "string", "number"
1439
+ FROM ${SQLite3Driver.escape(this.prefix + 'comparisons_' + etype)}
1440
+ WHERE "name"=@${name}
1441
+ ORDER BY "number"${order}, "string"${order}
1442
+ ) ${sTable} USING ("guid")`;
1443
+ sortBy = `${sTable}."number"${order}, ${sTable}."string"${order}`;
1444
+ sortByInner = sortBy;
1445
+ params[name] = sort;
1329
1446
  break;
1330
1447
  }
1331
- if (options.reverse) {
1332
- sortBy += ' DESC';
1333
- }
1334
1448
 
1335
1449
  let query: string;
1336
1450
  if (queryParts.length) {
@@ -1367,8 +1481,9 @@ export default class SQLite3Driver extends NymphDriver {
1367
1481
  FROM ${SQLite3Driver.escape(
1368
1482
  this.prefix + 'entities_' + etype
1369
1483
  )} ${ieTable}
1484
+ ${sortJoin}
1370
1485
  WHERE (${whereClause})
1371
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1486
+ ORDER BY ${sortByInner}, "guid"${limit}${offset}`;
1372
1487
  } else {
1373
1488
  query = `SELECT
1374
1489
  ${eTable}."guid",
@@ -1388,15 +1503,17 @@ export default class SQLite3Driver extends NymphDriver {
1388
1503
  INNER JOIN ${SQLite3Driver.escape(
1389
1504
  this.prefix + 'comparisons_' + etype
1390
1505
  )} ${cTable} USING ("guid", "name")
1506
+ ${sortJoin}
1391
1507
  INNER JOIN (
1392
1508
  SELECT "guid"
1393
1509
  FROM ${SQLite3Driver.escape(
1394
1510
  this.prefix + 'entities_' + etype
1395
1511
  )} ${ieTable}
1512
+ ${sortJoin}
1396
1513
  WHERE (${whereClause})
1397
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1514
+ ORDER BY ${sortByInner}${limit}${offset}
1398
1515
  ) ${fTable} USING ("guid")
1399
- ORDER BY ${eTable}.${sortBy}`;
1516
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1400
1517
  }
1401
1518
  }
1402
1519
  } else {
@@ -1430,7 +1547,8 @@ export default class SQLite3Driver extends NymphDriver {
1430
1547
  FROM ${SQLite3Driver.escape(
1431
1548
  this.prefix + 'entities_' + etype
1432
1549
  )} ${ieTable}
1433
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}`;
1550
+ ${sortJoin}
1551
+ ORDER BY ${sortByInner}, "guid"${limit}${offset}`;
1434
1552
  } else {
1435
1553
  if (limit || offset) {
1436
1554
  query = `SELECT
@@ -1451,14 +1569,16 @@ export default class SQLite3Driver extends NymphDriver {
1451
1569
  INNER JOIN ${SQLite3Driver.escape(
1452
1570
  this.prefix + 'comparisons_' + etype
1453
1571
  )} c USING ("guid", "name")
1572
+ ${sortJoin}
1454
1573
  INNER JOIN (
1455
1574
  SELECT "guid"
1456
1575
  FROM ${SQLite3Driver.escape(
1457
1576
  this.prefix + 'entities_' + etype
1458
1577
  )} ${ieTable}
1459
- ORDER BY ${ieTable}.${sortBy}${limit}${offset}
1578
+ ${sortJoin}
1579
+ ORDER BY ${sortByInner}${limit}${offset}
1460
1580
  ) ${fTable} USING ("guid")
1461
- ORDER BY ${eTable}.${sortBy}`;
1581
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1462
1582
  } else {
1463
1583
  query = `SELECT
1464
1584
  ${eTable}."guid",
@@ -1478,7 +1598,8 @@ export default class SQLite3Driver extends NymphDriver {
1478
1598
  INNER JOIN ${SQLite3Driver.escape(
1479
1599
  this.prefix + 'comparisons_' + etype
1480
1600
  )} ${cTable} USING ("guid", "name")
1481
- ORDER BY ${eTable}.${sortBy}`;
1601
+ ${sortJoin}
1602
+ ORDER BY ${sortBy}, ${eTable}."guid"`;
1482
1603
  }
1483
1604
  }
1484
1605
  }
@@ -1586,7 +1707,7 @@ export default class SQLite3Driver extends NymphDriver {
1586
1707
  if (name == null) {
1587
1708
  throw new InvalidParametersError('Name not given for UID.');
1588
1709
  }
1589
- const result = this.queryGet(
1710
+ const result: any = this.queryGet(
1590
1711
  `SELECT "cur_uid" FROM ${SQLite3Driver.escape(
1591
1712
  `${this.prefix}uids`
1592
1713
  )} WHERE "name"=@name;`,
@@ -1600,7 +1721,6 @@ export default class SQLite3Driver extends NymphDriver {
1600
1721
  }
1601
1722
 
1602
1723
  public async import(filename: string) {
1603
- this.checkReadOnlyMode();
1604
1724
  try {
1605
1725
  return this.importFromFile(
1606
1726
  filename,
@@ -1763,19 +1883,20 @@ export default class SQLite3Driver extends NymphDriver {
1763
1883
  if (name == null) {
1764
1884
  throw new InvalidParametersError('Name not given for UID.');
1765
1885
  }
1766
- this.checkReadOnlyMode();
1767
1886
  await this.startTransaction('nymph-newuid');
1768
1887
  try {
1769
1888
  let curUid =
1770
- this.queryGet(
1771
- `SELECT "cur_uid" FROM ${SQLite3Driver.escape(
1772
- `${this.prefix}uids`
1773
- )} WHERE "name"=@name;`,
1774
- {
1775
- params: {
1776
- name,
1777
- },
1778
- }
1889
+ (
1890
+ this.queryGet(
1891
+ `SELECT "cur_uid" FROM ${SQLite3Driver.escape(
1892
+ `${this.prefix}uids`
1893
+ )} WHERE "name"=@name;`,
1894
+ {
1895
+ params: {
1896
+ name,
1897
+ },
1898
+ }
1899
+ ) as any
1779
1900
  )?.cur_uid ?? null;
1780
1901
  if (curUid == null) {
1781
1902
  curUid = 1;
@@ -1816,7 +1937,7 @@ export default class SQLite3Driver extends NymphDriver {
1816
1937
  if (oldName == null || newName == null) {
1817
1938
  throw new InvalidParametersError('Name not given for UID.');
1818
1939
  }
1819
- this.checkReadOnlyMode();
1940
+ await this.startTransaction('nymph-rename-uid');
1820
1941
  this.queryRun(
1821
1942
  `UPDATE ${SQLite3Driver.escape(
1822
1943
  `${this.prefix}uids`
@@ -1828,6 +1949,7 @@ export default class SQLite3Driver extends NymphDriver {
1828
1949
  },
1829
1950
  }
1830
1951
  );
1952
+ await this.commit('nymph-rename-uid');
1831
1953
  return true;
1832
1954
  }
1833
1955
 
@@ -1837,16 +1959,26 @@ export default class SQLite3Driver extends NymphDriver {
1837
1959
  'Transaction rollback attempted without a name.'
1838
1960
  );
1839
1961
  }
1840
- if (this.transactionsStarted === 0) {
1962
+ if (this.store.transactionsStarted === 0) {
1841
1963
  return true;
1842
1964
  }
1843
1965
  this.queryRun(`ROLLBACK TO SAVEPOINT ${SQLite3Driver.escape(name)};`);
1844
- this.transactionsStarted--;
1966
+ this.store.transactionsStarted--;
1967
+
1968
+ if (
1969
+ this.store.transactionsStarted === 0 &&
1970
+ this.store.linkWrite &&
1971
+ !this.config.explicitWrite
1972
+ ) {
1973
+ this.store.linkWrite.exec('PRAGMA optimize;');
1974
+ this.store.linkWrite.close();
1975
+ this.store.linkWrite = undefined;
1976
+ }
1977
+
1845
1978
  return true;
1846
1979
  }
1847
1980
 
1848
1981
  public async saveEntity(entity: EntityInterface) {
1849
- this.checkReadOnlyMode();
1850
1982
  const insertData = (
1851
1983
  guid: string,
1852
1984
  data: EntityData,
@@ -1919,6 +2051,12 @@ export default class SQLite3Driver extends NymphDriver {
1919
2051
  return this.saveEntityRowLike(
1920
2052
  entity,
1921
2053
  async (_entity, guid, tags, data, sdata, cdate, etype) => {
2054
+ if (
2055
+ Object.keys(data).length === 0 &&
2056
+ Object.keys(sdata).length === 0
2057
+ ) {
2058
+ return false;
2059
+ }
1922
2060
  this.queryRun(
1923
2061
  `INSERT INTO ${SQLite3Driver.escape(
1924
2062
  `${this.prefix}entities_${etype}`
@@ -1936,6 +2074,12 @@ export default class SQLite3Driver extends NymphDriver {
1936
2074
  return true;
1937
2075
  },
1938
2076
  async (entity, guid, tags, data, sdata, mdate, etype) => {
2077
+ if (
2078
+ Object.keys(data).length === 0 &&
2079
+ Object.keys(sdata).length === 0
2080
+ ) {
2081
+ return false;
2082
+ }
1939
2083
  const info = this.queryRun(
1940
2084
  `UPDATE ${SQLite3Driver.escape(
1941
2085
  `${this.prefix}entities_${etype}`
@@ -2012,7 +2156,7 @@ export default class SQLite3Driver extends NymphDriver {
2012
2156
  if (name == null) {
2013
2157
  throw new InvalidParametersError('Name not given for UID.');
2014
2158
  }
2015
- this.checkReadOnlyMode();
2159
+ await this.startTransaction('nymph-set-uid');
2016
2160
  this.queryRun(
2017
2161
  `DELETE FROM ${SQLite3Driver.escape(
2018
2162
  `${this.prefix}uids`
@@ -2034,6 +2178,7 @@ export default class SQLite3Driver extends NymphDriver {
2034
2178
  },
2035
2179
  }
2036
2180
  );
2181
+ await this.commit('nymph-set-uid');
2037
2182
  return true;
2038
2183
  }
2039
2184
 
@@ -2043,8 +2188,11 @@ export default class SQLite3Driver extends NymphDriver {
2043
2188
  'Transaction start attempted without a name.'
2044
2189
  );
2045
2190
  }
2191
+ if (!this.config.explicitWrite && !this.store.linkWrite) {
2192
+ this._connect(true);
2193
+ }
2046
2194
  this.queryRun(`SAVEPOINT ${SQLite3Driver.escape(name)};`);
2047
- this.transactionsStarted++;
2195
+ this.store.transactionsStarted++;
2048
2196
  return this.nymph;
2049
2197
  }
2050
2198
  }
package/src/conf/d.ts CHANGED
@@ -23,9 +23,28 @@ export interface SQLite3DriverConfig {
23
23
  */
24
24
  timeout: number;
25
25
  /**
26
- * Open for readonly, which is needed for PubSub.
26
+ * Open explicitly for writing.
27
+ *
28
+ * By default, the driver will always open the DB as readonly, and attempt to
29
+ * open another link to perform write operations. If you know that only one
30
+ * instance will be writing, you can force the driver to open for writing by
31
+ * default, which will block any other instance from opening it for writing.
32
+ *
33
+ * One thing to note is that starting a transaction is a write operation, so
34
+ * as long as an instance is in a transaction, no other instances can write.
35
+ *
36
+ * PubSub also needs to open the DB, and it only needs read access.
37
+ */
38
+ explicitWrite: boolean;
39
+ /**
40
+ * Turn on WAL mode.
41
+ *
42
+ * This will generally increase performance, but does mean that the DB must be
43
+ * on a local disk.
44
+ *
45
+ * See: https://www.sqlite.org/wal.html
27
46
  */
28
- readonly: boolean;
47
+ wal: boolean;
29
48
  /**
30
49
  * Function that gets called with every SQL string executed.
31
50
  */
@@ -5,6 +5,7 @@ export default {
5
5
  fileMustExist: false,
6
6
  prefix: 'nymph_',
7
7
  timeout: 10000,
8
- readonly: false,
8
+ explicitWrite: false,
9
+ wal: false,
9
10
  verbose: undefined,
10
11
  } as SQLite3DriverConfig;