@evolu/common 7.4.1 → 8.0.0-next.1

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 (250) hide show
  1. package/README.md +4 -61
  2. package/dist/src/Array.d.ts +343 -102
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +181 -85
  5. package/dist/src/Assert.d.ts +57 -11
  6. package/dist/src/Assert.d.ts.map +1 -1
  7. package/dist/src/Assert.js +47 -11
  8. package/dist/src/BigInt.d.ts +6 -1
  9. package/dist/src/BigInt.d.ts.map +1 -1
  10. package/dist/src/BigInt.js +5 -0
  11. package/dist/src/Brand.d.ts +40 -12
  12. package/dist/src/Brand.d.ts.map +1 -1
  13. package/dist/src/Brand.js +5 -0
  14. package/dist/src/Buffer.d.ts +6 -1
  15. package/dist/src/Buffer.d.ts.map +1 -1
  16. package/dist/src/Buffer.js +7 -2
  17. package/dist/src/Cache.d.ts +10 -2
  18. package/dist/src/Cache.d.ts.map +1 -1
  19. package/dist/src/Cache.js +6 -0
  20. package/dist/src/Callbacks.d.ts +10 -7
  21. package/dist/src/Callbacks.d.ts.map +1 -1
  22. package/dist/src/Callbacks.js +23 -13
  23. package/dist/src/Console.d.ts +361 -69
  24. package/dist/src/Console.d.ts.map +1 -1
  25. package/dist/src/Console.js +217 -117
  26. package/dist/src/Crypto.d.ts +70 -21
  27. package/dist/src/Crypto.d.ts.map +1 -1
  28. package/dist/src/Crypto.js +72 -27
  29. package/dist/src/Eq.d.ts +22 -2
  30. package/dist/src/Eq.d.ts.map +1 -1
  31. package/dist/src/Eq.js +21 -1
  32. package/dist/src/Error.d.ts +23 -12
  33. package/dist/src/Error.d.ts.map +1 -1
  34. package/dist/src/Error.js +27 -11
  35. package/dist/src/Function.d.ts +128 -56
  36. package/dist/src/Function.d.ts.map +1 -1
  37. package/dist/src/Function.js +118 -14
  38. package/dist/src/Identicon.d.ts +5 -0
  39. package/dist/src/Identicon.d.ts.map +1 -1
  40. package/dist/src/Identicon.js +6 -1
  41. package/dist/src/Lookup.d.ts +162 -0
  42. package/dist/src/Lookup.d.ts.map +1 -0
  43. package/dist/src/Lookup.js +200 -0
  44. package/dist/src/Microtask.d.ts +21 -0
  45. package/dist/src/Microtask.d.ts.map +1 -0
  46. package/dist/src/Microtask.js +37 -0
  47. package/dist/src/Number.d.ts +18 -4
  48. package/dist/src/Number.d.ts.map +1 -1
  49. package/dist/src/Number.js +23 -1
  50. package/dist/src/Object.d.ts +110 -5
  51. package/dist/src/Object.d.ts.map +1 -1
  52. package/dist/src/Object.js +108 -5
  53. package/dist/src/Option.d.ts +58 -0
  54. package/dist/src/Option.d.ts.map +1 -0
  55. package/dist/src/Option.js +43 -0
  56. package/dist/src/Order.d.ts +5 -0
  57. package/dist/src/Order.d.ts.map +1 -1
  58. package/dist/src/Order.js +8 -3
  59. package/dist/src/Platform.d.ts +36 -2
  60. package/dist/src/Platform.d.ts.map +1 -1
  61. package/dist/src/Platform.js +10 -6
  62. package/dist/src/Polyfills.d.ts +29 -0
  63. package/dist/src/Polyfills.d.ts.map +1 -0
  64. package/dist/src/Polyfills.js +329 -0
  65. package/dist/src/Random.d.ts +18 -7
  66. package/dist/src/Random.d.ts.map +1 -1
  67. package/dist/src/Random.js +9 -7
  68. package/dist/src/Redacted.d.ts +7 -4
  69. package/dist/src/Redacted.d.ts.map +1 -1
  70. package/dist/src/Redacted.js +5 -0
  71. package/dist/src/Ref.d.ts +31 -16
  72. package/dist/src/Ref.d.ts.map +1 -1
  73. package/dist/src/Ref.js +35 -7
  74. package/dist/src/RefCount.d.ts +62 -0
  75. package/dist/src/RefCount.d.ts.map +1 -0
  76. package/dist/src/RefCount.js +83 -0
  77. package/dist/src/Relation.d.ts +84 -29
  78. package/dist/src/Relation.d.ts.map +1 -1
  79. package/dist/src/Relation.js +83 -105
  80. package/dist/src/Resource.d.ts +263 -0
  81. package/dist/src/Resource.d.ts.map +1 -0
  82. package/dist/src/Resource.js +389 -0
  83. package/dist/src/Result.d.ts +390 -374
  84. package/dist/src/Result.d.ts.map +1 -1
  85. package/dist/src/Result.js +142 -70
  86. package/dist/src/Schedule.d.ts +953 -0
  87. package/dist/src/Schedule.d.ts.map +1 -0
  88. package/dist/src/Schedule.js +1199 -0
  89. package/dist/src/Set.d.ts +181 -0
  90. package/dist/src/Set.d.ts.map +1 -0
  91. package/dist/src/Set.js +137 -0
  92. package/dist/src/Skiplist.d.ts +6 -1
  93. package/dist/src/Skiplist.d.ts.map +1 -1
  94. package/dist/src/Skiplist.js +5 -0
  95. package/dist/src/Sqlite.d.ts +160 -60
  96. package/dist/src/Sqlite.d.ts.map +1 -1
  97. package/dist/src/Sqlite.js +290 -190
  98. package/dist/src/Store.d.ts +36 -28
  99. package/dist/src/Store.d.ts.map +1 -1
  100. package/dist/src/Store.js +55 -15
  101. package/dist/src/String.d.ts +5 -0
  102. package/dist/src/String.d.ts.map +1 -1
  103. package/dist/src/String.js +5 -0
  104. package/dist/src/Task.d.ts +2274 -423
  105. package/dist/src/Task.d.ts.map +1 -1
  106. package/dist/src/Task.js +1499 -348
  107. package/dist/src/Test.d.ts +150 -0
  108. package/dist/src/Test.d.ts.map +1 -0
  109. package/dist/src/Test.js +97 -0
  110. package/dist/src/Time.d.ts +202 -48
  111. package/dist/src/Time.d.ts.map +1 -1
  112. package/dist/src/Time.js +199 -88
  113. package/dist/src/Tracer.d.ts +48 -0
  114. package/dist/src/Tracer.d.ts.map +1 -0
  115. package/dist/src/Tracer.js +6 -0
  116. package/dist/src/Type.d.ts +587 -209
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +370 -240
  119. package/dist/src/Types.d.ts +160 -6
  120. package/dist/src/Types.d.ts.map +1 -1
  121. package/dist/src/Types.js +22 -2
  122. package/dist/src/WebSocket.d.ts +130 -58
  123. package/dist/src/WebSocket.d.ts.map +1 -1
  124. package/dist/src/WebSocket.js +274 -113
  125. package/dist/src/Worker.d.ts +245 -60
  126. package/dist/src/Worker.d.ts.map +1 -1
  127. package/dist/src/Worker.js +220 -64
  128. package/dist/src/index.d.ts +25 -3
  129. package/dist/src/index.d.ts.map +1 -1
  130. package/dist/src/index.js +19 -3
  131. package/dist/src/local-first/Db.d.ts +24 -213
  132. package/dist/src/local-first/Db.d.ts.map +1 -1
  133. package/dist/src/local-first/Db.js +621 -293
  134. package/dist/src/local-first/Error.d.ts +12 -0
  135. package/dist/src/local-first/Error.d.ts.map +1 -0
  136. package/dist/src/local-first/Error.js +6 -0
  137. package/dist/src/local-first/Evolu.d.ts +330 -285
  138. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  139. package/dist/src/local-first/Evolu.js +472 -402
  140. package/dist/src/local-first/LocalAuth.d.ts +10 -10
  141. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  142. package/dist/src/local-first/LocalAuth.js +6 -1
  143. package/dist/src/local-first/Owner.d.ts +70 -27
  144. package/dist/src/local-first/Owner.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.js +27 -14
  146. package/dist/src/local-first/Protocol.d.ts +58 -50
  147. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  148. package/dist/src/local-first/Protocol.js +253 -155
  149. package/dist/src/local-first/Query.d.ts +133 -43
  150. package/dist/src/local-first/Query.d.ts.map +1 -1
  151. package/dist/src/local-first/Query.js +139 -90
  152. package/dist/src/local-first/Relay.d.ts +34 -49
  153. package/dist/src/local-first/Relay.d.ts.map +1 -1
  154. package/dist/src/local-first/Relay.js +59 -177
  155. package/dist/src/local-first/Schema.d.ts +139 -144
  156. package/dist/src/local-first/Schema.d.ts.map +1 -1
  157. package/dist/src/local-first/Schema.js +78 -163
  158. package/dist/src/local-first/Shared.d.ts +125 -0
  159. package/dist/src/local-first/Shared.d.ts.map +1 -0
  160. package/dist/src/local-first/Shared.js +569 -0
  161. package/dist/src/local-first/Storage.d.ts +54 -58
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +127 -179
  164. package/dist/src/local-first/Timestamp.d.ts +28 -35
  165. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  166. package/dist/src/local-first/Timestamp.js +24 -25
  167. package/dist/src/local-first/index.d.ts +2 -15
  168. package/dist/src/local-first/index.d.ts.map +1 -1
  169. package/dist/src/local-first/index.js +2 -15
  170. package/package.json +37 -24
  171. package/src/Array.ts +467 -106
  172. package/src/Assert.ts +87 -12
  173. package/src/BigInt.ts +7 -1
  174. package/src/Brand.ts +41 -12
  175. package/src/Buffer.ts +10 -4
  176. package/src/Cache.ts +11 -2
  177. package/src/Callbacks.ts +33 -23
  178. package/src/Console.ts +567 -178
  179. package/src/Crypto.ts +111 -62
  180. package/src/Eq.ts +30 -3
  181. package/src/Error.ts +50 -34
  182. package/src/Function.ts +140 -68
  183. package/src/Identicon.ts +7 -1
  184. package/src/Lookup.ts +429 -0
  185. package/src/Microtask.ts +58 -0
  186. package/src/Number.ts +40 -7
  187. package/src/Object.ts +155 -16
  188. package/src/Option.ts +74 -0
  189. package/src/Order.ts +11 -3
  190. package/src/Platform.ts +43 -7
  191. package/src/Polyfills.ts +520 -0
  192. package/src/Random.ts +25 -11
  193. package/src/Redacted.ts +8 -4
  194. package/src/Ref.ts +72 -21
  195. package/src/RefCount.ts +170 -0
  196. package/src/Relation.ts +188 -136
  197. package/src/Resource.ts +864 -0
  198. package/src/Result.ts +548 -381
  199. package/src/Schedule.ts +1471 -0
  200. package/src/Set.ts +247 -0
  201. package/src/Skiplist.ts +7 -1
  202. package/src/Sqlite.ts +409 -304
  203. package/src/Store.ts +94 -48
  204. package/src/String.ts +6 -0
  205. package/src/Task.ts +3696 -740
  206. package/src/Test.ts +202 -0
  207. package/src/Time.ts +357 -147
  208. package/src/Tracer.ts +54 -0
  209. package/src/Type.ts +866 -430
  210. package/src/Types.ts +202 -7
  211. package/src/WebSocket.ts +404 -167
  212. package/src/Worker.ts +527 -147
  213. package/src/index.ts +74 -3
  214. package/src/local-first/Db.ts +867 -597
  215. package/src/local-first/Error.ts +17 -0
  216. package/src/local-first/Evolu.ts +884 -824
  217. package/src/local-first/LocalAuth.ts +10 -10
  218. package/src/local-first/Owner.ts +94 -42
  219. package/src/local-first/Protocol.ts +441 -307
  220. package/src/local-first/Query.ts +243 -194
  221. package/src/local-first/Relay.ts +119 -273
  222. package/src/local-first/Schema.ts +284 -394
  223. package/src/local-first/Shared.ts +932 -0
  224. package/src/local-first/Storage.ts +211 -283
  225. package/src/local-first/Timestamp.ts +38 -45
  226. package/src/local-first/index.ts +2 -16
  227. package/dist/src/Instances.d.ts +0 -34
  228. package/dist/src/Instances.d.ts.map +0 -1
  229. package/dist/src/Instances.js +0 -44
  230. package/dist/src/Resources.d.ts +0 -118
  231. package/dist/src/Resources.d.ts.map +0 -1
  232. package/dist/src/Resources.js +0 -197
  233. package/dist/src/local-first/Platform.d.ts +0 -25
  234. package/dist/src/local-first/Platform.d.ts.map +0 -1
  235. package/dist/src/local-first/Platform.js +0 -1
  236. package/dist/src/local-first/Public.d.ts +0 -22
  237. package/dist/src/local-first/Public.d.ts.map +0 -1
  238. package/dist/src/local-first/Public.js +0 -15
  239. package/dist/src/local-first/PublicKysely.d.ts +0 -148
  240. package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
  241. package/dist/src/local-first/PublicKysely.js +0 -184
  242. package/dist/src/local-first/Sync.d.ts +0 -112
  243. package/dist/src/local-first/Sync.d.ts.map +0 -1
  244. package/dist/src/local-first/Sync.js +0 -526
  245. package/src/Instances.ts +0 -92
  246. package/src/Resources.ts +0 -367
  247. package/src/local-first/Platform.ts +0 -27
  248. package/src/local-first/Public.ts +0 -42
  249. package/src/local-first/PublicKysely.ts +0 -239
  250. package/src/local-first/Sync.ts +0 -949
