cozy-pouch-link 60.30.3 → 60.30.6

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.
@@ -39,6 +39,21 @@ function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflec
39
39
 
40
40
  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
41
41
 
42
+ // busy_timeout is per-connection, not per-database, and applies to the
43
+ // statements issued after it. Two values are needed because the statements it
44
+ // covers here do not run on the same thread:
45
+ //
46
+ // - journal_mode=WAL runs through executeSync, ON the JS thread, and needs the
47
+ // write lock to switch. Under a replication batch it would wait for the
48
+ // whole timeout, freezing the UI, hence the short value. Losing that race is
49
+ // harmless: the mode is persistent, so the next open retries it, and it is a
50
+ // no-op once the database is already in WAL.
51
+ // - queries run through executeAsync, OFF the JS thread, so waiting costs
52
+ // latency rather than responsiveness. A short value there would make them
53
+ // fail during a bulk insert and fall back to pouch-find for no reason.
54
+ var SETUP_BUSY_TIMEOUT_MS = 250;
55
+ var QUERY_BUSY_TIMEOUT_MS = 5000;
56
+
42
57
  var SQLiteQueryEngine = /*#__PURE__*/function (_DatabaseQueryEngine) {
43
58
  (0, _inherits2.default)(SQLiteQueryEngine, _DatabaseQueryEngine);
44
59
 
@@ -87,19 +102,31 @@ var SQLiteQueryEngine = /*#__PURE__*/function (_DatabaseQueryEngine) {
87
102
  },
88
103
  get: function get() {
89
104
  if (resolved) return resolved;
90
- resolved = (0, _opSqlite.open)({
105
+ var handle = (0, _opSqlite.open)({
91
106
  name: fileDbName
107
+ }); // Each pragma is independent and best-effort: journal_mode can lose the
108
+ // race for the write lock, and sharing one try/catch would then skip the
109
+ // query timeout and strand the connection on the setup one.
110
+
111
+ var applyPragma = function applyPragma(sql) {
112
+ try {
113
+ handle.executeSync(sql);
114
+ } catch (err) {
115
+ _logger.default.error(err);
116
+ }
117
+ };
118
+
119
+ applyPragma("PRAGMA busy_timeout=".concat(SETUP_BUSY_TIMEOUT_MS));
120
+ applyPragma('PRAGMA journal_mode=WAL');
121
+ applyPragma("PRAGMA busy_timeout=".concat(QUERY_BUSY_TIMEOUT_MS));
122
+ resolved = handle;
123
+ Promise.resolve().then(function () {
124
+ return (0, _sql.executeSQL)(handle, (0, _sql.makeSQLCreateDocIDIndex)());
125
+ }).then(function () {
126
+ return (0, _sql.executeSQL)(handle, (0, _sql.makeSQLCreateDeletedIndex)());
127
+ }).catch(function (err) {
128
+ return _logger.default.error(err);
92
129
  });
93
-
94
- try {
95
- (0, _sql.executeSQL)(resolved, 'PRAGMA journal_mode=WAL');
96
- (0, _sql.executeSQL)(resolved, 'PRAGMA busy_timeout=5000');
97
- (0, _sql.executeSQL)(resolved, (0, _sql.makeSQLCreateDocIDIndex)());
98
- (0, _sql.executeSQL)(resolved, (0, _sql.makeSQLCreateDeletedIndex)());
99
- } catch (err) {
100
- _logger.default.error(err);
101
- }
102
-
103
130
  return resolved;
104
131
  }
105
132
  });
@@ -3,6 +3,8 @@ import PouchDBQueryEngine from '../pouchdb/pouchdb'
3
3
  import SQLiteQueryEngine from './sqliteDb.native'
4
4
 
5
5
  jest.mock('@op-engineering/op-sqlite', () => ({ open: jest.fn() }))
6
+
7
+ import { open } from '@op-engineering/op-sqlite'
6
8
  jest.mock('../pouchdb/pouchdb')
7
9
 
8
10
  // The engine answers a query it cannot translate - or one SQLite rejects - by
@@ -68,3 +70,89 @@ describe('SQLiteQueryEngine find fallback', () => {
68
70
  expect(PouchDBQueryEngine).not.toHaveBeenCalled()
69
71
  })
70
72
  })
