@webority-technologies/mobile-core 0.0.1 → 0.0.3

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.
Files changed (146) hide show
  1. package/README.md +22 -1
  2. package/lib/commonjs/api/baseUrl.js +34 -0
  3. package/lib/commonjs/api/curl.js +109 -0
  4. package/lib/commonjs/api/publicClient.js +11 -20
  5. package/lib/commonjs/api/retry.js +75 -0
  6. package/lib/commonjs/api/userClient.js +46 -22
  7. package/lib/commonjs/auth/authStore.js +101 -33
  8. package/lib/commonjs/auth/jwt.js +32 -8
  9. package/lib/commonjs/config/index.js +2 -1
  10. package/lib/commonjs/deepLink/index.js +286 -0
  11. package/lib/commonjs/download/adapters/expoFs.js +100 -0
  12. package/lib/commonjs/download/adapters/shared.js +13 -0
  13. package/lib/commonjs/download/index.js +272 -0
  14. package/lib/commonjs/formatters/date.js +1 -1
  15. package/lib/commonjs/formatters/index.js +24 -0
  16. package/lib/commonjs/formatters/phone.js +132 -15
  17. package/lib/commonjs/geolocation/adapters/expoLocation.js +126 -0
  18. package/lib/commonjs/geolocation/index.js +339 -0
  19. package/lib/commonjs/index.js +151 -48
  20. package/lib/commonjs/initSDK.js +23 -2
  21. package/lib/commonjs/initUserAuth.js +2 -1
  22. package/lib/commonjs/logger/index.js +159 -5
  23. package/lib/commonjs/network/index.js +6 -0
  24. package/lib/commonjs/network/networkStatus.js +50 -15
  25. package/lib/commonjs/permissions/index.js +24 -1
  26. package/lib/commonjs/sqlite/adapters/expo.js +53 -0
  27. package/lib/commonjs/sqlite/adapters/op.js +43 -0
  28. package/lib/commonjs/sqlite/adapters/rows.js +33 -0
  29. package/lib/commonjs/sqlite/adapters/storage.js +50 -0
  30. package/lib/commonjs/sqlite/adapters/sync.js +45 -0
  31. package/lib/commonjs/sqlite/index.js +610 -0
  32. package/lib/commonjs/sqlite/nitro.js +17 -0
  33. package/lib/commonjs/sqlite/op.js +18 -0
  34. package/lib/commonjs/sqlite/quick.js +14 -0
  35. package/lib/commonjs/sqlite/storage.js +14 -0
  36. package/lib/commonjs/storage/index.js +41 -20
  37. package/lib/commonjs/types/globals.d.js +6 -0
  38. package/lib/commonjs/utils/imageCompression.js +51 -14
  39. package/lib/commonjs/utils/index.js +6 -0
  40. package/lib/commonjs/versionCheck/index.js +28 -4
  41. package/lib/module/api/baseUrl.js +30 -0
  42. package/lib/module/api/curl.js +104 -0
  43. package/lib/module/api/publicClient.js +11 -20
  44. package/lib/module/api/retry.js +69 -0
  45. package/lib/module/api/userClient.js +45 -21
  46. package/lib/module/auth/authStore.js +99 -32
  47. package/lib/module/auth/jwt.js +32 -8
  48. package/lib/module/config/index.js +2 -1
  49. package/lib/module/deepLink/index.js +280 -0
  50. package/lib/module/download/adapters/expoFs.js +95 -0
  51. package/lib/module/download/adapters/shared.js +8 -0
  52. package/lib/module/download/index.js +264 -0
  53. package/lib/module/formatters/date.js +1 -1
  54. package/lib/module/formatters/index.js +1 -1
  55. package/lib/module/formatters/phone.js +110 -13
  56. package/lib/module/geolocation/adapters/expoLocation.js +121 -0
  57. package/lib/module/geolocation/index.js +332 -0
  58. package/lib/module/index.js +28 -11
  59. package/lib/module/initSDK.js +23 -2
  60. package/lib/module/initUserAuth.js +2 -1
  61. package/lib/module/logger/index.js +156 -4
  62. package/lib/module/network/index.js +1 -1
  63. package/lib/module/network/networkStatus.js +47 -14
  64. package/lib/module/permissions/index.js +22 -0
  65. package/lib/module/sqlite/adapters/expo.js +48 -0
  66. package/lib/module/sqlite/adapters/op.js +38 -0
  67. package/lib/module/sqlite/adapters/rows.js +28 -0
  68. package/lib/module/sqlite/adapters/storage.js +46 -0
  69. package/lib/module/sqlite/adapters/sync.js +41 -0
  70. package/lib/module/sqlite/index.js +600 -0
  71. package/lib/module/sqlite/nitro.js +12 -0
  72. package/lib/module/sqlite/op.js +13 -0
  73. package/lib/module/sqlite/quick.js +9 -0
  74. package/lib/module/sqlite/storage.js +9 -0
  75. package/lib/module/storage/index.js +38 -19
  76. package/lib/module/types/globals.d.js +10 -0
  77. package/lib/module/utils/imageCompression.js +49 -13
  78. package/lib/module/utils/index.js +1 -1
  79. package/lib/module/versionCheck/index.js +27 -4
  80. package/lib/typescript/commonjs/api/baseUrl.d.ts +20 -0
  81. package/lib/typescript/commonjs/api/curl.d.ts +18 -0
  82. package/lib/typescript/commonjs/api/retry.d.ts +27 -0
  83. package/lib/typescript/commonjs/api/userClient.d.ts +11 -0
  84. package/lib/typescript/commonjs/auth/authStore.d.ts +29 -6
  85. package/lib/typescript/commonjs/config/index.d.ts +6 -0
  86. package/lib/typescript/commonjs/deepLink/index.d.ts +54 -0
  87. package/lib/typescript/commonjs/download/adapters/expoFs.d.ts +45 -0
  88. package/lib/typescript/commonjs/download/adapters/shared.d.ts +3 -0
  89. package/lib/typescript/commonjs/download/index.d.ts +76 -0
  90. package/lib/typescript/commonjs/formatters/index.d.ts +2 -1
  91. package/lib/typescript/commonjs/formatters/phone.d.ts +27 -1
  92. package/lib/typescript/commonjs/geolocation/adapters/expoLocation.d.ts +42 -0
  93. package/lib/typescript/commonjs/geolocation/index.d.ts +89 -0
  94. package/lib/typescript/commonjs/index.d.ts +30 -13
  95. package/lib/typescript/commonjs/initSDK.d.ts +32 -0
  96. package/lib/typescript/commonjs/logger/index.d.ts +39 -0
  97. package/lib/typescript/commonjs/network/index.d.ts +2 -2
  98. package/lib/typescript/commonjs/network/networkStatus.d.ts +16 -0
  99. package/lib/typescript/commonjs/permissions/index.d.ts +35 -0
  100. package/lib/typescript/commonjs/sqlite/adapters/expo.d.ts +30 -0
  101. package/lib/typescript/commonjs/sqlite/adapters/op.d.ts +27 -0
  102. package/lib/typescript/commonjs/sqlite/adapters/rows.d.ts +7 -0
  103. package/lib/typescript/commonjs/sqlite/adapters/storage.d.ts +30 -0
  104. package/lib/typescript/commonjs/sqlite/adapters/sync.d.ts +31 -0
  105. package/lib/typescript/commonjs/sqlite/index.d.ts +121 -0
  106. package/lib/typescript/commonjs/sqlite/nitro.d.ts +7 -0
  107. package/lib/typescript/commonjs/sqlite/op.d.ts +8 -0
  108. package/lib/typescript/commonjs/sqlite/quick.d.ts +4 -0
  109. package/lib/typescript/commonjs/sqlite/storage.d.ts +4 -0
  110. package/lib/typescript/commonjs/storage/index.d.ts +25 -3
  111. package/lib/typescript/commonjs/utils/imageCompression.d.ts +17 -0
  112. package/lib/typescript/commonjs/utils/index.d.ts +2 -2
  113. package/lib/typescript/module/api/baseUrl.d.ts +20 -0
  114. package/lib/typescript/module/api/curl.d.ts +18 -0
  115. package/lib/typescript/module/api/retry.d.ts +27 -0
  116. package/lib/typescript/module/api/userClient.d.ts +11 -0
  117. package/lib/typescript/module/auth/authStore.d.ts +29 -6
  118. package/lib/typescript/module/config/index.d.ts +6 -0
  119. package/lib/typescript/module/deepLink/index.d.ts +54 -0
  120. package/lib/typescript/module/download/adapters/expoFs.d.ts +45 -0
  121. package/lib/typescript/module/download/adapters/shared.d.ts +3 -0
  122. package/lib/typescript/module/download/index.d.ts +76 -0
  123. package/lib/typescript/module/formatters/index.d.ts +2 -1
  124. package/lib/typescript/module/formatters/phone.d.ts +27 -1
  125. package/lib/typescript/module/geolocation/adapters/expoLocation.d.ts +42 -0
  126. package/lib/typescript/module/geolocation/index.d.ts +89 -0
  127. package/lib/typescript/module/index.d.ts +30 -13
  128. package/lib/typescript/module/initSDK.d.ts +32 -0
  129. package/lib/typescript/module/logger/index.d.ts +39 -0
  130. package/lib/typescript/module/network/index.d.ts +2 -2
  131. package/lib/typescript/module/network/networkStatus.d.ts +16 -0
  132. package/lib/typescript/module/permissions/index.d.ts +35 -0
  133. package/lib/typescript/module/sqlite/adapters/expo.d.ts +30 -0
  134. package/lib/typescript/module/sqlite/adapters/op.d.ts +27 -0
  135. package/lib/typescript/module/sqlite/adapters/rows.d.ts +7 -0
  136. package/lib/typescript/module/sqlite/adapters/storage.d.ts +30 -0
  137. package/lib/typescript/module/sqlite/adapters/sync.d.ts +31 -0
  138. package/lib/typescript/module/sqlite/index.d.ts +121 -0
  139. package/lib/typescript/module/sqlite/nitro.d.ts +7 -0
  140. package/lib/typescript/module/sqlite/op.d.ts +8 -0
  141. package/lib/typescript/module/sqlite/quick.d.ts +4 -0
  142. package/lib/typescript/module/sqlite/storage.d.ts +4 -0
  143. package/lib/typescript/module/storage/index.d.ts +25 -3
  144. package/lib/typescript/module/utils/imageCompression.d.ts +17 -0
  145. package/lib/typescript/module/utils/index.d.ts +2 -2
  146. package/package.json +102 -13
