@evolu/common 7.4.0 → 8.0.0-next.0

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 (251) 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 +160 -0
  42. package/dist/src/Lookup.d.ts.map +1 -0
  43. package/dist/src/Lookup.js +192 -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 +27 -0
  63. package/dist/src/Polyfills.d.ts.map +1 -0
  64. package/dist/src/Polyfills.js +299 -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 +2263 -423
  105. package/dist/src/Task.d.ts.map +1 -1
  106. package/dist/src/Task.js +1488 -348
  107. package/dist/src/Test.d.ts +122 -0
  108. package/dist/src/Test.d.ts.map +1 -0
  109. package/dist/src/Test.js +66 -0
  110. package/dist/src/Time.d.ts +201 -48
  111. package/dist/src/Time.d.ts.map +1 -1
  112. package/dist/src/Time.js +190 -86
  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 +582 -209
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +362 -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 +113 -58
  123. package/dist/src/WebSocket.d.ts.map +1 -1
  124. package/dist/src/WebSocket.js +210 -118
  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 +622 -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 +18 -38
  153. package/dist/src/local-first/Relay.d.ts.map +1 -1
  154. package/dist/src/local-first/Relay.js +59 -171
  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 +50 -53
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +121 -173
  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 +36 -26
  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 +415 -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 +465 -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 +3685 -740
  206. package/src/Test.ts +162 -0
  207. package/src/Time.ts +348 -145
  208. package/src/Tracer.ts +54 -0
  209. package/src/Type.ts +1064 -630
  210. package/src/Types.ts +202 -7
  211. package/src/WebSocket.ts +311 -178
  212. package/src/Worker.ts +527 -147
  213. package/src/index.ts +74 -3
  214. package/src/local-first/Db.ts +871 -597
  215. package/src/local-first/Error.ts +17 -0
  216. package/src/local-first/Evolu.ts +884 -823
  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 +440 -303
  220. package/src/local-first/Query.ts +243 -194
  221. package/src/local-first/Relay.ts +91 -252
  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 +209 -282
  225. package/src/local-first/Timestamp.ts +38 -45
  226. package/src/local-first/index.ts +2 -16
  227. package/LICENSE +0 -21
  228. package/dist/src/Instances.d.ts +0 -34
  229. package/dist/src/Instances.d.ts.map +0 -1
  230. package/dist/src/Instances.js +0 -44
  231. package/dist/src/Resources.d.ts +0 -118
  232. package/dist/src/Resources.d.ts.map +0 -1
  233. package/dist/src/Resources.js +0 -197
  234. package/dist/src/local-first/Platform.d.ts +0 -25
  235. package/dist/src/local-first/Platform.d.ts.map +0 -1
  236. package/dist/src/local-first/Platform.js +0 -1
  237. package/dist/src/local-first/Public.d.ts +0 -22
  238. package/dist/src/local-first/Public.d.ts.map +0 -1
  239. package/dist/src/local-first/Public.js +0 -15
  240. package/dist/src/local-first/PublicKysely.d.ts +0 -148
  241. package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
  242. package/dist/src/local-first/PublicKysely.js +0 -184
  243. package/dist/src/local-first/Sync.d.ts +0 -112
  244. package/dist/src/local-first/Sync.d.ts.map +0 -1
  245. package/dist/src/local-first/Sync.js +0 -529
  246. package/src/Instances.ts +0 -90
  247. package/src/Resources.ts +0 -367
  248. package/src/local-first/Platform.ts +0 -27
  249. package/src/local-first/Public.ts +0 -42
  250. package/src/local-first/PublicKysely.ts +0 -239
  251. package/src/local-first/Sync.ts +0 -960