73
+
74
+ describe('SQLiteQueryEngine openDB setup', () => {
75
+ const setup = ({ createIndexFails = false } = {}) => {
76
+ const executeSync = jest.fn()
77
+ const executeAsync = jest.fn(() =>
78
+ createIndexFails
79
+ ? Promise.reject(new Error('database is locked'))
80
+ : Promise.resolve({ rows: { length: 0, item: () => undefined } })
81
+ )
82
+ open.mockReturnValue({ executeSync, executeAsync })
83
+ const engine = new SQLiteQueryEngine({ client: {} }, 'io.cozy.files')
84
+ engine.openDB('cozy-files')
85
+ return { engine, executeSync, executeAsync }
86
+ }
87
+
88
+ it('applies the pragmas synchronously so a query cannot outrun them', () => {
89
+ const { engine, executeSync } = setup()
90
+
91
+ expect(engine.db).toBeDefined()
92
+ expect(executeSync).toHaveBeenCalled()
93
+ })
94
+
95
+ it('sets busy_timeout before switching the journal mode', () => {
96
+ const { engine, executeSync } = setup()
97
+ void engine.db
98
+
99
+ const statements = executeSync.mock.calls.map(([sql]) => sql)
100
+ const firstTimeout = statements.findIndex(sql =>
101
+ sql.includes('busy_timeout')
102
+ )
103
+ const journalMode = statements.findIndex(sql =>
104
+ sql.includes('journal_mode')
105
+ )
106
+
107
+ expect(firstTimeout).toBeGreaterThanOrEqual(0)
108
+ expect(journalMode).toBeGreaterThan(firstTimeout)
109
+ })
110
+
111
+ it('keeps a locked CREATE INDEX from becoming an unhandled rejection', async () => {
112
+ const { engine } = setup({ createIndexFails: true })
113
+ const unhandled = jest.fn()
114
+ process.on('unhandledRejection', unhandled)
115
+
116
+ void engine.db
117
+ await new Promise(resolve => setImmediate(resolve))
118
+ await new Promise(resolve => setImmediate(resolve))
119
+
120
+ process.off('unhandledRejection', unhandled)
121
+ expect(unhandled).not.toHaveBeenCalled()
122
+ })
123
+
124
+ it('still returns a usable handle when the pragmas throw', () => {
125
+ open.mockReturnValue({
126
+ executeSync: jest.fn(() => {
127
+ throw new Error('database is locked')
128
+ }),
129
+ executeAsync: jest.fn().mockResolvedValue({})
130
+ })
131
+ const engine = new SQLiteQueryEngine({ client: {} }, 'io.cozy.files')
132
+ engine.openDB('cozy-files')
133
+
134
+ expect(engine.db).toBeDefined()
135
+ })
136
+
137
+ it('still raises the query timeout when the journal-mode switch is refused', () => {
138
+ const executeSync = jest.fn(sql => {
139
+ if (sql.includes('journal_mode')) {
140
+ throw new Error('database is locked')
141
+ }
142
+ })
143
+ open.mockReturnValue({
144
+ executeSync,
145
+ executeAsync: jest.fn().mockResolvedValue({})
146
+ })
147
+ const engine = new SQLiteQueryEngine({ client: {} }, 'io.cozy.files')
148
+ engine.openDB('cozy-files')
149
+
150
+ void engine.db
151
+
152
+ const timeouts = executeSync.mock.calls
153
+ .map(([sql]) => sql)
154
+ .filter(sql => sql.includes('busy_timeout'))
155
+ expect(timeouts).toHaveLength(2)
156
+ expect(timeouts[timeouts.length - 1]).toContain('5000')
157
+ })
158
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-pouch-link",
3
- "version": "60.30.3",
3
+ "version": "60.30.6",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "types/index.d.ts",
@@ -13,7 +13,7 @@
13
13
  "url": "git+https://github.com/linagora/cozy-client.git"
14
14
  },
15
15
  "dependencies": {
16
- "cozy-client": "^60.30.1",
16
+ "cozy-client": "^60.30.6",
17
17
  "pouchdb-browser": "^7.2.2",
18
18
  "pouchdb-find": "^7.2.2"
19
19
  },
@@ -41,5 +41,5 @@
41
41
  "typecheck": "tsc -p tsconfig.json"
42
42
  },
43
43
  "sideEffects": false,
44
- "gitHead": "6dc10895e420d7f6f0782b029a55af82e052e11a"
44
+ "gitHead": "88101a3d13943828cd1cfcf39c40535c0a259afa"
45
45
  }