@@ -0,0 +1,610 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.whereIn = exports.toSqliteValue = exports.setSqliteImplementation = exports.openDatabase = exports.isSqliteAvailable = exports.fromSqliteBoolean = exports.assertSqlIdentifier = exports.MIGRATIONS_TABLE = exports.DEFAULT_PRAGMAS = void 0;
7
+ var ExpoSqlite = _interopRequireWildcard(require("expo-sqlite"));
8
+ var _index = require("../logger/index.js");
9
+ var _expo = require("./adapters/expo.js");
10
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
11
+ /**
12
+ * This module names NO native package. Metro resolves requires statically, so a
13
+ * driver named here would be bundled by every consumer and would have to be
14
+ * installed by every app, whichever driver it actually chose. Each driver lives
15
+ * behind its own import path, and the app wires one at startup:
16
+ *
17
+ * import { setSqliteImplementation } from '@webority-technologies/mobile-core/sqlite';
18
+ * import { opSqliteDriver } from '@webority-technologies/mobile-core/sqlite/op';
19
+ * setSqliteImplementation(opSqliteDriver());
20
+ *
21
+ * Supported entry points: /sqlite/op, /sqlite/nitro, /sqlite/quick,
22
+ * /sqlite/storage. Any object matching SqliteDriver also works.
23
+ */
24
+
25
+ /** Only what a test or an app injected; null means use expo-sqlite. */
26
+ let backend = null;
27
+
28
+ /**
29
+ * expo-sqlite, adapted once and memoised.
30
+ *
31
+ * Unlike the other surfaces this one keeps a real reason to inject: an app with
32
+ * an existing database on another driver (op-sqlite, nitro, quick,
33
+ * sqlite-storage) can hand it over, and those entry points stay published for
34
+ * exactly that. expo-sqlite is simply what happens when nobody does.
35
+ */
36
+ let expoDriver;
37
+
38
+ /**
39
+ * Inject the SQLite driver. Pass `null` to clear it.
40
+ *
41
+ * `@webority-technologies/mobile-core` ships no database: it is pure JavaScript
42
+ * and defines only this interface. Wire a driver once at startup:
43
+ *
44
+ * import { opSqliteDriver } from '@webority-technologies/mobile-core/sqlite/op';
45
+ * setSqliteImplementation(opSqliteDriver());
46
+ */
47
+ const setSqliteImplementation = impl => {
48
+ backend = impl;
49
+ };
50
+ exports.setSqliteImplementation = setSqliteImplementation;
51
+ const resolveDriver = () => {
52
+ if (backend) {
53
+ return backend;
54
+ }
55
+ if (!expoDriver) {
56
+ expoDriver = (0, _expo.adaptExpoSqlite)(ExpoSqlite);
57
+ }
58
+ return expoDriver;
59
+ };
60
+ const isSqliteAvailable = () => resolveDriver() !== null;
61
+ exports.isSqliteAvailable = isSqliteAvailable;
62
+ const requireBackend = op => {
63
+ const b = resolveDriver();
64
+ if (!b) {
65
+ // Naming the SETTER and an import path, not just packages to install:
66
+ // installing a driver alone does nothing here, and the previous wording
67
+ // ("Install one of ...") sent people to fix a thing that was not broken.
68
+ const err = new Error(`[@webority-technologies/mobile-core] No SQLite driver injected; cannot ${op}. ` + 'Install a driver and wire it once at startup, e.g. ' + "`import { opSqliteDriver } from '@webority-technologies/mobile-core/sqlite/op'` " + 'then `setSqliteImplementation(opSqliteDriver())`. Drivers: @op-engineering/op-sqlite ' + '(recommended), expo-sqlite, react-native-nitro-sqlite, react-native-quick-sqlite, ' + 'react-native-sqlite-storage.');
69
+ _index.Logger.error(err.message);
70
+ throw err;
71
+ }
72
+ return b;
73
+ };
74
+
75
+ /** The ledger of applied migrations. Never emptied by `wipe`. */
76
+ const MIGRATIONS_TABLE = exports.MIGRATIONS_TABLE = '_webority_migrations';
77
+ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
78
+
79
+ /**
80
+ * SQLite cannot bind an identifier to `?`, so every table/column name reaching
81
+ * a statement is concatenated. This is the ONE place that is allowed to happen,
82
+ * and it throws rather than quoting a hostile name into something executable.
83
+ */
84
+ const assertSqlIdentifier = (value, kind = 'identifier') => {
85
+ if (typeof value !== 'string' || !IDENTIFIER.test(value)) {
86
+ throw new Error(`[@webority-technologies/mobile-core] Invalid SQL ${kind} ${JSON.stringify(value)}. ` + 'Only letters, digits and underscores are allowed, and it must not start with a digit. ' + 'Pass values as bound parameters instead of building them into the name.');
87
+ }
88
+ return value;
89
+ };
90
+ exports.assertSqlIdentifier = assertSqlIdentifier;
91
+ const quoteIdentifier = (value, kind = 'identifier') => `"${assertSqlIdentifier(value, kind)}"`;
92
+
93
+ /** For names read back out of sqlite_master, which may legitimately be quoted. */
94
+ const escapeIdentifier = value => `"${value.replace(/"/g, '""')}"`;
95
+ const COLUMN_DEFINITION = /^[A-Za-z0-9_ ,.()'"+*/=<>|%:$-]+$/;
96
+ const assertColumnDefinition = (definition, column) => {
97
+ const trimmed = typeof definition === 'string' ? definition.trim() : '';
98
+ if (trimmed.length === 0 || !COLUMN_DEFINITION.test(trimmed) || trimmed.includes('--')) {
99
+ throw new Error(`[@webority-technologies/mobile-core] Invalid column definition for "${column}": ` + `${JSON.stringify(definition)}. A definition is raw SQL such as ` + "'INTEGER NOT NULL DEFAULT 0' and may not contain a statement separator or a comment.");
100
+ }
101
+ return trimmed;
102
+ };
103
+
104
+ /** Normalise a JS value into something every SQLite driver can bind. */
105
+ const toSqliteValue = value => {
106
+ if (value === undefined) {
107
+ return null;
108
+ }
109
+ if (typeof value === 'boolean') {
110
+ return value ? 1 : 0;
111
+ }
112
+ if (value instanceof Date) {
113
+ return value.toISOString();
114
+ }
115
+ return value;
116
+ };
117
+
118
+ /** SQLite has no boolean type, so a stored flag comes back as 0/1 or '0'/'1'. */
119
+ exports.toSqliteValue = toSqliteValue;
120
+ const fromSqliteBoolean = value => {
121
+ if (typeof value === 'boolean') {
122
+ return value;
123
+ }
124
+ if (typeof value === 'number') {
125
+ return value !== 0;
126
+ }
127
+ if (typeof value === 'string') {
128
+ const normalised = value.trim().toLowerCase();
129
+ return normalised === '1' || normalised === 'true';
130
+ }
131
+ return false;
132
+ };
133
+
134
+ /**
135
+ * Build an `IN (...)` clause with one placeholder per value. An empty list
136
+ * yields a clause that matches nothing, because `IN ()` is a syntax error.
137
+ */
138
+ exports.fromSqliteBoolean = fromSqliteBoolean;
139
+ const whereIn = (column, values) => {
140
+ const quoted = quoteIdentifier(column, 'column name');
141
+ if (values.length === 0) {
142
+ return {
143
+ sql: '1 = 0',
144
+ params: []
145
+ };
146
+ }
147
+ return {
148
+ sql: `${quoted} IN (${values.map(() => '?').join(', ')})`,
149
+ params: values.map(toSqliteValue)
150
+ };
151
+ };
152
+
153
+ /**
154
+ * Either a map of column to value (ANDed, `null` meaning IS NULL) or a raw
155
+ * fragment for anything the object form cannot express.
156
+ */
157
+ exports.whereIn = whereIn;
158
+ const isRawWhere = where => typeof where.sql === 'string' && Object.keys(where).every(key => key === 'sql' || key === 'params');
159
+ const buildWhere = where => {
160
+ if (!where) {
161
+ return {
162
+ clause: '',
163
+ params: []
164
+ };
165
+ }
166
+ if (isRawWhere(where)) {
167
+ const sql = where.sql.trim();
168
+ if (sql.length === 0) {
169
+ return {
170
+ clause: '',
171
+ params: []
172
+ };
173
+ }
174
+ return {
175
+ clause: ` WHERE ${sql}`,
176
+ params: [...(where.params ?? [])]
177
+ };
178
+ }
179
+ const entries = Object.entries(where);
180
+ if (entries.length === 0) {
181
+ return {
182
+ clause: '',
183
+ params: []
184
+ };
185
+ }
186
+ const parts = [];
187
+ const params = [];
188
+ for (const [column, rawValue] of entries) {
189
+ const quoted = quoteIdentifier(column, 'column name');
190
+ const value = toSqliteValue(rawValue);
191
+ if (value === null) {
192
+ parts.push(`${quoted} IS NULL`);
193
+ } else {
194
+ parts.push(`${quoted} = ?`);
195
+ params.push(value);
196
+ }
197
+ }
198
+ return {
199
+ clause: ` WHERE ${parts.join(' AND ')}`,
200
+ params
201
+ };
202
+ };
203
+ const buildOrderBy = orderBy => {
204
+ if (orderBy === undefined) {
205
+ return '';
206
+ }
207
+ const terms = orderBy.split(',').map(term => term.trim()).filter(term => term.length > 0);
208
+ if (terms.length === 0) {
209
+ return '';
210
+ }
211
+ const rendered = terms.map(term => {
212
+ const [column, direction, ...rest] = term.split(/\s+/);
213
+ if (rest.length > 0) {
214
+ throw new Error(`[@webority-technologies/mobile-core] Invalid orderBy term ${JSON.stringify(term)}. ` + 'Expected a column name optionally followed by ASC or DESC.');
215
+ }
216
+ const quoted = quoteIdentifier(column, 'orderBy column');
217
+ if (direction === undefined) {
218
+ return quoted;
219
+ }
220
+ const upper = direction.toUpperCase();
221
+ if (upper !== 'ASC' && upper !== 'DESC') {
222
+ throw new Error(`[@webority-technologies/mobile-core] Invalid sort direction ${JSON.stringify(direction)} ` + `in orderBy ${JSON.stringify(orderBy)}. Expected ASC or DESC.`);
223
+ }
224
+ return `${quoted} ${upper}`;
225
+ });
226
+ return ` ORDER BY ${rendered.join(', ')}`;
227
+ };
228
+ const assertNonNegativeInteger = (value, kind) => {
229
+ if (!Number.isInteger(value) || value < 0) {
230
+ throw new Error(`[@webority-technologies/mobile-core] Invalid ${kind} ${JSON.stringify(value)}. ` + 'Expected a non-negative integer.');
231
+ }
232
+ return value;
233
+ };
234
+ /**
235
+ * FNV-1a with the length mixed in, so two different bodies must also share a
236
+ * length before they can be mistaken for one another. Deliberately hand-rolled:
237
+ * this module ships with zero dependencies.
238
+ */
239
+ const hashString = value => {
240
+ let hash = 0x811c9dc5;
241
+ for (let index = 0; index < value.length; index += 1) {
242
+ hash ^= value.charCodeAt(index);
243
+ hash = Math.imul(hash, 0x01000193) >>> 0;
244
+ }
245
+ return `${hash.toString(16).padStart(8, '0')}-${value.length.toString(16)}`;
246
+ };
247
+
248
+ /**
249
+ * A function migration has no stable text: release bundles are minified, so
250
+ * hashing its source would report a mismatch on every production build. Its
251
+ * body is therefore unverifiable, and only the switch between a function and
252
+ * SQL is detected.
253
+ */
254
+ const migrationChecksum = migration => {
255
+ if (typeof migration.up === 'function') {
256
+ return hashString('fn');
257
+ }
258
+ const sql = Array.isArray(migration.up) ? migration.up.join('\n') : migration.up;
259
+ return hashString(sql);
260
+ };
261
+ const DEFAULT_PRAGMAS = exports.DEFAULT_PRAGMAS = {
262
+ journal_mode: 'WAL',
263
+ synchronous: 'NORMAL',
264
+ foreign_keys: 'ON',
265
+ busy_timeout: 5000
266
+ };
267
+ const PRAGMA_VALUE = /^[A-Za-z0-9_-]+$/;
268
+ const renderPragmaValue = (name, value) => {
269
+ if (typeof value === 'number') {
270
+ if (!Number.isFinite(value)) {
271
+ throw new Error(`[@webority-technologies/mobile-core] Invalid value for PRAGMA ${name}: ${value}.`);
272
+ }
273
+ return String(value);
274
+ }
275
+ if (!PRAGMA_VALUE.test(value)) {
276
+ throw new Error(`[@webority-technologies/mobile-core] Invalid value ${JSON.stringify(value)} for ` + `PRAGMA ${name}. PRAGMA values cannot be bound, so only letters, digits, ` + 'underscores and hyphens are accepted.');
277
+ }
278
+ return value;
279
+ };
280
+ const createDb = (name, raw) => {
281
+ // SQLite has ONE transaction per connection, so two concurrent transaction()
282
+ // calls would interleave their BEGIN/COMMIT and the second BEGIN would be
283
+ // rejected outright. An outbox flush racing a UI write is exactly that case,
284
+ // so transactions queue on this chain rather than run concurrently.
285
+ let txQueue = Promise.resolve();
286
+ const execute = async (sql, params) => {
287
+ try {
288
+ return await raw.execute(sql, params);
289
+ } catch (error) {
290
+ _index.Logger.error(`[@webority-technologies/mobile-core] SQLite execute failed: ${sql}`, error);
291
+ throw error;
292
+ }
293
+ };
294
+ const select = async (sql, params) => {
295
+ const result = await execute(sql, params);
296
+ return result.rows;
297
+ };
298
+ const selectOne = async (sql, params) => {
299
+ const rows = await select(sql, params);
300
+ return rows.length > 0 ? rows[0] : null;
301
+ };
302
+ const runTransaction = async work => {
303
+ await raw.execute('BEGIN');
304
+ let result;
305
+ try {
306
+ result = await work({
307
+ execute,
308
+ select,
309
+ selectOne
310
+ });
311
+ } catch (error) {
312
+ try {
313
+ await raw.execute('ROLLBACK');
314
+ } catch (rollbackError) {
315
+ _index.Logger.error('[@webority-technologies/mobile-core] SQLite ROLLBACK failed', rollbackError);
316
+ }
317
+ throw error;
318
+ }
319
+ try {
320
+ await raw.execute('COMMIT');
321
+ } catch (commitError) {
322
+ // A failed COMMIT leaves the transaction open, which would poison every
323
+ // later one on this connection. Roll back before surfacing it.
324
+ try {
325
+ await raw.execute('ROLLBACK');
326
+ } catch {
327
+ /* the connection is already in a bad state; the commit error is the useful one */
328
+ }
329
+ throw commitError;
330
+ }
331
+ return result;
332
+ };
333
+ const transaction = work => {
334
+ // A rejected link must not break the chain for everyone behind it.
335
+ const next = txQueue.then(() => runTransaction(work), () => runTransaction(work));
336
+ txQueue = next.catch(() => undefined);
337
+ return next;
338
+ };
339
+ const insertSql = (table, row) => {
340
+ const quotedTable = quoteIdentifier(table, 'table name');
341
+ const entries = Object.entries(row);
342
+ if (entries.length === 0) {
343
+ throw new Error(`[@webority-technologies/mobile-core] Cannot insert an empty row into ${table}. ` + 'Pass at least one column.');
344
+ }
345
+ const columns = entries.map(([column]) => quoteIdentifier(column, 'column name'));
346
+ const placeholders = entries.map(() => '?');
347
+ return {
348
+ sql: `INSERT INTO ${quotedTable} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})`,
349
+ params: entries.map(([, value]) => toSqliteValue(value))
350
+ };
351
+ };
352
+ const insert = async (table, row) => {
353
+ const {
354
+ sql,
355
+ params
356
+ } = insertSql(table, row);
357
+ const result = await execute(sql, params);
358
+ return result.insertId;
359
+ };
360
+ const insertMany = async (table, rows) => {
361
+ if (rows.length === 0) {
362
+ return 0;
363
+ }
364
+ // Built before the transaction opens so a bad identifier throws without
365
+ // leaving a BEGIN behind.
366
+ const statements = rows.map(row => insertSql(table, row));
367
+ await transaction(async tx => {
368
+ for (const statement of statements) {
369
+ await tx.execute(statement.sql, statement.params);
370
+ }
371
+ });
372
+ return rows.length;
373
+ };
374
+ const update = async (table, values, where) => {
375
+ const quotedTable = quoteIdentifier(table, 'table name');
376
+ const entries = Object.entries(values);
377
+ if (entries.length === 0) {
378
+ throw new Error(`[@webority-technologies/mobile-core] Cannot update ${table} with no values. ` + 'Pass at least one column to set.');
379
+ }
380
+ const assignments = entries.map(([column]) => `${quoteIdentifier(column, 'column name')} = ?`);
381
+ const params = entries.map(([, value]) => toSqliteValue(value));
382
+ const built = buildWhere(where);
383
+ const result = await execute(`UPDATE ${quotedTable} SET ${assignments.join(', ')}${built.clause}`, [...params, ...built.params]);
384
+ return result.rowsAffected;
385
+ };
386
+ const remove = async (table, where) => {
387
+ const quotedTable = quoteIdentifier(table, 'table name');
388
+ const built = buildWhere(where);
389
+ const result = await execute(`DELETE FROM ${quotedTable}${built.clause}`, built.params);
390
+ return result.rowsAffected;
391
+ };
392
+ const upsert = async (table, row, conflictColumns) => {
393
+ if (conflictColumns.length === 0) {
394
+ throw new Error(`[@webority-technologies/mobile-core] upsert on ${table} needs at least one conflict ` + 'column; without one SQLite has no key to match an existing row on.');
395
+ }
396
+ const {
397
+ sql,
398
+ params
399
+ } = insertSql(table, row);
400
+ const conflictSet = new Set(conflictColumns);
401
+ const targets = conflictColumns.map(column => quoteIdentifier(column, 'column name'));
402
+ const updates = Object.keys(row).filter(column => !conflictSet.has(column)).map(column => {
403
+ const quoted = quoteIdentifier(column, 'column name');
404
+ return `${quoted} = excluded.${quoted}`;
405
+ });
406
+ const action = updates.length > 0 ? `DO UPDATE SET ${updates.join(', ')}` : 'DO NOTHING';
407
+ await execute(`${sql} ON CONFLICT (${targets.join(', ')}) ${action}`, params);
408
+ };
409
+ const findAll = async (table, where, options) => {
410
+ const quotedTable = quoteIdentifier(table, 'table name');
411
+ const built = buildWhere(where);
412
+ const params = [...built.params];
413
+ let sql = `SELECT * FROM ${quotedTable}${built.clause}${buildOrderBy(options?.orderBy)}`;
414
+ if (options?.limit !== undefined) {
415
+ assertNonNegativeInteger(options.limit, 'limit');
416
+ sql += ' LIMIT ?';
417
+ params.push(options.limit);
418
+ } else if (options?.offset !== undefined) {
419
+ // SQLite rejects OFFSET without LIMIT; -1 is its documented "no limit".
420
+ sql += ' LIMIT -1';
421
+ }
422
+ if (options?.offset !== undefined) {
423
+ assertNonNegativeInteger(options.offset, 'offset');
424
+ sql += ' OFFSET ?';
425
+ params.push(options.offset);
426
+ }
427
+ return select(sql, params);
428
+ };
429
+ const findOne = async (table, where) => {
430
+ const rows = await findAll(table, where, {
431
+ limit: 1
432
+ });
433
+ return rows.length > 0 ? rows[0] : null;
434
+ };
435
+ const count = async (table, where) => {
436
+ const quotedTable = quoteIdentifier(table, 'table name');
437
+ const built = buildWhere(where);
438
+ const row = await selectOne(`SELECT COUNT(*) AS cnt FROM ${quotedTable}${built.clause}`, built.params);
439
+ return Number(row?.cnt ?? 0);
440
+ };
441
+ const tableExists = async table => {
442
+ assertSqlIdentifier(table, 'table name');
443
+ const row = await selectOne("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?", [table]);
444
+ return row !== null;
445
+ };
446
+ const createTable = async (table, columns, options) => {
447
+ const quotedTable = quoteIdentifier(table, 'table name');
448
+ const entries = Object.entries(columns);
449
+ if (entries.length === 0) {
450
+ throw new Error(`[@webority-technologies/mobile-core] Cannot create table ${table} with no columns.`);
451
+ }
452
+ const definitions = entries.map(([column, definition]) => `${quoteIdentifier(column, 'column name')} ${assertColumnDefinition(definition, column)}`);
453
+ const guard = options?.ifNotExists === false ? '' : 'IF NOT EXISTS ';
454
+ await execute(`CREATE TABLE ${guard}${quotedTable} (${definitions.join(', ')})`);
455
+ };
456
+ const dropTable = async (table, options) => {
457
+ const quotedTable = quoteIdentifier(table, 'table name');
458
+ const guard = options?.ifExists === false ? '' : 'IF EXISTS ';
459
+ await execute(`DROP TABLE ${guard}${quotedTable}`);
460
+ };
461
+ const ensureLedger = async () => {
462
+ await execute(`CREATE TABLE IF NOT EXISTS ${escapeIdentifier(MIGRATIONS_TABLE)} (` + 'version INTEGER PRIMARY KEY, name TEXT, applied_at TEXT NOT NULL, checksum TEXT NOT NULL)');
463
+ };
464
+ const recordLedgerSql = `INSERT OR REPLACE INTO ${escapeIdentifier(MIGRATIONS_TABLE)} ` + '(version, name, applied_at, checksum) VALUES (?, ?, ?, ?)';
465
+ const wipe = async () => {
466
+ const pragmaRow = await selectOne('PRAGMA foreign_keys');
467
+ const wasOn = Number(pragmaRow?.foreign_keys ?? 0) === 1;
468
+ // PRAGMA foreign_keys is a documented no-op inside a transaction, so the
469
+ // toggle has to straddle it. Left on, deleting a parent table before its
470
+ // children aborts the run and leaves the database half emptied.
471
+ await execute('PRAGMA foreign_keys = OFF');
472
+ try {
473
+ await transaction(async tx => {
474
+ const tables = await tx.select("SELECT name FROM sqlite_master WHERE type = 'table' " + "AND name NOT LIKE 'sqlite_%' AND name <> ?", [MIGRATIONS_TABLE]);
475
+ for (const table of tables) {
476
+ await tx.execute(`DELETE FROM ${escapeIdentifier(table.name)}`);
477
+ }
478
+ });
479
+ } finally {
480
+ await execute(`PRAGMA foreign_keys = ${wasOn ? 'ON' : 'OFF'}`);
481
+ }
482
+ };
483
+ const migrate = async (migrations, options) => {
484
+ const seenVersions = new Set();
485
+ for (const migration of migrations) {
486
+ if (seenVersions.has(migration.version)) {
487
+ throw new Error(`[@webority-technologies/mobile-core] Duplicate migration version ${migration.version} ` + `for database "${name}".`);
488
+ }
489
+ seenVersions.add(migration.version);
490
+ }
491
+ const versionRow = await selectOne('PRAGMA user_version');
492
+ let currentVersion = Number(versionRow?.user_version ?? 0);
493
+ if (migrations.length > 0 && !options?.allowDowngrade) {
494
+ const highest = migrations.reduce((max, m) => Math.max(max, m.version), 0);
495
+ if (currentVersion > highest) {
496
+ throw new Error(`[@webority-technologies/mobile-core] Database "${name}" is at schema version ` + `${currentVersion}, which is newer than the highest migration supplied (${highest}). ` + 'This build would run old code against a newer schema. Ship the missing migrations, ' + 'or pass { allowDowngrade: true } if this build is known to be compatible.');
497
+ }
498
+ }
499
+ await ensureLedger();
500
+ const appliedRows = await select(`SELECT version, checksum FROM ${escapeIdentifier(MIGRATIONS_TABLE)}`);
501
+ const appliedChecksums = new Map(appliedRows.map(row => [Number(row.version), String(row.checksum)]));
502
+ for (const migration of migrations) {
503
+ if (migration.version > currentVersion) {
504
+ continue;
505
+ }
506
+ const recorded = appliedChecksums.get(migration.version);
507
+ // A database migrated before the ledger existed has no row to compare
508
+ // against, which is not a mismatch.
509
+ if (recorded === undefined) {
510
+ continue;
511
+ }
512
+ const checksum = migrationChecksum(migration);
513
+ if (recorded !== checksum) {
514
+ throw new Error(`[@webority-technologies/mobile-core] Migration ${migration.version} has changed since ` + `it was applied to database "${name}" (recorded ${recorded}, now ${checksum}). ` + 'A released migration must never be edited; add a new one instead.');
515
+ }
516
+ }
517
+ const pending = migrations.filter(migration => migration.version > currentVersion).sort((a, b) => a.version - b.version);
518
+ for (const migration of pending) {
519
+ const checksum = migrationChecksum(migration);
520
+ await transaction(async tx => {
521
+ if (typeof migration.up === 'function') {
522
+ await migration.up(tx);
523
+ } else {
524
+ const statements = Array.isArray(migration.up) ? migration.up : [migration.up];
525
+ for (const statement of statements) {
526
+ await tx.execute(statement);
527
+ }
528
+ }
529
+ // The ledger row is written inside the migration's own transaction so
530
+ // it can never disagree with user_version after a partial failure.
531
+ await tx.execute(recordLedgerSql, [migration.version, migration.name ?? null, new Date().toISOString(), checksum]);
532
+ // PRAGMA statements do not accept bound `?` parameters in SQLite, so the
533
+ // version (a validated integer from this module's own loop, never user
534
+ // input) is inlined rather than passed as a param.
535
+ await tx.execute(`PRAGMA user_version = ${migration.version}`);
536
+ });
537
+ currentVersion = migration.version;
538
+ }
539
+ return currentVersion;
540
+ };
541
+ const reset = async migrations => {
542
+ await wipe();
543
+ await ensureLedger();
544
+ await transaction(async tx => {
545
+ await tx.execute(`DELETE FROM ${escapeIdentifier(MIGRATIONS_TABLE)}`);
546
+ await tx.execute('PRAGMA user_version = 0');
547
+ });
548
+ return migrate(migrations);
549
+ };
550
+ const baseline = async (version, migrations) => {
551
+ assertNonNegativeInteger(version, 'baseline version');
552
+ await ensureLedger();
553
+ const appliedAt = new Date().toISOString();
554
+ await transaction(async tx => {
555
+ for (const migration of migrations ?? []) {
556
+ if (migration.version > version) {
557
+ continue;
558
+ }
559
+ await tx.execute(recordLedgerSql, [migration.version, migration.name ?? null, appliedAt, migrationChecksum(migration)]);
560
+ }
561
+ await tx.execute(`PRAGMA user_version = ${version}`);
562
+ });
563
+ };
564
+ const close = async () => {
565
+ await raw.close();
566
+ };
567
+ return {
568
+ name,
569
+ execute,
570
+ select,
571
+ selectOne,
572
+ transaction,
573
+ insert,
574
+ insertMany,
575
+ update,
576
+ remove,
577
+ upsert,
578
+ findAll,
579
+ findOne,
580
+ count,
581
+ tableExists,
582
+ createTable,
583
+ dropTable,
584
+ wipe,
585
+ reset,
586
+ migrate,
587
+ baseline,
588
+ close
589
+ };
590
+ };
591
+ const openDatabase = async (name, options) => {
592
+ const driver = requireBackend('open');
593
+ const raw = await driver.open(name, options ? {
594
+ location: options.location
595
+ } : undefined);
596
+ const db = createDb(name, raw);
597
+ if (options?.pragmas !== false) {
598
+ const pragmas = {
599
+ ...DEFAULT_PRAGMAS,
600
+ ...(options?.pragmas ?? {})
601
+ };
602
+ for (const [pragma, value] of Object.entries(pragmas)) {
603
+ assertSqlIdentifier(pragma, 'PRAGMA name');
604
+ await db.execute(`PRAGMA ${pragma} = ${renderPragmaValue(pragma, value)}`);
605
+ }
606
+ }
607
+ return db;
608
+ };
609
+ exports.openDatabase = openDatabase;
610
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.nitroSqliteDriver = void 0;
7
+ var _sync = require("./adapters/sync.js");
8
+ /**
9
+ * The one place `react-native-nitro-sqlite` is named. Import this module only if
10
+ * you installed it; nothing else in the library reaches it.
11
+ */
12
+ const nitroSqliteDriver = () => {
13
+ const mod = require('react-native-nitro-sqlite');
14
+ return (0, _sync.adaptSyncSqlite)(mod.default ?? mod);
15
+ };
16
+ exports.nitroSqliteDriver = nitroSqliteDriver;
17
+ //# sourceMappingURL=nitro.js.map
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.opSqliteDriver = void 0;
7
+ var _op = require("./adapters/op.js");
8
+ /**
9
+ * The one place `@op-engineering/op-sqlite` is named. It is the driver we
10
+ * recommend: the most actively maintained of the four, and the only one already
11
+ * proven in one of our apps. Import this module only if you installed it.
12
+ */
13
+ const opSqliteDriver = () => {
14
+ const mod = require('@op-engineering/op-sqlite');
15
+ return (0, _op.adaptOpSqlite)(mod.default ?? mod);
16
+ };
17
+ exports.opSqliteDriver = opSqliteDriver;
18
+ //# sourceMappingURL=op.js.map
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.quickSqliteDriver = void 0;
7
+ var _sync = require("./adapters/sync.js");
8
+ /** The one place `react-native-quick-sqlite` is named. */
9
+ const quickSqliteDriver = () => {
10
+ const mod = require('react-native-quick-sqlite');
11
+ return (0, _sync.adaptSyncSqlite)(mod.default ?? mod);
12
+ };
13
+ exports.quickSqliteDriver = quickSqliteDriver;
14
+ //# sourceMappingURL=quick.js.map
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.sqliteStorageDriver = void 0;
7
+ var _storage = require("./adapters/storage.js");
8
+ /** The one place `react-native-sqlite-storage` is named. */
9
+ const sqliteStorageDriver = () => {
10
+ const mod = require('react-native-sqlite-storage');
11
+ return (0, _storage.adaptSqliteStorage)(mod.default ?? mod);
12
+ };
13
+ exports.sqliteStorageDriver = sqliteStorageDriver;
14
+ //# sourceMappingURL=storage.js.map