@@ -1,135 +1,281 @@
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 } 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, getOwnerUsage, getTimestampInsertStrategy, 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 repeats sends until it gets a response, so handling here
131
+ * must be idempotent and ignore already processed IDs.
132
+ *
133
+ * TODO: Bound memory growth by evicting old IDs.
134
+ */
135
+ const processedRequestIds = new Set();
136
+ port.onMessage = ({ callbackId, request }) => {
137
+ if (processedRequestIds.has(callbackId))
138
+ return;
139
+ processedRequestIds.add(callbackId);
140
+ const postQueuedResponse = (response) => {
141
+ port.postMessage({ type: "OnQueuedResponse", callbackId, response }, response.type === "ForEvolu" && response.message.type === "Export"
142
+ ? [response.message.file.buffer]
143
+ : undefined);
144
+ };
145
+ switch (request.type) {
146
+ case "ForEvolu": {
147
+ switch (request.message.type) {
148
+ case "Mutate": {
149
+ const result = handleMutation({ ...dbDeps, clock })(request.message);
150
+ if (!result.ok) {
151
+ port.postMessage({ type: "OnError", error: result.error });
152
+ }
153
+ else {
154
+ postQueuedResponse({
155
+ type: "ForEvolu",
156
+ evoluPortId: request.evoluPortId,
157
+ message: result.value,
158
+ });
159
+ }
160
+ break;
161
+ }
162
+ case "Query":
163
+ postQueuedResponse({
164
+ type: "ForEvolu",
165
+ evoluPortId: request.evoluPortId,
166
+ message: {
167
+ type: "Query",
168
+ rowsByQuery: loadQueries(dbDeps)(request.message.queries),
169
+ },
170
+ });
171
+ break;
172
+ case "Export":
173
+ postQueuedResponse({
174
+ type: "ForEvolu",
175
+ evoluPortId: request.evoluPortId,
176
+ message: {
177
+ type: "Export",
178
+ file: dbDeps.sqlite.export(),
179
+ },
180
+ });
181
+ break;
182
+ default:
183
+ exhaustiveCheck(request.message);
184
+ }
185
+ break;
186
+ }
187
+ case "ForSharedWorker": {
188
+ switch (request.message.type) {
189
+ case "CreateSyncMessages": {
190
+ const protocolMessagesByOwnerId = new Map();
191
+ for (const owner of request.message.owners) {
192
+ storage.setOwnerState(owner.encryptionKey);
193
+ const protocolMessage = createProtocolMessageForSync({
194
+ storage,
195
+ console,
196
+ })(owner.id, SubscriptionFlags.Subscribe);
197
+ if (protocolMessage) {
198
+ protocolMessagesByOwnerId.set(owner.id, protocolMessage);
199
+ }
200
+ }
201
+ postQueuedResponse({
202
+ type: "ForSharedWorker",
203
+ message: {
204
+ type: "CreateSyncMessages",
205
+ protocolMessagesByOwnerId,
206
+ },
207
+ });
208
+ break;
209
+ }
210
+ case "ApplySyncMessage": {
211
+ const { owner, inputMessage } = request.message;
212
+ runWithStorage(async (run) => {
213
+ storage.setOwnerState(owner.encryptionKey);
214
+ const result = await run(applyProtocolMessageAsClient(inputMessage, {
215
+ writeKey: owner.writeKey,
216
+ }));
217
+ const didWriteMessages = storage.didWriteMessages();
218
+ postQueuedResponse({
219
+ type: "ForSharedWorker",
220
+ message: {
221
+ type: "ApplySyncMessage",
222
+ ownerId: owner.id,
223
+ didWriteMessages,
224
+ result,
225
+ },
226
+ });
227
+ return ok();
228
+ });
229
+ break;
230
+ }
231
+ default:
232
+ exhaustiveCheck(request.message);
233
+ }
234
+ break;
235
+ }
236
+ default:
237
+ exhaustiveCheck(request);
238
+ }
239
+ };
240
+ return ok();
241
+ }
242
+ catch (e_1) {
243
+ env_1.error = e_1;
244
+ env_1.hasError = true;
245
+ }
246
+ finally {
247
+ const result_1 = __disposeResources(env_1);
248
+ if (result_1)
249
+ await result_1;
250
+ }
129
251
  };
