@nymphjs/driver-sqlite3 1.0.0-beta.4 → 1.0.0-beta.41

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;
150
+ }
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
+ );
88
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);
89
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
  }
@@ -1529,25 +1650,6 @@ export default class SQLite3Driver extends NymphDriver {
1529
1650
  options: Options<T> = {},
1530
1651
  ...selectors: Selector[]
1531
1652
  ): Promise<ReturnType<T['factorySync']>[] | string[] | number> {
1532
- return this.getEntitiesSync(options, ...selectors);
1533
- }
1534
-
1535
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
1536
- options: Options<T> & { return: 'count' },
1537
- ...selectors: Selector[]
1538
- ): number;
1539
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
1540
- options: Options<T> & { return: 'guid' },
1541
- ...selectors: Selector[]
1542
- ): string[];
1543
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
1544
- options?: Options<T>,
1545
- ...selectors: Selector[]
1546
- ): ReturnType<T['factorySync']>[];
1547
- protected getEntitiesSync<T extends EntityConstructor = EntityConstructor>(
1548
- options: Options<T> = {},
1549
- ...selectors: Selector[]
1550
- ): ReturnType<T['factorySync']>[] | string[] | number {
1551
1653
  const { result, process } = this.getEntitesRowLike<T>(
1552
1654
  options,
1553
1655
  selectors,
@@ -1586,7 +1688,7 @@ export default class SQLite3Driver extends NymphDriver {
1586
1688
  if (name == null) {
1587
1689
  throw new InvalidParametersError('Name not given for UID.');
1588
1690
  }
1589
- const result = this.queryGet(
1691
+ const result: any = this.queryGet(
1590
1692
  `SELECT "cur_uid" FROM ${SQLite3Driver.escape(
1591
1693
  `${this.prefix}uids`
1592
1694
  )} WHERE "name"=@name;`,
@@ -1600,7 +1702,6 @@ export default class SQLite3Driver extends NymphDriver {
1600
1702
  }
1601
1703
 
1602
1704
  public async import(filename: string) {
1603
- this.checkReadOnlyMode();
1604
1705
  try {
1605
1706
  return this.importFromFile(
1606
1707
  filename,
@@ -1763,19 +1864,20 @@ export default class SQLite3Driver extends NymphDriver {
1763
1864
  if (name == null) {
1764
1865
  throw new InvalidParametersError('Name not given for UID.');
1765
1866
  }
1766
- this.checkReadOnlyMode();
1767
1867
  await this.startTransaction('nymph-newuid');
1768
1868
  try {
1769
1869
  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
- }
1870
+ (
1871
+ this.queryGet(
1872
+ `SELECT "cur_uid" FROM ${SQLite3Driver.escape(
1873
+ `${this.prefix}uids`
1874
+ )} WHERE "name"=@name;`,
1875
+ {
1876
+ params: {
1877
+ name,
1878
+ },
1879
+ }
1880
+ ) as any
1779
1881
  )?.cur_uid ?? null;
1780
1882
  if (curUid == null) {
1781
1883
  curUid = 1;
@@ -1816,7 +1918,7 @@ export default class SQLite3Driver extends NymphDriver {
1816
1918
  if (oldName == null || newName == null) {
1817
1919
  throw new InvalidParametersError('Name not given for UID.');
1818
1920
  }
1819
- this.checkReadOnlyMode();
1921
+ await this.startTransaction('nymph-rename-uid');
1820
1922
  this.queryRun(
1821
1923
  `UPDATE ${SQLite3Driver.escape(
1822
1924
  `${this.prefix}uids`
@@ -1828,6 +1930,7 @@ export default class SQLite3Driver extends NymphDriver {
1828
1930
  },
1829
1931
  }
1830
1932
  );
1933
+ await this.commit('nymph-rename-uid');
1831
1934
  return true;
1832
1935
  }
1833
1936
 
@@ -1837,16 +1940,26 @@ export default class SQLite3Driver extends NymphDriver {
1837
1940
  'Transaction rollback attempted without a name.'
1838
1941
  );
1839
1942
  }
1840
- if (this.transactionsStarted === 0) {
1943
+ if (this.store.transactionsStarted === 0) {
1841
1944
  return true;
1842
1945
  }
1843
1946
  this.queryRun(`ROLLBACK TO SAVEPOINT ${SQLite3Driver.escape(name)};`);
1844
- this.transactionsStarted--;
1947
+ this.store.transactionsStarted--;
1948
+
1949
+ if (
1950
+ this.store.transactionsStarted === 0 &&
1951
+ this.store.linkWrite &&
1952
+ !this.config.explicitWrite
1953
+ ) {
1954
+ this.store.linkWrite.exec('PRAGMA optimize;');
1955
+ this.store.linkWrite.close();
1956
+ this.store.linkWrite = undefined;
1957
+ }
1958
+
1845
1959
  return true;
1846
1960
  }
1847
1961
 
1848
1962
  public async saveEntity(entity: EntityInterface) {
1849
- this.checkReadOnlyMode();
1850
1963
  const insertData = (
1851
1964
  guid: string,
1852
1965
  data: EntityData,
@@ -1919,6 +2032,12 @@ export default class SQLite3Driver extends NymphDriver {
1919
2032
  return this.saveEntityRowLike(
1920
2033
  entity,
1921
2034
  async (_entity, guid, tags, data, sdata, cdate, etype) => {
2035
+ if (
2036
+ Object.keys(data).length === 0 &&
2037
+ Object.keys(sdata).length === 0
2038
+ ) {
2039
+ return false;
2040
+ }
1922
2041
  this.queryRun(
1923
2042
  `INSERT INTO ${SQLite3Driver.escape(
1924
2043
  `${this.prefix}entities_${etype}`
@@ -1936,6 +2055,12 @@ export default class SQLite3Driver extends NymphDriver {
1936
2055
  return true;
1937
2056
  },
1938
2057
  async (entity, guid, tags, data, sdata, mdate, etype) => {
2058
+ if (
2059
+ Object.keys(data).length === 0 &&
2060
+ Object.keys(sdata).length === 0
2061
+ ) {
2062
+ return false;
2063
+ }
1939
2064
  const info = this.queryRun(
1940
2065
  `UPDATE ${SQLite3Driver.escape(
1941
2066
  `${this.prefix}entities_${etype}`
@@ -2012,7 +2137,7 @@ export default class SQLite3Driver extends NymphDriver {
2012
2137
  if (name == null) {
2013
2138
  throw new InvalidParametersError('Name not given for UID.');
2014
2139
  }
2015
- this.checkReadOnlyMode();
2140
+ await this.startTransaction('nymph-set-uid');
2016
2141
  this.queryRun(
2017
2142
  `DELETE FROM ${SQLite3Driver.escape(
2018
2143
  `${this.prefix}uids`
@@ -2034,6 +2159,7 @@ export default class SQLite3Driver extends NymphDriver {
2034
2159
  },
2035
2160
  }
2036
2161
  );
2162
+ await this.commit('nymph-set-uid');
2037
2163
  return true;
2038
2164
  }
2039
2165
 
@@ -2043,8 +2169,11 @@ export default class SQLite3Driver extends NymphDriver {
2043
2169
  'Transaction start attempted without a name.'
2044
2170
  );
2045
2171
  }
2172
+ if (!this.config.explicitWrite && !this.store.linkWrite) {
2173
+ this._connect(true);
2174
+ }
2046
2175
  this.queryRun(`SAVEPOINT ${SQLite3Driver.escape(name)};`);
2047
- this.transactionsStarted++;
2176
+ this.store.transactionsStarted++;
2048
2177
  return this.nymph;
2049
2178
  }
2050
2179
  }
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;
package/typedoc.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": ["../../typedoc.base.json"],
3
+ "entryPoints": ["src/index.ts"]
4
+ }