@@ -1,135 +1,283 @@
1
- import { firstInArray, isNonEmptyArray, } from "../Array.js";
2
- import { assertNonEmptyReadonlyArray } from "../Assert.js";
3
- import { createSymmetricCrypto, } from "../Crypto.js";
4
- import { ok } from "../Result.js";
5
- import { createSqlite, sql, } from "../Sqlite.js";
6
- import { SimpleName } from "../Type.js";
7
- import { createInitializedWorkerWithHandlers, } from "../Worker.js";
8
- import { createAppOwner, createOwnerSecret, mnemonicToOwnerSecret, } from "./Owner.js";
9
- import { protocolVersion } from "./Protocol.js";
10
- import { createGetQueryRowsCache, loadQueries, } from "./Query.js";
11
- import { ensureDbSchema, getDbSchema, } from "./Schema.js";
12
- import { createBaseSqliteStorageTables } from "./Storage.js";
13
- import { applyLocalOnlyChange, createClock, createSync, tryApplyQuarantinedMessages, } from "./Sync.js";
14
- import { timestampBytesToTimestamp, timestampToTimestampBytes, } from "./Timestamp.js";
15
- export const defaultDbConfig = {
16
- name: SimpleName.orThrow("Evolu"),
17
- transports: [{ type: "WebSocket", url: "wss://free.evoluhq.com" }],
18
- maxDrift: 5 * 60 * 1000,
19
- enableLogging: false,
20
- };
21
- export const createDbWorkerForPlatform = (platformDeps) => createInitializedWorkerWithHandlers({
22
- init: async (initMessage, postMessage) => {
23
- platformDeps.console.enabled = initMessage.config.enableLogging ?? false;
24
- const deps = await createDbWorkerDeps(platformDeps, initMessage, postMessage);
25
- if (!deps.ok) {
26
- postMessage({ type: "onError", error: deps.error });
27
- return null;
28
- }
29
- return deps.value;
30
- },
31
- handlers,
32
- });
33
- const createDbWorkerDeps = async (platformDeps, initMessage, postMessage) => {
34
- const sqlite = await createSqlite(platformDeps)(initMessage.config.name, {
35
- memory: initMessage.config.inMemory ?? false,
36
- encryptionKey: initMessage.config.encryptionKey ?? undefined,
37
- });
38
- if (!sqlite.ok)
39
- return sqlite;
40
- const deps = { ...platformDeps, sqlite: sqlite.value };
41
- return deps.sqlite.transaction(() => {
42
- const dbSchema = getDbSchema(deps)();
43
- if (!dbSchema.ok)
44
- return dbSchema;
45
- const dbIsInitialized = "evolu_version" in dbSchema.value.tables;
46
- let appOwner;
47
- let clock;
48
- if (dbIsInitialized) {
49
- const currentVersion = deps.sqlite.exec(sql `select protocolVersion from evolu_version limit 1;`);
50
- if (!currentVersion.ok)
51
- return currentVersion;
52
- const configResult = deps.sqlite.exec(sql `
53
- select
54
- clock,
55
- appOwnerId,
56
- appOwnerEncryptionKey,
57
- appOwnerWriteKey,
58
- appOwnerMnemonic
59
- from evolu_config
60
- limit 1;
61
- `);
62
- if (!configResult.ok)
63
- return configResult;
64
- assertNonEmptyReadonlyArray(configResult.value.rows);
65
- const config = firstInArray(configResult.value.rows);
66
- appOwner = {
67
- type: "AppOwner",
68
- id: config.appOwnerId,
69
- encryptionKey: config.appOwnerEncryptionKey,
70
- writeKey: config.appOwnerWriteKey,
71
- mnemonic: config.appOwnerMnemonic,
72
- };
73
- clock = createClock(deps)(timestampBytesToTimestamp(config.clock));
1
+ /**
2
+ * Platform-agnostic Evolu DbWorker.
3
+ *
4
+ * @module
5
+ */
6
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
7
+ if (value !== null && value !== void 0) {
8
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
9
+ var dispose, inner;
10
+ if (async) {
11
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
12
+ dispose = value[Symbol.asyncDispose];
74
13
  }
75
- else {
76
- appOwner =
77
- initMessage.config.externalAppOwner ??
78
- createAppOwner(createOwnerSecret(platformDeps));
79
- clock = createClock(deps)();
80
- const result = initializeDb(deps)(appOwner, clock.get());
81
- if (!result.ok)
82
- return result;
14
+ if (dispose === void 0) {
15
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
16
+ dispose = value[Symbol.dispose];
17
+ if (async) inner = dispose;
83
18
  }
84
- {
85
- const result = ensureDbSchema(deps)(initMessage.dbSchema, dbSchema.value);
86
- if (!result.ok)
87
- return result;
19
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
20
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
21
+ env.stack.push({ value: value, dispose: dispose, async: async });
22
+ }
23
+ else if (async) {
24
+ env.stack.push({ async: true });
25
+ }
26
+ return value;
27
+ };
28
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
29
+ return function (env) {
30
+ function fail(e) {
31
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
32
+ env.hasError = true;
88
33
  }
89
- {
90
- const result = ensureMessageQuarantineTable(deps);
91
- if (!result.ok)
92
- return result;
34
+ var r, s = 0;
35
+ function next() {
36
+ while (r = env.stack.pop()) {
37
+ try {
38
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
39
+ if (r.dispose) {
40
+ var result = r.dispose.call(r.value);
41
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
42
+ }
43
+ else s |= 1;
44
+ }
45
+ catch (e) {
46
+ fail(e);
47
+ }
48
+ }
49
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
50
+ if (env.hasError) throw env.error;
93
51
  }
94
- const sync = createSync({
95
- ...deps,
96
- clock,
97
- symmetricCrypto: createSymmetricCrypto(platformDeps),
98
- timestampConfig: initMessage.config,
99
- dbSchema: initMessage.dbSchema,
100
- })({
101
- appOwner,
102
- transports: initMessage.config.transports,
52
+ return next();
53
+ };
54
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
55
+ var e = new Error(message);
56
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
57
+ });
58
+ import { appendToArray, firstInArray, } from "../Array.js";
59
+ import { assert, assertNonEmptyReadonlyArray, assertType } from "../Assert.js";
60
+ import { EncryptionKey, } from "../Crypto.js";
61
+ import { exhaustiveCheck, lazyFalse, lazyVoid } from "../Function.js";
62
+ import { createRecord, getProperty, objectToEntries } from "../Object.js";
63
+ import { ok } from "../Result.js";
64
+ import { booleanToSqliteBoolean, createSqlite, sql, SqliteBoolean, sqliteBooleanToBoolean, sqliteQueryStringToSqliteQuery, SqliteValue, } from "../Sqlite.js";
65
+ import { callback } from "../Task.js";
66
+ import { Millis, millisToDateIso } from "../Time.js";
67
+ import { Id, IdBytes, idBytesToId, idToIdBytes, onePositiveInt, } from "../Type.js";
68
+ import { ownerIdBytesToOwnerId, ownerIdToOwnerIdBytes } from "./Owner.js";
69
+ import { applyProtocolMessageAsClient, createProtocolMessageForSync, decryptAndDecodeDbChange, encodeAndEncryptDbChange, protocolVersion, SubscriptionFlags, } from "./Protocol.js";
70
+ import { ensureSqliteSchema, getEvoluSqliteSchema, systemColumns, } from "./Schema.js";
71
+ import { createBaseSqliteStorage, createBaseSqliteStorageTables, DbChange, getTimestampInsertStrategy, readOwnerUsageOrDefault, updateOwnerUsage, } from "./Storage.js";
72
+ import { createInitialTimestamp, defaultTimestampMaxDrift, receiveTimestamp, sendTimestamp, TimestampBytes, timestampBytesToTimestamp, timestampToTimestampBytes, } from "./Timestamp.js";
73
+ export const startDbWorker = (self) => async (_run) => {
74
+ const env_1 = { stack: [], error: void 0, hasError: false };
75
+ try {
76
+ const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
77
+ const run = stack.use(_run.create());
78
+ const { deps } = run;
79
+ let initialized = false;
80
+ const initMessage = await run.orThrow(callback(({ ok }) => {
81
+ self.onMessage = (message) => {
82
+ assert(!initialized, "DbWorker must be initialized only once");
83
+ initialized = true;
84
+ ok(message);
85
+ };
86
+ }));
87
+ const console = deps.console.child(initMessage.name).child("DbWorker");
88
+ console.setLevel(initMessage.consoleLevel);
89
+ console.info("start DbWorker");
90
+ const port = stack.use(deps.createMessagePort(initMessage.port));
91
+ stack.defer(deps.consoleStoreOutputEntry.subscribe(() => {
92
+ const entry = deps.consoleStoreOutputEntry.get();
93
+ if (entry)
94
+ port.postMessage({ type: "OnConsoleEntry", entry });
95
+ }));
96
+ stack.use(await run.orThrow(deps.leaderLock.lock(initMessage.name)));
97
+ if (stack.disposed)
98
+ return ok();
99
+ port.postMessage({ type: "LeaderAcquired", name: initMessage.name });
100
+ const sqlite = stack.use(await run.orThrow(createSqlite(initMessage.name, initMessage.memoryOnly
101
+ ? { mode: "memory" }
102
+ : { mode: "encrypted", encryptionKey: initMessage.encryptionKey })));
103
+ console.debug("SQLite created");
104
+ const baseSqliteStorage = createBaseSqliteStorage({ sqlite, ...run.deps });
105
+ const dbDeps = {
106
+ ...run.deps,
107
+ sqlite,
108
+ sqliteSchema: initMessage.sqliteSchema,
109
+ baseSqliteStorage,
110
+ timestampConfig: { maxDrift: defaultTimestampMaxDrift },
111
+ };
112
+ const currentSchema = getEvoluSqliteSchema(dbDeps)();
113
+ const dbIsInitialized = "evolu_version" in currentSchema.tables;
114
+ const clock = createClock(dbDeps)(dbIsInitialized);
115
+ sqlite.transaction(() => {
116
+ if (!dbIsInitialized)
117
+ initializeDb(dbDeps)(clock.get());
118
+ ensureSqliteSchema(dbDeps)(initMessage.sqliteSchema, currentSchema);
119
+ tryApplyQuarantinedMessages(dbDeps);
120
+ });
121
+ const storage = createClientStorage({ ...dbDeps, clock })({
103
122
  onError: (error) => {
104
- postMessage({ type: "onError", error });
105
- },
106
- onReceive: () => {
107
- postMessage({ type: "refreshQueries" });
123
+ port.postMessage({ type: "OnError", error });
108
124
  },
109
125
  });
110
- if (!sync.ok)
111
- return sync;
112
- {
113
- const result = tryApplyQuarantinedMessages({
114
- ...deps,
115
- dbSchema: initMessage.dbSchema,
116
- })();
117
- if (!result.ok)
118
- return result;
119
- }
120
- sync.value.useOwner(true, appOwner);
121
- return ok({
122
- ...deps,
123
- getQueryRowsCache: createGetQueryRowsCache(),
124
- postMessage,
125
- sync: sync.value,
126
- appOwner,
127
- });
128
- });
126
+ // TODO: Call on dispose message.
127
+ const _moved = stack.move();
128
+ const runWithStorage = run.addDeps({ storage });
129
+ /**
130
+ * SharedWorker retries until some leader replies, so this worker must
131
+ * ignore callback IDs it already completed.
132
+ *
133
+ * The Set intentionally lives for one leader's lifetime. If that leader tab
134
+ * closes, retries go to a new leader with a fresh Set. Revisit this only if
135
+ * memory pressure shows up; even millions of short callback IDs are
136
+ * acceptable here, and naive eviction could let a still-retried request run
137
+ * again.
138
+ */
139
+ const processedRequestIds = new Set();
140
+ port.onMessage = ({ callbackId, request }) => {
141
+ if (processedRequestIds.has(callbackId))
142
+ return;
143
+ processedRequestIds.add(callbackId);
144
+ const postQueuedResponse = (response) => {
145
+ port.postMessage({ type: "OnQueuedResponse", callbackId, response }, response.type === "ForEvolu" && response.message.type === "Export"
146
+ ? [response.message.file.buffer]
147
+ : undefined);
148
+ };
149
+ switch (request.type) {
150
+ case "ForEvolu": {
151
+ switch (request.message.type) {
152
+ case "Mutate": {
153
+ const result = handleMutation({ ...dbDeps, clock })(request.message);
154
+ if (!result.ok) {
155
+ port.postMessage({ type: "OnError", error: result.error });
156
+ }
157
+ else {
158
+ postQueuedResponse({
159
+ type: "ForEvolu",
160
+ evoluPortId: request.evoluPortId,
161
+ message: result.value,
162
+ });
163
+ }
164
+ break;
165
+ }
166
+ case "Query":
167
+ postQueuedResponse({
168
+ type: "ForEvolu",
169
+ evoluPortId: request.evoluPortId,
170
+ message: {
171
+ type: "Query",
172
+ rowsByQuery: loadQueries(dbDeps)(request.message.queries),
173
+ },
174
+ });
175
+ break;
176
+ case "Export":
177
+ postQueuedResponse({
178
+ type: "ForEvolu",
179
+ evoluPortId: request.evoluPortId,
180
+ message: {
181
+ type: "Export",
182
+ file: dbDeps.sqlite.export(),
183
+ },
184
+ });
185
+ break;
186
+ default:
187
+ exhaustiveCheck(request.message);
188
+ }
189
+ break;
190
+ }
191
+ case "ForSharedWorker": {
192
+ switch (request.message.type) {
193
+ case "CreateSyncMessages": {
194
+ const protocolMessagesByOwnerId = new Map();
195
+ for (const owner of request.message.owners) {
196
+ storage.setRequestContext(owner.encryptionKey);
197
+ const protocolMessage = createProtocolMessageForSync({
198
+ storage,
199
+ console,
200
+ })(owner.id, SubscriptionFlags.Subscribe);
201
+ protocolMessagesByOwnerId.set(owner.id, protocolMessage);
202
+ }
203
+ postQueuedResponse({
204
+ type: "ForSharedWorker",
205
+ message: {
206
+ type: "CreateSyncMessages",
207
+ protocolMessagesByOwnerId,
208
+ },
209
+ });
210
+ break;
211
+ }
212
+ case "ApplySyncMessage": {
213
+ const { owner, inputMessage } = request.message;
214
+ runWithStorage(async (run) => {
215
+ storage.setRequestContext(owner.encryptionKey);
216
+ const result = await run(applyProtocolMessageAsClient(inputMessage, {
217
+ writeKey: owner.writeKey,
218
+ }));
219
+ const didWriteMessages = storage.didWriteMessages();
220
+ postQueuedResponse({
221
+ type: "ForSharedWorker",
222
+ message: {
223
+ type: "ApplySyncMessage",
224
+ ownerId: owner.id,
225
+ didWriteMessages,
226
+ result,
227
+ },
228
+ });
229
+ return ok();
230
+ });
231
+ break;
232
+ }
233
+ default:
234
+ exhaustiveCheck(request.message);
235
+ }
236
+ break;
237
+ }
238
+ default:
239
+ exhaustiveCheck(request);
240
+ }
241
+ };
242
+ return ok();
243
+ }
244
+ catch (e_1) {
245
+ env_1.error = e_1;
246
+ env_1.hasError = true;
247
+ }
248
+ finally {
249
+ const result_1 = __disposeResources(env_1);
250
+ if (result_1)
251
+ await result_1;
252
+ }
129
253
  };
130
- const initializeDb = (deps) => (initialAppOwner, initialClock) => {
254
+ const createClock = (deps) => (dbIsInitialized) => {
255
+ let currentTimestamp;
256
+ if (dbIsInitialized) {
257
+ const { rows } = deps.sqlite.exec(sql `
258
+ select clock
259
+ from evolu_config
260
+ limit 1;
261
+ `);
262
+ assertNonEmptyReadonlyArray(rows);
263
+ currentTimestamp = timestampBytesToTimestamp(firstInArray(rows).clock);
264
+ }
265
+ else {
266
+ currentTimestamp = createInitialTimestamp(deps);
267
+ }
268
+ return {
269
+ get: () => currentTimestamp,
270
+ save: (timestamp) => {
271
+ currentTimestamp = timestamp;
272
+ deps.sqlite.exec(sql.prepared `
273
+ update evolu_config
274
+ set "clock" = ${timestampToTimestampBytes(timestamp)};
275
+ `);
276
+ },
277
+ };
278
+ };
279
+ const initializeDb = ({ sqlite }) => (initialClock) => {
131
280
  for (const query of [
132
- // Never change structure to ensure all versions can read it.
133
281
  sql `
134
282
  create table evolu_version (
135
283
  "protocolVersion" integer not null
@@ -142,31 +290,13 @@ const initializeDb = (deps) => (initialAppOwner, initialClock) => {
142
290
  `,
143
291
  sql `
144
292
  create table evolu_config (
145
- "clock" blob not null,
146
- "appOwnerId" text not null,
147
- "appOwnerEncryptionKey" blob not null,
148
- "appOwnerWriteKey" blob not null,
149
- "appOwnerMnemonic" text
293
+ "clock" blob not null
150
294
  )
151
295
  strict;
152
296
  `,
153
297
  sql `
154
- insert into evolu_config
155
- (
156
- "clock",
157
- "appOwnerId",
158
- "appOwnerEncryptionKey",
159
- "appOwnerWriteKey",
160
- "appOwnerMnemonic"
161
- )
162
- values
163
- (
164
- ${timestampToTimestampBytes(initialClock)},
165
- ${initialAppOwner.id},
166
- ${initialAppOwner.encryptionKey},
167
- ${initialAppOwner.writeKey},
168
- ${initialAppOwner.mnemonic ?? null}
169
- );
298
+ insert into evolu_config ("clock")
299
+ values (${timestampToTimestampBytes(initialClock)});
170
300
  `,
171
301
  /**
172
302
  * The History table stores all values per ownerId, timestamp, table, id,
@@ -202,167 +332,365 @@ const initializeDb = (deps) => (initialAppOwner, initialClock) => {
202
332
  "timestamp" desc
203
333
  );
204
334
  `,
335
+ /**
336
+ * Stores messages with unknown schema in a quarantine table.
337
+ *
338
+ * When a device receives sync messages containing tables or columns that
339
+ * don't exist in its current schema (e.g., from a newer app version),
340
+ * those messages are stored here instead of being discarded. This enables
341
+ * forward compatibility:
342
+ *
343
+ * 1. Unknown data is preserved and can be applied when the app is updated
344
+ * 2. Messages are still propagated to other devices that may understand them
345
+ * 3. Partial messages work - known columns go to app tables, unknown to
346
+ * quarantine
347
+ *
348
+ * The `union all` query in `readDbChange` combines `evolu_history` and
349
+ * this table, ensuring all data (known and unknown) is included when
350
+ * syncing to other devices.
351
+ */
352
+ sql `
353
+ create table evolu_message_quarantine (
354
+ "ownerId" blob not null,
355
+ "timestamp" blob not null,
356
+ "table" text not null,
357
+ "id" blob not null,
358
+ "column" text not null,
359
+ "value" any,
360
+ primary key ("ownerId", "timestamp", "table", "id", "column")
361
+ )
362
+ strict;
363
+ `,
205
364
  ]) {
206
- const result = deps.sqlite.exec(query);
207
- if (!result.ok)
208
- return result;
365
+ sqlite.exec(query);
209
366
  }
210
- const result = createBaseSqliteStorageTables(deps);
211
- if (!result.ok)
212
- return result;
213
- return ok();
367
+ createBaseSqliteStorageTables({ sqlite });
214
368
  };
215
- /**
216
- * Ensures the quarantine table exists for storing messages with unknown schema.
217
- *
218
- * When a device receives sync messages containing tables or columns that don't
219
- * exist in its current schema (e.g., from a newer app version), those messages
220
- * are stored here instead of being discarded. This enables forward
221
- * compatibility:
222
- *
223
- * 1. Unknown data is preserved and can be applied when the app is updated
224
- * 2. Messages are still propagated to other devices that may understand them
225
- * 3. Partial messages work - known columns go to app tables, unknown to quarantine
226
- *
227
- * The `union all` query in `readDbChange` combines `evolu_history` and this
228
- * table, ensuring all data (known and unknown) is included when syncing to
229
- * other devices.
230
- */
231
- const ensureMessageQuarantineTable = (deps) => {
232
- const result = deps.sqlite.exec(sql `
233
- create table if not exists evolu_message_quarantine (
234
- "ownerId" blob not null,
235
- "timestamp" blob not null,
236
- "table" text not null,
237
- "id" blob not null,
238
- "column" text not null,
239
- "value" any,
240
- primary key ("ownerId", "timestamp", "table", "id", "column")
241
- )
242
- strict;
369
+ const tryApplyQuarantinedMessages = (deps) => {
370
+ const rows = deps.sqlite.exec(sql `
371
+ select "ownerId", "timestamp", "table", "id", "column", "value"
372
+ from evolu_message_quarantine;
243
373
  `);
244
- if (!result.ok)
245
- return result;
246
- return ok();
374
+ for (const row of rows.rows) {
375
+ if (!validateColumnValue(deps)(row.table, row.column, row.value))
376
+ continue;
377
+ applyColumnChange(deps)(row.ownerId, ownerIdBytesToOwnerId(row.ownerId), row.table, row.id, idBytesToId(row.id), row.column, row.value, row.timestamp);
378
+ deps.sqlite.exec(sql `
379
+ delete from evolu_message_quarantine
380
+ where
381
+ "ownerId" = ${row.ownerId}
382
+ and "timestamp" = ${row.timestamp}
383
+ and "table" = ${row.table}
384
+ and "id" = ${row.id}
385
+ and "column" = ${row.column};
386
+ `);
387
+ }
247
388
  };
248
- const handlers = {
249
- getAppOwner: (deps) => () => {
250
- deps.postMessage({
251
- type: "onGetAppOwner",
252
- appOwner: deps.appOwner,
253
- });
254
- },
255
- mutate: (deps) => (message) => {
256
- const mutate = deps.sqlite.transaction(() => {
257
- const syncChanges = [];
258
- for (const change of message.changes) {
259
- const isLocalOnlyChange = change.table.startsWith("_");
260
- if (isLocalOnlyChange) {
261
- const result = applyLocalOnlyChange(deps)(change);
262
- if (!result.ok)
263
- return result;
264
- }
265
- else {
266
- syncChanges.push(change);
389
+ const validateColumnValue = (deps) => (table, column, _value) => {
390
+ const schemaColumns = getProperty(deps.sqliteSchema.tables, table);
391
+ return (schemaColumns != null &&
392
+ (systemColumnsWithoutOwnerId.has(column) || schemaColumns.has(column)));
393
+ };
394
+ const systemColumnsWithoutOwnerId = systemColumns.difference(new Set(["ownerId"]));
395
+ const applyColumnChange = (deps) => (ownerIdBytes, ownerId, table, idBytes, id, column, value, timestampBytes) => {
396
+ deps.sqlite.exec(sql.prepared `
397
+ with
398
+ existingTimestamp as (
399
+ select 1
400
+ from evolu_history
401
+ where
402
+ "ownerId" = ${ownerIdBytes}
403
+ and "table" = ${table}
404
+ and "id" = ${idBytes}
405
+ and "column" = ${column}
406
+ and "timestamp" >= ${timestampBytes}
407
+ limit 1
408
+ )
409
+ insert into ${sql.identifier(table)}
410
+ ("ownerId", "id", ${sql.identifier(column)})
411
+ select ${ownerId}, ${id}, ${value}
412
+ where not exists (select 1 from existingTimestamp)
413
+ on conflict ("ownerId", "id") do update
414
+ set ${sql.identifier(column)} = ${value}
415
+ where not exists (select 1 from existingTimestamp);
416
+ `);
417
+ deps.sqlite.exec(sql.prepared `
418
+ insert into evolu_history
419
+ ("ownerId", "table", "id", "column", "value", "timestamp")
420
+ values
421
+ (
422
+ ${ownerIdBytes},
423
+ ${table},
424
+ ${idBytes},
425
+ ${column},
426
+ ${value},
427
+ ${timestampBytes}
428
+ )
429
+ on conflict do nothing;
430
+ `);
431
+ };
432
+ const createClientStorage = (deps) => ({ onError, }) => {
433
+ let encryptionKey = null;
434
+ let didWriteMessages = false;
435
+ const getEncryptionKey = () => {
436
+ assert(encryptionKey != null, "ClientStorage encryption key must be set");
437
+ return encryptionKey;
438
+ };
439
+ return {
440
+ ...deps.baseSqliteStorage,
441
+ // DEV: ClientStorage was designed when Storage and Sync lived in the
442
+ // same file.
443
+ // This is safe because the worker handles one message at a time. We will
444
+ // refactor it later, we will probably have to change Protocol API.
445
+ setRequestContext: (nextEncryptionKey) => {
446
+ encryptionKey = nextEncryptionKey;
447
+ didWriteMessages = false;
448
+ },
449
+ didWriteMessages: () => didWriteMessages,
450
+ // Not implemented yet.
451
+ validateWriteKey: lazyFalse,
452
+ setWriteKey: lazyVoid,
453
+ writeMessages: (ownerIdBytes, encryptedMessages) => () => {
454
+ // TODO: Add quota checking for collaborative scenarios.
455
+ // When receiving messages from other owners via relay broadcast,
456
+ // check if this owner is within quota before accepting the data.
457
+ // This prevents an owner from exceeding storage limits when receiving
458
+ // data shared by other collaborators.
459
+ const messages = [];
460
+ const currentEncryptionKey = getEncryptionKey();
461
+ for (const message of encryptedMessages) {
462
+ const change = decryptAndDecodeDbChange(message, currentEncryptionKey);
463
+ if (!change.ok) {
464
+ onError(change.error);
465
+ return ok();
267
466
  }
467
+ messages.push({ timestamp: message.timestamp, change: change.value });
268
468
  }
269
- if (isNonEmptyArray(syncChanges)) {
270
- const result = deps.sync.applyChanges(syncChanges);
271
- if (!result.ok)
272
- return result;
469
+ let clockTimestamp = deps.clock.get();
470
+ for (const message of messages) {
471
+ const nextTimestamp = receiveTimestamp(deps)(clockTimestamp, message.timestamp);
472
+ if (!nextTimestamp.ok) {
473
+ onError(nextTimestamp.error);
474
+ return ok();
475
+ }
476
+ clockTimestamp = nextTimestamp.value;
273
477
  }
274
- // Read writes before commit to update UI ASAP
275
- const queryPatches = loadQueries(deps)(message.tabId, message.subscribedQueries);
276
- if (!queryPatches.ok)
277
- return queryPatches;
278
- // Update the tab that performed the mutation.
279
- deps.postMessage({
280
- type: "onQueryPatches",
281
- tabId: message.tabId,
282
- queryPatches: queryPatches.value,
283
- onCompleteIds: message.onCompleteIds,
478
+ assertNonEmptyReadonlyArray(messages);
479
+ return deps.sqlite.transaction(() => {
480
+ applyMessages(deps)(ownerIdBytesToOwnerId(ownerIdBytes), messages);
481
+ deps.clock.save(clockTimestamp);
482
+ didWriteMessages = true;
483
+ return ok();
284
484
  });
285
- // Notify other tabs to refresh their queries.
286
- deps.postMessage({ type: "refreshQueries", tabId: message.tabId });
287
- return ok();
288
- });
289
- if (!mutate.ok) {
290
- deps.postMessage({ type: "onError", error: mutate.error });
291
- return;
292
- }
293
- },
294
- query: (deps) => (message) => {
295
- const queryPatches = loadQueries(deps)(message.tabId, message.queries);
296
- if (!queryPatches.ok) {
297
- deps.postMessage({ type: "onError", error: queryPatches.error });
298
- return;
299
- }
300
- deps.postMessage({
301
- type: "onQueryPatches",
302
- tabId: message.tabId,
303
- queryPatches: queryPatches.value,
304
- onCompleteIds: [],
305
- });
306
- },
307
- reset: (deps) => (message) => {
308
- const result = deps.sqlite.transaction(() => {
309
- const dbSchema = getDbSchema(deps)();
310
- if (!dbSchema.ok)
311
- return dbSchema;
312
- for (const tableName in dbSchema.value.tables) {
313
- /**
314
- * The dropped table is completely removed from the database schema and
315
- * the disk file. The table can not be recovered. All indices and
316
- * triggers associated with the table are also deleted.
317
- * https://sqlite.org/lang_droptable.html
318
- */
319
- const result = deps.sqlite.exec(sql `
320
- drop table ${sql.identifier(tableName)};
485
+ },
486
+ readDbChange: (ownerId, timestamp) => {
487
+ const result = deps.sqlite.exec(sql `
488
+ select "table", "id", "column", "value"
489
+ from evolu_history
490
+ where "ownerId" = ${ownerId} and "timestamp" = ${timestamp}
491
+ union all
492
+ select "table", "id", "column", "value"
493
+ from evolu_message_quarantine
494
+ where "ownerId" = ${ownerId} and "timestamp" = ${timestamp};
321
495
  `);
322
- if (!result.ok)
323
- return result;
324
- }
325
- if (message.restore) {
326
- const result = ensureDbSchema(deps)(message.restore.dbSchema);
327
- if (!result.ok)
328
- return result;
329
- const secret = mnemonicToOwnerSecret(message.restore.mnemonic);
330
- const appOwner = createAppOwner(secret);
331
- const clock = createClock(deps)();
332
- return initializeDb(deps)(appOwner, clock.get());
496
+ const { rows } = result;
497
+ assertNonEmptyReadonlyArray(rows, "Every timestamp must have rows");
498
+ const firstRow = firstInArray(rows);
499
+ const values = createRecord();
500
+ let isInsert = false;
501
+ let isDelete = null;
502
+ for (const r of rows) {
503
+ switch (r.column) {
504
+ case "createdAt":
505
+ isInsert = true;
506
+ break;
507
+ case "updatedAt":
508
+ isInsert = false;
509
+ break;
510
+ case "isDeleted":
511
+ assertType(SqliteBoolean, r.value);
512
+ isDelete = sqliteBooleanToBoolean(r.value);
513
+ break;
514
+ default:
515
+ values[r.column] = r.value;
516
+ }
333
517
  }
334
- return ok();
335
- });
336
- if (!result.ok) {
337
- deps.postMessage({ type: "onError", error: result.error });
338
- return;
518
+ const message = {
519
+ timestamp: timestampBytesToTimestamp(timestamp),
520
+ change: DbChange.orThrow({
521
+ table: firstRow.table,
522
+ id: idBytesToId(firstRow.id),
523
+ values,
524
+ isInsert,
525
+ isDelete,
526
+ }),
527
+ };
528
+ return encodeAndEncryptDbChange(deps)(message, getEncryptionKey());
529
+ },
530
+ };
531
+ };
532
+ const handleMutation = (deps) => (message) => deps.sqlite.transaction(() => {
533
+ const messagesByOwnerId = new Map();
534
+ let clockTimestamp = deps.clock.get();
535
+ let clockChanged = false;
536
+ for (const change of message.changes) {
537
+ if (change.table.startsWith("_")) {
538
+ applyLocalOnlyChange(deps)(change);
539
+ continue;
339
540
  }
340
- deps.postMessage({
341
- type: "onReset",
342
- onCompleteId: message.onCompleteId,
343
- reload: message.reload,
344
- });
345
- },
346
- ensureDbSchema: (deps) => (message) => {
347
- const result = deps.sqlite.transaction(() => ensureDbSchema(deps)(message.dbSchema));
348
- if (!result.ok) {
349
- deps.postMessage({ type: "onError", error: result.error });
350
- return;
541
+ const nextTimestamp = sendTimestamp(deps)(clockTimestamp);
542
+ if (!nextTimestamp.ok)
543
+ return nextTimestamp;
544
+ clockTimestamp = nextTimestamp.value;
545
+ clockChanged = true;
546
+ const { ownerId, ...dbChange } = change;
547
+ const message = {
548
+ timestamp: clockTimestamp,
549
+ change: dbChange,
550
+ };
551
+ const messages = messagesByOwnerId.get(ownerId);
552
+ if (messages)
553
+ messages.push(message);
554
+ else
555
+ messagesByOwnerId.set(ownerId, [message]);
556
+ }
557
+ for (const [ownerId, messages] of messagesByOwnerId) {
558
+ applyMessages(deps)(ownerId, messages);
559
+ }
560
+ if (clockChanged)
561
+ deps.clock.save(clockTimestamp);
562
+ return ok({
563
+ type: "Mutate",
564
+ messagesByOwnerId,
565
+ rowsByQuery: loadQueries(deps)(message.subscribedQueries),
566
+ });
567
+ });
568
+ const applyLocalOnlyChange = (deps) => (change) => {
569
+ if (change.isDelete) {
570
+ deps.sqlite.exec(sql `
571
+ delete from ${sql.identifier(change.table)}
572
+ where "ownerId" = ${change.ownerId} and "id" = ${change.id};
573
+ `);
574
+ }
575
+ else {
576
+ const ownerId = change.ownerId;
577
+ const columns = dbChangeToColumns(change, deps.time.now());
578
+ for (const [column, value] of columns) {
579
+ deps.sqlite.exec(sql.prepared `
580
+ insert into ${sql.identifier(change.table)}
581
+ ("ownerId", "id", ${sql.identifier(column)})
582
+ values (${ownerId}, ${change.id}, ${value})
583
+ on conflict ("ownerId", "id") do update
584
+ set ${sql.identifier(column)} = ${value};
585
+ `);
351
586
  }
352
- },
353
- export: (deps) => (message) => {
354
- const file = deps.sqlite.export();
355
- if (!file.ok) {
356
- deps.postMessage({ type: "onError", error: file.error });
357
- return;
587
+ }
588
+ };
589
+ const applyMessages = (deps) => (ownerId, messages) => {
590
+ const ownerIdBytes = ownerIdToOwnerIdBytes(ownerId);
591
+ const usage = readOwnerUsageOrDefault(deps)(ownerIdBytes, timestampToTimestampBytes(firstInArray(messages).timestamp));
592
+ let { firstTimestamp, lastTimestamp } = usage;
593
+ for (const { timestamp, change } of messages) {
594
+ const columns = dbChangeToColumns(change, timestamp.millis);
595
+ const idBytes = idToIdBytes(change.id);
596
+ const timestampBytes = timestampToTimestampBytes(timestamp);
597
+ for (const [column, value] of columns) {
598
+ if (validateColumnValue(deps)(change.table, column, value)) {
599
+ applyColumnChange(deps)(ownerIdBytes, ownerId, change.table, idBytes, change.id, column, value, timestampBytes);
600
+ }
601
+ else {
602
+ deps.sqlite.exec(sql.prepared `
603
+ insert into evolu_message_quarantine
604
+ ("ownerId", "timestamp", "table", "id", "column", "value")
605
+ values
606
+ (
607
+ ${ownerIdBytes},
608
+ ${timestampBytes},
609
+ ${change.table},
610
+ ${idBytes},
611
+ ${column},
612
+ ${value}
613
+ )
614
+ on conflict do nothing;
615
+ `);
616
+ }
358
617
  }
359
- deps.postMessage({
360
- type: "onExport",
361
- onCompleteId: message.onCompleteId,
362
- file: file.value,
363
- });
364
- },
365
- useOwner: (deps) => (message) => {
366
- deps.sync.useOwner(message.use, message.owner);
367
- },
618
+ let strategy;
619
+ [strategy, firstTimestamp, lastTimestamp] = getTimestampInsertStrategy(timestampBytes, firstTimestamp, lastTimestamp);
620
+ deps.baseSqliteStorage.insertTimestamp(ownerIdBytes, timestampBytes, strategy);
621
+ }
622
+ /**
623
+ * TODO: Implement proper storedBytes tracking for client using received and
624
+ * sent encrypted message sizes.
625
+ */
626
+ updateOwnerUsage(deps)(ownerIdBytes, onePositiveInt, // Placeholder until proper tracking implemented
627
+ firstTimestamp, lastTimestamp);
628
+ };
629
+ const dbChangeToColumns = (change, now) => {
630
+ let values = objectToEntries(change.values);
631
+ // SystemColumns are not encoded in change.values.
632
+ values = appendToArray(values, [
633
+ change.isInsert ? "createdAt" : "updatedAt",
634
+ millisToDateIso(now),
635
+ ]);
636
+ if (change.isDelete != null) {
637
+ values = appendToArray(values, [
638
+ "isDeleted",
639
+ booleanToSqliteBoolean(change.isDelete),
640
+ ]);
641
+ }
642
+ return values;
643
+ };
644
+ const loadQueries = (deps) => (queries) => {
645
+ const rowsByQuery = new Map();
646
+ for (const query of queries) {
647
+ const { rows } = deps.sqlite.exec(sqliteQueryStringToSqliteQuery(query));
648
+ rowsByQuery.set(query, rows);
649
+ }
650
+ return rowsByQuery;
368
651
  };
652
+ // reset: (deps) => (message) => {
653
+ // const result = deps.sqlite.transaction(() => {
654
+ // const sqliteSchema = getSqliteSchema(deps)();
655
+ // if (!sqliteSchema.ok) return sqliteSchema;
656
+ // for (const tableName in sqliteSchema.value.tables) {
657
+ // /**
658
+ // * The dropped table is completely removed from the database schema and
659
+ // * the disk file. The table can not be recovered. All indices and
660
+ // * triggers associated with the table are also deleted.
661
+ // * https://sqlite.org/lang_droptable.html
662
+ // */
663
+ // const result = deps.sqlite.exec(sql`
664
+ // drop table ${sql.identifier(tableName)};
665
+ // `);
666
+ // if (!result.ok) return result;
667
+ // }
668
+ // if (message.restore) {
669
+ // const result = ensureSqliteSchema(deps)(message.restore.sqliteSchema);
670
+ // if (!result.ok) return result;
671
+ // const secret = mnemonicToOwnerSecret(message.restore.mnemonic);
672
+ // const appOwner = createAppOwner(secret);
673
+ // const clock = createClock(deps)();
674
+ // return initializeDb(deps)(appOwner, clock.get());
675
+ // }
676
+ // return ok();
677
+ // });
678
+ // if (!result.ok) {
679
+ // deps.postMessage({ type: "onError", error: result.error });
680
+ // return;
681
+ // }
682
+ // deps.postMessage({
683
+ // type: "onReset",
684
+ // onCompleteId: message.onCompleteId,
685
+ // reload: message.reload,
686
+ // });
687
+ // },
688
+ // ensureSqliteSchema: (deps) => (message) => {
689
+ // const result = deps.sqlite.transaction(() =>
690
+ // ensureSqliteSchema(deps)(message.sqliteSchema),
691
+ // );
692
+ // if (!result.ok) {
693
+ // deps.postMessage({ type: "onError", error: result.error });
694
+ // return;
695
+ // }
696
+ // },