130
- const initializeDb = (deps) => (initialAppOwner, initialClock) => {
252
+ const createClock = (deps) => (dbIsInitialized) => {
253
+ let currentTimestamp;
254
+ if (dbIsInitialized) {
255
+ const { rows } = deps.sqlite.exec(sql `
256
+ select clock
257
+ from evolu_config
258
+ limit 1;
259
+ `);
260
+ assertNonEmptyReadonlyArray(rows);
261
+ currentTimestamp = timestampBytesToTimestamp(firstInArray(rows).clock);
262
+ }
263
+ else {
264
+ currentTimestamp = createInitialTimestamp(deps);
265
+ }
266
+ return {
267
+ get: () => currentTimestamp,
268
+ save: (timestamp) => {
269
+ currentTimestamp = timestamp;
270
+ deps.sqlite.exec(sql.prepared `
271
+ update evolu_config
272
+ set "clock" = ${timestampToTimestampBytes(timestamp)};
273
+ `);
274
+ },
275
+ };
276
+ };
277
+ const initializeDb = ({ sqlite }) => (initialClock) => {
131
278
  for (const query of [
132
- // Never change structure to ensure all versions can read it.
133
279
  sql `
134
280
  create table evolu_version (
135
281
  "protocolVersion" integer not null
@@ -142,31 +288,13 @@ const initializeDb = (deps) => (initialAppOwner, initialClock) => {
142
288
  `,
143
289
  sql `
144
290
  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
291
+ "clock" blob not null
150
292
  )
151
293
  strict;
152
294
  `,
153
295
  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
- );
296
+ insert into evolu_config ("clock")
297
+ values (${timestampToTimestampBytes(initialClock)});
170
298
  `,
171
299
  /**
172
300
  * The History table stores all values per ownerId, timestamp, table, id,
@@ -202,167 +330,368 @@ const initializeDb = (deps) => (initialAppOwner, initialClock) => {
202
330
  "timestamp" desc
203
331
  );
204
332
  `,
333
+ /**
334
+ * Stores messages with unknown schema in a quarantine table.
335
+ *
336
+ * When a device receives sync messages containing tables or columns that
337
+ * don't exist in its current schema (e.g., from a newer app version),
338
+ * those messages are stored here instead of being discarded. This enables
339
+ * forward compatibility:
340
+ *
341
+ * 1. Unknown data is preserved and can be applied when the app is updated
342
+ * 2. Messages are still propagated to other devices that may understand them
343
+ * 3. Partial messages work - known columns go to app tables, unknown to
344
+ * quarantine
345
+ *
346
+ * The `union all` query in `readDbChange` combines `evolu_history` and
347
+ * this table, ensuring all data (known and unknown) is included when
348
+ * syncing to other devices.
349
+ */
350
+ sql `
351
+ create table evolu_message_quarantine (
352
+ "ownerId" blob not null,
353
+ "timestamp" blob not null,
354
+ "table" text not null,
355
+ "id" blob not null,
356
+ "column" text not null,
357
+ "value" any,
358
+ primary key ("ownerId", "timestamp", "table", "id", "column")
359
+ )
360
+ strict;
361
+ `,
205
362
  ]) {
206
- const result = deps.sqlite.exec(query);
207
- if (!result.ok)
208
- return result;
363
+ sqlite.exec(query);
209
364
  }
210
- const result = createBaseSqliteStorageTables(deps);
211
- if (!result.ok)
212
- return result;
213
- return ok();
365
+ createBaseSqliteStorageTables({ sqlite });
214
366
  };
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;
367
+ const tryApplyQuarantinedMessages = (deps) => {
368
+ const rows = deps.sqlite.exec(sql `
369
+ select "ownerId", "timestamp", "table", "id", "column", "value"
370
+ from evolu_message_quarantine;
243
371
  `);
244
- if (!result.ok)
245
- return result;
246
- return ok();
372
+ for (const row of rows.rows) {
373
+ if (!validateColumnValue(deps)(row.table, row.column, row.value))
374
+ continue;
375
+ applyColumnChange(deps)(row.ownerId, ownerIdBytesToOwnerId(row.ownerId), row.table, row.id, idBytesToId(row.id), row.column, row.value, row.timestamp);
376
+ deps.sqlite.exec(sql `
377
+ delete from evolu_message_quarantine
378
+ where
379
+ "ownerId" = ${row.ownerId}
380
+ and "timestamp" = ${row.timestamp}
381
+ and "table" = ${row.table}
382
+ and "id" = ${row.id}
383
+ and "column" = ${row.column};
384
+ `);
385
+ }
247
386
  };
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);
387
+ const validateColumnValue = (deps) => (table, column, _value) => {
388
+ const schemaColumns = getProperty(deps.sqliteSchema.tables, table);
389
+ return (schemaColumns != null &&
390
+ (systemColumnsWithoutOwnerId.has(column) || schemaColumns.has(column)));
391
+ };
392
+ const systemColumnsWithoutOwnerId = systemColumns.difference(new Set(["ownerId"]));
393
+ const applyColumnChange = (deps) => (ownerIdBytes, ownerId, table, idBytes, id, column, value, timestampBytes) => {
394
+ deps.sqlite.exec(sql.prepared `
395
+ with
396
+ existingTimestamp as (
397
+ select 1
398
+ from evolu_history
399
+ where
400
+ "ownerId" = ${ownerIdBytes}
401
+ and "table" = ${table}
402
+ and "id" = ${idBytes}
403
+ and "column" = ${column}
404
+ and "timestamp" >= ${timestampBytes}
405
+ limit 1
406
+ )
407
+ insert into ${sql.identifier(table)}
408
+ ("ownerId", "id", ${sql.identifier(column)})
409
+ select ${ownerId}, ${id}, ${value}
410
+ where not exists (select 1 from existingTimestamp)
411
+ on conflict ("ownerId", "id") do update
412
+ set ${sql.identifier(column)} = ${value}
413
+ where not exists (select 1 from existingTimestamp);
414
+ `);
415
+ deps.sqlite.exec(sql.prepared `
416
+ insert into evolu_history
417
+ ("ownerId", "table", "id", "column", "value", "timestamp")
418
+ values
419
+ (
420
+ ${ownerIdBytes},
421
+ ${table},
422
+ ${idBytes},
423
+ ${column},
424
+ ${value},
425
+ ${timestampBytes}
426
+ )
427
+ on conflict do nothing;
428
+ `);
429
+ };
430
+ const createClientStorage = (deps) => ({ onError, }) => {
431
+ let encryptionKey = null;
432
+ let didWriteMessages = false;
433
+ const getEncryptionKey = () => {
434
+ assert(encryptionKey != null, "ClientStorage encryption key must be set");
435
+ return encryptionKey;
436
+ };
437
+ return {
438
+ ...deps.baseSqliteStorage,
439
+ // DEV: ClientStorage was designed when Storage and Sync lived in the
440
+ // same file.
441
+ // This is safe because the worker handles one message at a time. We will
442
+ // refactor it later, we will probably have to change Protocol API.
443
+ setOwnerState: (nextEncryptionKey) => {
444
+ encryptionKey = nextEncryptionKey;
445
+ didWriteMessages = false;
446
+ },
447
+ didWriteMessages: () => didWriteMessages,
448
+ // Not implemented yet.
449
+ validateWriteKey: lazyFalse,
450
+ setWriteKey: lazyVoid,
451
+ writeMessages: (ownerIdBytes, encryptedMessages) => () => {
452
+ // TODO: Add quota checking for collaborative scenarios.
453
+ // When receiving messages from other owners via relay broadcast,
454
+ // check if this owner is within quota before accepting the data.
455
+ // This prevents an owner from exceeding storage limits when receiving
456
+ // data shared by other collaborators.
457
+ const messages = [];
458
+ const currentEncryptionKey = getEncryptionKey();
459
+ for (const message of encryptedMessages) {
460
+ const change = decryptAndDecodeDbChange(message, currentEncryptionKey);
461
+ if (!change.ok) {
462
+ onError(change.error);
463
+ return ok();
267
464
  }
465
+ messages.push({ timestamp: message.timestamp, change: change.value });
268
466
  }
269
- if (isNonEmptyArray(syncChanges)) {
270
- const result = deps.sync.applyChanges(syncChanges);
271
- if (!result.ok)
272
- return result;
467
+ let clockTimestamp = deps.clock.get();
468
+ for (const message of messages) {
469
+ const nextTimestamp = receiveTimestamp(deps)(clockTimestamp, message.timestamp);
470
+ if (!nextTimestamp.ok) {
471
+ onError(nextTimestamp.error);
472
+ return ok();
473
+ }
474
+ clockTimestamp = nextTimestamp.value;
273
475
  }
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,
476
+ assertNonEmptyReadonlyArray(messages);
477
+ return deps.sqlite.transaction(() => {
478
+ applyMessages(deps)(ownerIdBytesToOwnerId(ownerIdBytes), messages);
479
+ deps.clock.save(clockTimestamp);
480
+ didWriteMessages = true;
481
+ return ok();
284
482
  });
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)};
483
+ },
484
+ readDbChange: (ownerId, timestamp) => {
485
+ const result = deps.sqlite.exec(sql `
486
+ select "table", "id", "column", "value"
487
+ from evolu_history
488
+ where "ownerId" = ${ownerId} and "timestamp" = ${timestamp}
489
+ union all
490
+ select "table", "id", "column", "value"
491
+ from evolu_message_quarantine
492
+ where "ownerId" = ${ownerId} and "timestamp" = ${timestamp};
321
493
  `);
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());
494
+ const { rows } = result;
495
+ assertNonEmptyReadonlyArray(rows, "Every timestamp must have rows");
496
+ const firstRow = firstInArray(rows);
497
+ const values = createRecord();
498
+ let isInsert = false;
499
+ let isDelete = null;
500
+ for (const r of rows) {
501
+ switch (r.column) {
502
+ case "createdAt":
503
+ isInsert = true;
504
+ break;
505
+ case "updatedAt":
506
+ isInsert = false;
507
+ break;
508
+ case "isDeleted":
509
+ if (SqliteBoolean.is(r.value)) {
510
+ isDelete = sqliteBooleanToBoolean(r.value);
511
+ }
512
+ break;
513
+ default:
514
+ values[r.column] = r.value;
515
+ }
333
516
  }
334
- return ok();
335
- });
336
- if (!result.ok) {
337
- deps.postMessage({ type: "onError", error: result.error });
338
- return;
517
+ const message = {
518
+ timestamp: timestampBytesToTimestamp(timestamp),
519
+ change: DbChange.orThrow({
520
+ table: firstRow.table,
521
+ id: idBytesToId(firstRow.id),
522
+ values,
523
+ isInsert,
524
+ isDelete,
525
+ }),
526
+ };
527
+ return encodeAndEncryptDbChange(deps)(message, getEncryptionKey());
528
+ },
529
+ };
530
+ };
531
+ const handleMutation = (deps) => (message) => deps.sqlite.transaction(() => {
532
+ const messagesByOwnerId = new Map();
533
+ let clockTimestamp = deps.clock.get();
534
+ let clockChanged = false;
535
+ for (const change of message.changes) {
536
+ if (change.table.startsWith("_")) {
537
+ applyLocalOnlyChange(deps)(change);
538
+ continue;
339
539
  }
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;
540
+ const nextTimestamp = sendTimestamp(deps)(clockTimestamp);
541
+ if (!nextTimestamp.ok)
542
+ return nextTimestamp;
543
+ clockTimestamp = nextTimestamp.value;
544
+ clockChanged = true;
545
+ const { ownerId, ...dbChange } = change;
546
+ const message = {
547
+ timestamp: clockTimestamp,
548
+ change: dbChange,
549
+ };
550
+ const messages = messagesByOwnerId.get(ownerId);
551
+ if (messages)
552
+ messages.push(message);
553
+ else
554
+ messagesByOwnerId.set(ownerId, [message]);
555
+ }
556
+ for (const [ownerId, messages] of messagesByOwnerId) {
557
+ applyMessages(deps)(ownerId, messages);
558
+ }
559
+ if (clockChanged)
560
+ deps.clock.save(clockTimestamp);
561
+ return ok({
562
+ type: "Mutate",
563
+ messagesByOwnerId,
564
+ rowsByQuery: loadQueries(deps)(message.subscribedQueries),
565
+ });
566
+ });
567
+ const applyLocalOnlyChange = (deps) => (change) => {
568
+ if (change.isDelete) {
569
+ deps.sqlite.exec(sql `
570
+ delete from ${sql.identifier(change.table)}
571
+ where id = ${change.id};
572
+ `);
573
+ }
574
+ else {
575
+ const ownerId = change.ownerId;
576
+ const columns = dbChangeToColumns(change, deps.time.now());
577
+ for (const [column, value] of columns) {
578
+ deps.sqlite.exec(sql.prepared `
579
+ insert into ${sql.identifier(change.table)}
580
+ ("ownerId", "id", ${sql.identifier(column)})
581
+ values (${ownerId}, ${change.id}, ${value})
582
+ on conflict ("ownerId", "id") do update
583
+ set ${sql.identifier(column)} = ${value};
584
+ `);
351
585
  }
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;
586
+ }
587
+ };
588
+ const applyMessages = (deps) => (ownerId, messages) => {
589
+ const ownerIdBytes = ownerIdToOwnerIdBytes(ownerId);
590
+ const usage = getOwnerUsage(deps)(ownerIdBytes, timestampToTimestampBytes(firstInArray(messages).timestamp));
591
+ if (!usage.ok)
592
+ return;
593
+ let { firstTimestamp, lastTimestamp } = usage.value;
594
+ for (const { timestamp, change } of messages) {
595
+ const columns = dbChangeToColumns(change, timestamp.millis);
596
+ const idBytes = idToIdBytes(change.id);
597
+ const timestampBytes = timestampToTimestampBytes(timestamp);
598
+ for (const [column, value] of columns) {
599
+ if (validateColumnValue(deps)(change.table, column, value)) {
600
+ applyColumnChange(deps)(ownerIdBytes, ownerId, change.table, idBytes, change.id, column, value, timestampBytes);
601
+ }
602
+ else {
603
+ deps.sqlite.exec(sql.prepared `
604
+ insert into evolu_message_quarantine
605
+ ("ownerId", "timestamp", "table", "id", "column", "value")
606
+ values
607
+ (
608
+ ${ownerIdBytes},
609
+ ${timestampBytes},
610
+ ${change.table},
611
+ ${idBytes},
612
+ ${column},
613
+ ${value}
614
+ )
615
+ on conflict do nothing;
616
+ `);
617
+ }
358
618
  }
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
- },
619
+ let strategy;
620
+ [strategy, firstTimestamp, lastTimestamp] = getTimestampInsertStrategy(timestampBytes, firstTimestamp, lastTimestamp);
621
+ deps.baseSqliteStorage.insertTimestamp(ownerIdBytes, timestampBytes, strategy);
622
+ }
623
+ /**
624
+ * TODO: Implement proper storedBytes tracking for client using received and
625
+ * sent encrypted message sizes.
626
+ */
627
+ updateOwnerUsage(deps)(ownerIdBytes, onePositiveInt, // Placeholder until proper tracking implemented
628
+ firstTimestamp, lastTimestamp);
629
+ };
630
+ const dbChangeToColumns = (change, now) => {
631
+ let values = objectToEntries(change.values);
632
+ // SystemColumns are not encoded in change.values.
633
+ values = appendToArray(values, [
634
+ change.isInsert ? "createdAt" : "updatedAt",
635
+ millisToDateIso(now),
636
+ ]);
637
+ if (change.isDelete != null) {
638
+ values = appendToArray(values, [
639
+ "isDeleted",
640
+ booleanToSqliteBoolean(change.isDelete),
641
+ ]);
642
+ }
643
+ return values;
644
+ };
645
+ const loadQueries = (deps) => (queries) => {
646
+ const rowsByQuery = new Map();
647
+ for (const query of queries) {
648
+ const { rows } = deps.sqlite.exec(sqliteQueryStringToSqliteQuery(query));
649
+ rowsByQuery.set(query, rows);
650
+ }
651
+ return rowsByQuery;
368
652
  };
653
+ // reset: (deps) => (message) => {
654
+ // const result = deps.sqlite.transaction(() => {
655
+ // const sqliteSchema = getSqliteSchema(deps)();
656
+ // if (!sqliteSchema.ok) return sqliteSchema;
657
+ // for (const tableName in sqliteSchema.value.tables) {
658
+ // /**
659
+ // * The dropped table is completely removed from the database schema and
660
+ // * the disk file. The table can not be recovered. All indices and
661
+ // * triggers associated with the table are also deleted.
662
+ // * https://sqlite.org/lang_droptable.html
663
+ // */
664
+ // const result = deps.sqlite.exec(sql`
665
+ // drop table ${sql.identifier(tableName)};
666
+ // `);
667
+ // if (!result.ok) return result;
668
+ // }
669
+ // if (message.restore) {
670
+ // const result = ensureSqliteSchema(deps)(message.restore.sqliteSchema);
671
+ // if (!result.ok) return result;
672
+ // const secret = mnemonicToOwnerSecret(message.restore.mnemonic);
673
+ // const appOwner = createAppOwner(secret);
674
+ // const clock = createClock(deps)();
675
+ // return initializeDb(deps)(appOwner, clock.get());
676
+ // }
677
+ // return ok();
678
+ // });
679
+ // if (!result.ok) {
680
+ // deps.postMessage({ type: "onError", error: result.error });
681
+ // return;
682
+ // }
683
+ // deps.postMessage({
684
+ // type: "onReset",
685
+ // onCompleteId: message.onCompleteId,
686
+ // reload: message.reload,
687
+ // });
688
+ // },
689
+ // ensureSqliteSchema: (deps) => (message) => {
690
+ // const result = deps.sqlite.transaction(() =>
691
+ // ensureSqliteSchema(deps)(message.sqliteSchema),
692
+ // );
693
+ // if (!result.ok) {
694
+ // deps.postMessage({ type: "onError", error: result.error });
695
+ // return;
696
+ // }
697
+ // },