@evolu/common 5.4.7 → 6.0.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 (242) hide show
  1. package/README.md +30 -34
  2. package/dist/src/Array.d.ts +17 -0
  3. package/dist/src/Array.d.ts.map +1 -0
  4. package/dist/src/Array.js +12 -0
  5. package/dist/src/Assert.d.ts +68 -0
  6. package/dist/src/Assert.d.ts.map +1 -0
  7. package/dist/src/Assert.js +77 -0
  8. package/dist/src/BigInt.d.ts +20 -0
  9. package/dist/src/BigInt.d.ts.map +1 -0
  10. package/dist/src/BigInt.js +18 -0
  11. package/dist/src/Buffer.d.ts +92 -0
  12. package/dist/src/Buffer.d.ts.map +1 -0
  13. package/dist/src/Buffer.js +62 -0
  14. package/dist/src/Callbacks.d.ts +20 -0
  15. package/dist/src/Callbacks.d.ts.map +1 -0
  16. package/dist/src/Callbacks.js +18 -0
  17. package/dist/src/Console.d.ts +78 -0
  18. package/dist/src/Console.d.ts.map +1 -0
  19. package/dist/src/Console.js +103 -0
  20. package/dist/src/Crypto.d.ts +72 -39
  21. package/dist/src/Crypto.d.ts.map +1 -1
  22. package/dist/src/Crypto.js +89 -54
  23. package/dist/src/Eq.d.ts +97 -0
  24. package/dist/src/Eq.d.ts.map +1 -0
  25. package/dist/src/Eq.js +167 -0
  26. package/dist/src/Error.d.ts +14 -10
  27. package/dist/src/Error.d.ts.map +1 -1
  28. package/dist/src/Error.js +43 -11
  29. package/dist/src/Evolu/Config.d.ts +69 -0
  30. package/dist/src/Evolu/Config.d.ts.map +1 -0
  31. package/dist/src/Evolu/Config.js +9 -0
  32. package/dist/src/Evolu/Db.d.ts +126 -0
  33. package/dist/src/Evolu/Db.d.ts.map +1 -0
  34. package/dist/src/Evolu/Db.js +774 -0
  35. package/dist/src/Evolu/Diff.d.ts +43 -0
  36. package/dist/src/Evolu/Diff.d.ts.map +1 -0
  37. package/dist/src/Evolu/Diff.js +95 -0
  38. package/dist/src/Evolu/Evolu.d.ts +334 -0
  39. package/dist/src/Evolu/Evolu.d.ts.map +1 -0
  40. package/dist/src/Evolu/Evolu.js +434 -0
  41. package/dist/src/Evolu/Internal.d.ts +26 -0
  42. package/dist/src/Evolu/Internal.d.ts.map +1 -0
  43. package/dist/src/Evolu/Internal.js +25 -0
  44. package/dist/src/Evolu/Kysely.d.ts +6 -0
  45. package/dist/src/Evolu/Kysely.d.ts.map +1 -0
  46. package/dist/src/Evolu/Kysely.js +21 -0
  47. package/dist/src/Evolu/Owner.d.ts +155 -0
  48. package/dist/src/Evolu/Owner.d.ts.map +1 -0
  49. package/dist/src/Evolu/Owner.js +126 -0
  50. package/dist/src/Evolu/Platform.d.ts +23 -0
  51. package/dist/src/Evolu/Platform.d.ts.map +1 -0
  52. package/dist/src/Evolu/Platform.js +1 -0
  53. package/dist/src/Evolu/Protocol.d.ts +401 -0
  54. package/dist/src/Evolu/Protocol.d.ts.map +1 -0
  55. package/dist/src/Evolu/Protocol.js +1151 -0
  56. package/dist/src/Evolu/Public.d.ts +18 -0
  57. package/dist/src/Evolu/Public.d.ts.map +1 -0
  58. package/dist/src/Evolu/Public.js +11 -0
  59. package/dist/src/Evolu/PublicKysely.d.ts +148 -0
  60. package/dist/src/Evolu/PublicKysely.d.ts.map +1 -0
  61. package/dist/src/Evolu/PublicKysely.js +185 -0
  62. package/dist/src/Evolu/Query.d.ts +63 -0
  63. package/dist/src/Evolu/Query.d.ts.map +1 -0
  64. package/dist/src/Evolu/Query.js +61 -0
  65. package/dist/src/Evolu/Relay.d.ts +13 -0
  66. package/dist/src/Evolu/Relay.d.ts.map +1 -0
  67. package/dist/src/Evolu/Relay.js +109 -0
  68. package/dist/src/Evolu/Schema.d.ts +201 -0
  69. package/dist/src/Evolu/Schema.d.ts.map +1 -0
  70. package/dist/src/Evolu/Schema.js +150 -0
  71. package/dist/src/Evolu/Storage.d.ts +49 -0
  72. package/dist/src/Evolu/Storage.d.ts.map +1 -0
  73. package/dist/src/Evolu/Storage.js +1111 -0
  74. package/dist/src/Evolu/Sync.d.ts +59 -0
  75. package/dist/src/Evolu/Sync.d.ts.map +1 -0
  76. package/dist/src/Evolu/Sync.js +29 -0
  77. package/dist/src/Evolu/Timestamp.d.ts +106 -0
  78. package/dist/src/Evolu/Timestamp.d.ts.map +1 -0
  79. package/dist/src/Evolu/Timestamp.js +179 -0
  80. package/dist/src/Function.d.ts +54 -0
  81. package/dist/src/Function.d.ts.map +1 -0
  82. package/dist/src/Function.js +38 -0
  83. package/dist/src/ManyToManyMap.d.ts +26 -0
  84. package/dist/src/ManyToManyMap.d.ts.map +1 -0
  85. package/dist/src/ManyToManyMap.js +92 -0
  86. package/dist/src/NanoId.d.ts +27 -0
  87. package/dist/src/NanoId.d.ts.map +1 -0
  88. package/dist/src/NanoId.js +6 -0
  89. package/dist/src/Number.d.ts +42 -0
  90. package/dist/src/Number.d.ts.map +1 -0
  91. package/dist/src/Number.js +55 -0
  92. package/dist/src/Object.d.ts +35 -0
  93. package/dist/src/Object.d.ts.map +1 -0
  94. package/dist/src/Object.js +36 -0
  95. package/dist/src/Order.d.ts +90 -0
  96. package/dist/src/Order.d.ts.map +1 -0
  97. package/dist/src/Order.js +85 -0
  98. package/dist/src/Promise.d.ts +180 -0
  99. package/dist/src/Promise.d.ts.map +1 -0
  100. package/dist/src/Promise.js +176 -0
  101. package/dist/src/Random.d.ts +52 -0
  102. package/dist/src/Random.d.ts.map +1 -0
  103. package/dist/src/Random.js +29 -0
  104. package/dist/src/Ref.d.ts +40 -0
  105. package/dist/src/Ref.d.ts.map +1 -0
  106. package/dist/src/Ref.js +13 -0
  107. package/dist/src/Result.d.ts +421 -0
  108. package/dist/src/Result.d.ts.map +1 -0
  109. package/dist/src/Result.js +357 -0
  110. package/dist/src/Skiplist.d.ts +23 -0
  111. package/dist/src/Skiplist.d.ts.map +1 -0
  112. package/dist/src/Skiplist.js +58 -0
  113. package/dist/src/Sqlite.d.ts +116 -52
  114. package/dist/src/Sqlite.d.ts.map +1 -1
  115. package/dist/src/Sqlite.js +183 -67
  116. package/dist/src/Store.d.ts +45 -8
  117. package/dist/src/Store.d.ts.map +1 -1
  118. package/dist/src/Store.js +33 -17
  119. package/dist/src/String.d.ts +2 -0
  120. package/dist/src/String.d.ts.map +1 -0
  121. package/dist/src/String.js +14 -0
  122. package/dist/src/Time.d.ts +20 -0
  123. package/dist/src/Time.d.ts.map +1 -0
  124. package/dist/src/Time.js +25 -0
  125. package/dist/src/Type.d.ts +1937 -0
  126. package/dist/src/Type.d.ts.map +1 -0
  127. package/dist/src/Type.js +2002 -0
  128. package/dist/src/Types.d.ts +188 -0
  129. package/dist/src/Types.d.ts.map +1 -0
  130. package/dist/src/Types.js +6 -0
  131. package/dist/src/WebSocket.d.ts +112 -0
  132. package/dist/src/WebSocket.d.ts.map +1 -0
  133. package/dist/src/WebSocket.js +139 -0
  134. package/dist/src/Worker.d.ts +44 -0
  135. package/dist/src/Worker.d.ts.map +1 -0
  136. package/dist/src/Worker.js +66 -0
  137. package/dist/src/index.d.ts +24 -11
  138. package/dist/src/index.d.ts.map +1 -1
  139. package/dist/src/index.js +24 -11
  140. package/package.json +29 -38
  141. package/src/Array.ts +39 -0
  142. package/src/Assert.ts +116 -0
  143. package/src/BigInt.ts +29 -0
  144. package/src/Buffer.ts +175 -0
  145. package/src/Callbacks.ts +43 -0
  146. package/src/Console.ts +159 -0
  147. package/src/Crypto.ts +169 -115
  148. package/src/Eq.ts +204 -0
  149. package/src/Error.ts +57 -20
  150. package/src/Evolu/Config.ts +83 -0
  151. package/src/Evolu/Db.ts +1275 -0
  152. package/src/Evolu/Diff.ts +142 -0
  153. package/src/Evolu/Evolu.ts +947 -0
  154. package/src/Evolu/Internal.ts +26 -0
  155. package/src/Evolu/Kysely.ts +38 -0
  156. package/src/Evolu/Owner.ts +296 -0
  157. package/src/Evolu/Platform.ts +27 -0
  158. package/src/Evolu/Protocol.ts +1857 -0
  159. package/src/Evolu/Public.ts +43 -0
  160. package/src/Evolu/PublicKysely.ts +240 -0
  161. package/src/Evolu/Query.ts +167 -0
  162. package/src/Evolu/Relay.ts +142 -0
  163. package/src/Evolu/Schema.ts +417 -0
  164. package/src/Evolu/Storage.ts +1281 -0
  165. package/src/Evolu/Sync.ts +105 -0
  166. package/src/Evolu/Timestamp.ts +311 -0
  167. package/src/Function.ts +58 -0
  168. package/src/ManyToManyMap.ts +140 -0
  169. package/src/NanoId.ts +39 -0
  170. package/src/Number.ts +90 -0
  171. package/src/Object.ts +64 -0
  172. package/src/Order.ts +113 -0
  173. package/src/Promise.ts +295 -0
  174. package/src/Random.ts +68 -0
  175. package/src/Ref.ts +63 -0
  176. package/src/Result.ts +453 -0
  177. package/src/Skiplist.ts +102 -0
  178. package/src/Sqlite.ts +366 -153
  179. package/src/Store.ts +79 -36
  180. package/src/String.ts +10 -0
  181. package/src/Time.ts +36 -0
  182. package/src/Type.ts +3978 -0
  183. package/src/Types.ts +209 -0
  184. package/src/WebSocket.ts +273 -0
  185. package/src/Worker.ts +129 -0
  186. package/src/index.ts +24 -11
  187. package/dist/src/Config.d.ts +0 -56
  188. package/dist/src/Config.d.ts.map +0 -1
  189. package/dist/src/Config.js +0 -39
  190. package/dist/src/Crdt.d.ts +0 -89
  191. package/dist/src/Crdt.d.ts.map +0 -1
  192. package/dist/src/Crdt.js +0 -181
  193. package/dist/src/Db.d.ts +0 -107
  194. package/dist/src/Db.d.ts.map +0 -1
  195. package/dist/src/Db.js +0 -443
  196. package/dist/src/Diff.d.ts +0 -27
  197. package/dist/src/Diff.d.ts.map +0 -1
  198. package/dist/src/Diff.js +0 -84
  199. package/dist/src/Evolu.d.ts +0 -426
  200. package/dist/src/Evolu.d.ts.map +0 -1
  201. package/dist/src/Evolu.js +0 -333
  202. package/dist/src/Model.d.ts +0 -141
  203. package/dist/src/Model.d.ts.map +0 -1
  204. package/dist/src/Model.js +0 -125
  205. package/dist/src/Murmurhash.d.ts +0 -2
  206. package/dist/src/Murmurhash.d.ts.map +0 -1
  207. package/dist/src/Murmurhash.js +0 -60
  208. package/dist/src/Owner.d.ts +0 -33
  209. package/dist/src/Owner.d.ts.map +0 -1
  210. package/dist/src/Owner.js +0 -26
  211. package/dist/src/Platform.d.ts +0 -37
  212. package/dist/src/Platform.d.ts.map +0 -1
  213. package/dist/src/Platform.js +0 -11
  214. package/dist/src/Protobuf.d.ts +0 -81
  215. package/dist/src/Protobuf.d.ts.map +0 -1
  216. package/dist/src/Protobuf.js +0 -92
  217. package/dist/src/Public.d.ts +0 -13
  218. package/dist/src/Public.d.ts.map +0 -1
  219. package/dist/src/Public.js +0 -6
  220. package/dist/src/Socket.d.ts +0 -8
  221. package/dist/src/Socket.d.ts.map +0 -1
  222. package/dist/src/Socket.js +0 -51
  223. package/dist/src/Sql.d.ts +0 -12
  224. package/dist/src/Sql.d.ts.map +0 -1
  225. package/dist/src/Sql.js +0 -30
  226. package/dist/src/Sync.d.ts +0 -70
  227. package/dist/src/Sync.d.ts.map +0 -1
  228. package/dist/src/Sync.js +0 -127
  229. package/src/Config.ts +0 -119
  230. package/src/Crdt.ts +0 -361
  231. package/src/Db.ts +0 -955
  232. package/src/Diff.ts +0 -114
  233. package/src/Evolu.ts +0 -1016
  234. package/src/Model.ts +0 -233
  235. package/src/Murmurhash.ts +0 -70
  236. package/src/Owner.ts +0 -69
  237. package/src/Platform.ts +0 -47
  238. package/src/Protobuf.ts +0 -155
  239. package/src/Public.ts +0 -12
  240. package/src/Socket.ts +0 -83
  241. package/src/Sql.ts +0 -41
  242. package/src/Sync.ts +0 -315
package/dist/src/Db.js DELETED
@@ -1,443 +0,0 @@
1
- import * as S from "@effect/schema/Schema";
2
- import * as Arr from "effect/Array";
3
- import * as Console from "effect/Console";
4
- import * as Context from "effect/Context";
5
- import * as Deferred from "effect/Deferred";
6
- import * as Effect from "effect/Effect";
7
- import { constVoid, pipe } from "effect/Function";
8
- import * as Option from "effect/Option";
9
- import * as Predicate from "effect/Predicate";
10
- import * as Ref from "effect/Ref";
11
- import * as Schedule from "effect/Schedule";
12
- import * as String from "effect/String";
13
- import * as SynchronizedRef from "effect/SynchronizedRef";
14
- import { Time, diffMerkleTrees, initialMerkleTree, insertIntoMerkleTree, makeInitialTimestamp, makeSyncTimestamp, merkleTreeToString, receiveTimestamp, sendTimestamp, timestampToString, unsafeTimestampFromString, } from "./Crdt.js";
15
- import { NanoIdGenerator } from "./Crypto.js";
16
- import { makePatches } from "./Diff.js";
17
- import { ensureTransferableError, makeUnexpectedError, } from "./Error.js";
18
- import { cast } from "./Model.js";
19
- import { Owner, makeOwner } from "./Owner.js";
20
- import { SyncLock } from "./Platform.js";
21
- import * as Sql from "./Sql.js";
22
- import { Sqlite, SqliteFactory, drawSqliteQueryPlan, isJsonObjectOrArray, } from "./Sqlite.js";
23
- import { Sync, SyncFactory, } from "./Sync.js";
24
- export const Index = S.Struct({
25
- name: S.String,
26
- sql: S.String,
27
- });
28
- export class DbFactory extends Context.Tag("DbFactory")() {
29
- }
30
- export const createDb = Effect.gen(function* () {
31
- const { createSqlite } = yield* SqliteFactory;
32
- const { createSync } = yield* SyncFactory;
33
- const initContext = Context.empty().pipe(Context.add(NanoIdGenerator, yield* NanoIdGenerator), Context.add(Time, yield* Time), Context.add(SyncLock, yield* SyncLock));
34
- const afterInitContext = yield* Deferred.make();
35
- const afterInit = (options) => (effect) => Effect.flatMap(Deferred.await(afterInitContext), (context) => Effect.flatMap(Sqlite, (sqlite) => sqlite.transaction(options.transaction)(effect)).pipe(Effect.provide(context)));
36
- const queryRowsRef = yield* SynchronizedRef.make(new Map());
37
- const db = {
38
- init: (schema, initialData, onError, onSyncStateChange, onReceive, mnemonic) => Effect.gen(function* () {
39
- yield* Effect.logDebug(["Db init", { schema }]);
40
- const sqlite = yield* createSqlite;
41
- const contextWithSqlite = Context.add(initContext, Sqlite, sqlite);
42
- const owner = yield* getSchema.pipe(Effect.tap(ensureSchema(schema)), Effect.flatMap((currentSchema) => {
43
- if (currentSchema.tables.map((t) => t.name).includes("evolu_owner"))
44
- return readOwner;
45
- return createOwner(mnemonic).pipe(Effect.tap(applyMutations(initialData)));
46
- }), sqlite.transaction("exclusive"), Effect.provide(contextWithSqlite));
47
- const sync = yield* createSync.pipe(Effect.provide(initContext));
48
- yield* sync.init(owner);
49
- Deferred.unsafeDone(afterInitContext, Effect.succeed(contextWithSqlite.pipe(Context.add(Owner, owner), Context.add(Sync, sync), Context.add(Callbacks, { onError, onSyncStateChange, onReceive }))));
50
- return owner;
51
- }),
52
- loadQueries: (queries) => Effect.logDebug(["Db loadQueries", { queries }]).pipe(Effect.zipRight(loadQueries(queries, queryRowsRef)), afterInit({ transaction: "shared" })),
53
- mutate: (mutations, queriesToRefresh) => Effect.gen(function* () {
54
- yield* Effect.logDebug(["Db mutate", { mutations, queriesToRefresh }]);
55
- const time = yield* Time;
56
- const sqlite = yield* Sqlite;
57
- const [toSyncMutations, localOnlyMutations] = Arr.partition(mutations,
58
- /** Table name starting with '_' is local only (not synced). */
59
- (mutation) => mutation.table.startsWith("_"));
60
- for (const mutation of localOnlyMutations) {
61
- const isDeleteMutation = mutationToNewMessages(mutation).some(({ column, value }) => column === "isDeleted" && value === 1);
62
- if (isDeleteMutation) {
63
- yield* sqlite.exec({
64
- sql: `delete from "${mutation.table}" where "id" = ?;`,
65
- parameters: [mutation.id],
66
- });
67
- }
68
- else {
69
- const messages = mutationToNewMessages(mutation);
70
- for (const message of messages) {
71
- const now = yield* time.now;
72
- yield* upsertValueIntoTableRowColumn(message, messages, now);
73
- }
74
- }
75
- }
76
- if (toSyncMutations.length > 0) {
77
- yield* Effect.tap(applyMutations(toSyncMutations), forkSync);
78
- }
79
- return yield* loadQueries(queriesToRefresh, queryRowsRef);
80
- }).pipe(afterInit({ transaction: "exclusive" })),
81
- resetOwner: () => Effect.logTrace("Db resetOwner").pipe(Effect.tap(dropAllTables), afterInit({ transaction: "last" })),
82
- restoreOwner: (schema, mnemonic) => Effect.logTrace("Db restoreOwner").pipe(Effect.tap(dropAllTables), Effect.tap(Effect.flatMap(getSchema, ensureSchema(schema))), Effect.tap(createOwner(mnemonic)), afterInit({ transaction: "last" })),
83
- ensureSchema: (schema) => getSchema.pipe(Effect.flatMap(ensureSchema(schema)), afterInit({ transaction: "exclusive" })),
84
- sync: (queriesToRefresh) => Effect.logDebug(["Db sync", { queriesToRefresh }]).pipe(Effect.zipRight(forkSync()), Effect.zipRight(loadQueries(queriesToRefresh, queryRowsRef)), afterInit({ transaction: "shared" })),
85
- exportDatabase: () => Effect.logTrace("Db exportDatabase").pipe(Effect.zipRight(Sqlite), Effect.flatMap((sqlite) => sqlite.export()), afterInit({ transaction: "exclusive" })),
86
- // TODO:
87
- // dispose: () =>
88
- // Effect.logTrace("Db dispose").pipe(
89
- // Effect.tap(Scope.close(scope, Exit.succeed("Db disposed"))),
90
- // afterInit({ transaction: "last" }),
91
- // ),
92
- };
93
- return db;
94
- });
95
- export const Callbacks = Context.GenericTag("Callbacks");
96
- const loadQueries = (queries, queryRowsRef) => Effect.gen(function* () {
97
- const sqlite = yield* Sqlite;
98
- const previousState = yield* SynchronizedRef.getAndUpdateEffect(queryRowsRef, (previousState) => Effect.map(Effect.forEach(queries, (query) => {
99
- const sqliteQuery = deserializeQuery(query);
100
- return sqlite.exec(sqliteQuery).pipe(Effect.tap(maybeExplainQueryPlan(sqliteQuery)), Effect.map(({ rows }) => [query, rows]));
101
- }), (queriesRows) => new Map([...previousState, ...queriesRows])));
102
- const currentState = yield* SynchronizedRef.get(queryRowsRef);
103
- return queries.map((query) => ({
104
- query,
105
- patches: makePatches(previousState.get(query), currentState.get(query) || []),
106
- }));
107
- });
108
- const maybeExplainQueryPlan = (sqliteQuery) => {
109
- if (!sqliteQuery.options?.logExplainQueryPlan)
110
- return Effect.void;
111
- return Sqlite.pipe(Effect.flatMap((sqlite) => sqlite.exec({
112
- ...sqliteQuery,
113
- sql: `EXPLAIN QUERY PLAN ${sqliteQuery.sql}`,
114
- })), Effect.tap(Console.log("ExplainQueryPlan", sqliteQuery)), Effect.tap(({ rows }) => Console.log(drawSqliteQueryPlan(rows))), Effect.map(constVoid));
115
- };
116
- const getSchema = Effect.gen(function* (_) {
117
- yield* Effect.logTrace("Db getSchema");
118
- const sqlite = yield* Sqlite;
119
- const tables = yield* Effect.map(sqlite.exec({
120
- // https://til.simonwillison.net/sqlite/list-all-columns-in-a-database
121
- sql: `
122
- select
123
- sqlite_master.name as tableName,
124
- table_info.name as columnName
125
- from
126
- sqlite_master
127
- join pragma_table_info(sqlite_master.name) as table_info
128
- `.trim(),
129
- }), ({ rows }) => {
130
- const map = new Map();
131
- rows.forEach((row) => {
132
- const { tableName, columnName } = row;
133
- if (!map.has(tableName))
134
- map.set(tableName, []);
135
- map.get(tableName)?.push(columnName);
136
- });
137
- return globalThis.Array.from(map, ([name, columns]) => ({
138
- name,
139
- columns,
140
- }));
141
- });
142
- const indexes = yield* Effect.map(sqlite.exec({
143
- sql: `
144
- select
145
- name, sql
146
- from
147
- sqlite_master
148
- where
149
- type='index' and
150
- name not like 'sqlite_%' and
151
- name not like 'index_evolu_%'
152
- `.trim(),
153
- }), (result) => Arr.map(result.rows, (row) => ({
154
- name: row.name,
155
- /**
156
- * SQLite returns "CREATE INDEX" for "create index" for some reason.
157
- * Other keywords remain unchanged. We have to normalize the casing
158
- * for `indexEquivalence` manually.
159
- */
160
- sql: row.sql.replace("CREATE INDEX", "create index"),
161
- })));
162
- return { tables, indexes };
163
- });
164
- const ensureSchema = (newSchema) => (currentSchema) => Effect.gen(function* () {
165
- yield* Effect.logTrace("Db ensureSchema");
166
- const sql = [];
167
- newSchema.tables.forEach((table) => {
168
- const currentTable = currentSchema.tables.find((t) => t.name === table.name);
169
- if (!currentTable) {
170
- sql.push(`
171
- create table ${table.name} (
172
- "id" text primary key,
173
- ${table.columns
174
- .filter((c) => c !== "id")
175
- // "A column with affinity BLOB does not prefer one storage class over another
176
- // and no attempt is made to coerce data from one storage class into another."
177
- // https://www.sqlite.org/datatype3.html
178
- .map((name) => `"${name}" blob`)
179
- .join(", ")}
180
- );`.trim());
181
- }
182
- else {
183
- Arr.differenceWith(String.Equivalence)(table.columns, currentTable.columns).forEach((newColumn) => {
184
- sql.push(`alter table "${table.name}" add column "${newColumn}" blob;`);
185
- });
186
- }
187
- });
188
- // Remove old indexes.
189
- Arr.differenceWith(indexEquivalence)(currentSchema.indexes, Arr.intersectionWith(indexEquivalence)(currentSchema.indexes, newSchema.indexes)).forEach((indexToDrop) => {
190
- sql.push(`drop index "${indexToDrop.name}";`);
191
- });
192
- // Add new indexes.
193
- Arr.differenceWith(indexEquivalence)(newSchema.indexes, currentSchema.indexes).forEach((newIndex) => {
194
- sql.push(`${newIndex.sql};`);
195
- });
196
- if (sql.length > 0) {
197
- const sqlite = yield* Sqlite;
198
- yield* sqlite.exec({ sql: sql.join("\n") });
199
- }
200
- });
201
- const indexEquivalence = (self, that) => self.name === that.name && self.sql === that.sql;
202
- const readOwner = Effect.logTrace("Db readOwner").pipe(Effect.zipRight(Sqlite), Effect.flatMap((sqlite) => sqlite.exec(Sql.selectOwner)), Effect.map(({ rows: [row] }) => ({
203
- id: row.id,
204
- mnemonic: row.mnemonic,
205
- encryptionKey: row.encryptionKey,
206
- })));
207
- const createOwner = (mnemonic) => Effect.logTrace("Db createOwner").pipe(Effect.zipRight(Effect.all([makeOwner(mnemonic), Sqlite, makeInitialTimestamp])), Effect.tap(([owner, sqlite, initialTimestampString]) => Effect.all([
208
- sqlite.exec(Sql.createMessageTable),
209
- sqlite.exec(Sql.createMessageTableIndex),
210
- sqlite.exec(Sql.createOwnerTable),
211
- sqlite.exec({
212
- ...Sql.insertOwner,
213
- parameters: [
214
- owner.id,
215
- owner.mnemonic,
216
- owner.encryptionKey,
217
- timestampToString(initialTimestampString),
218
- merkleTreeToString(initialMerkleTree),
219
- ],
220
- }),
221
- ])), Effect.map(([owner]) => owner));
222
- const applyMutations = (mutations) => Effect.gen(function* (_) {
223
- const { timestamp, merkleTree } = yield* readTimestampAndMerkleTree;
224
- const [nextTimestamp, messages] = yield* Effect.mapAccum(mutations.flatMap(mutationToNewMessages), timestamp, (currentTimestamp, newMessage) => Effect.map(sendTimestamp(currentTimestamp), (nextTimestamp) => {
225
- const message = {
226
- ...newMessage,
227
- timestamp: timestampToString(nextTimestamp),
228
- };
229
- return [nextTimestamp, message];
230
- }));
231
- const nextMerkleTree = yield* applyMessages(merkleTree, messages);
232
- yield* writeTimestampAndMerkleTree(nextTimestamp, nextMerkleTree);
233
- return messages;
234
- });
235
- const readTimestampAndMerkleTree = Sqlite.pipe(Effect.flatMap((sqlite) => sqlite.exec(Sql.selectOwnerTimestampAndMerkleTree)), Effect.map(({ rows: [{ timestamp, merkleTree }] }) => ({
236
- timestamp: unsafeTimestampFromString(timestamp),
237
- merkleTree: merkleTree,
238
- })));
239
- const mutationToNewMessages = (mutation) => pipe(Object.entries(mutation.values), Arr.filterMap(([column, value]) =>
240
- // The value can be undefined if exactOptionalPropertyTypes isn't true.
241
- // Don't insert nulls because null is the default value.
242
- value === undefined || (mutation.isInsert && value == null)
243
- ? Option.none()
244
- : Option.some([column, value])), Arr.map(([column, value]) => ({
245
- table: mutation.table,
246
- row: mutation.id,
247
- column,
248
- value: typeof value === "boolean"
249
- ? cast(value)
250
- : value instanceof Date
251
- ? cast(value)
252
- : value,
253
- })));
254
- const applyMessages = (merkleTree, messages) => Effect.gen(function* () {
255
- const sqlite = yield* Sqlite;
256
- for (const message of messages) {
257
- const messageTimestamp = unsafeTimestampFromString(message.timestamp);
258
- const lastTimestamp = yield* Effect.map(sqlite.exec({
259
- ...Sql.selectLastTimestampForTableRowColumn,
260
- parameters: [message.table, message.row, message.column, 1],
261
- }), ({ rows }) => rows.length > 0 ? rows[0].timestamp : null);
262
- if (lastTimestamp == null || lastTimestamp < message.timestamp) {
263
- yield* upsertValueIntoTableRowColumn(message, messages, messageTimestamp.millis);
264
- }
265
- if (lastTimestamp == null || lastTimestamp !== message.timestamp) {
266
- const { changes } = yield* sqlite.exec({
267
- ...Sql.insertIntoMessagesIfNew,
268
- parameters: [
269
- message.timestamp,
270
- message.table,
271
- message.row,
272
- message.column,
273
- message.value,
274
- ],
275
- });
276
- if (changes === 1)
277
- merkleTree = insertIntoMerkleTree(merkleTree, messageTimestamp);
278
- }
279
- }
280
- return merkleTree;
281
- });
282
- export const upsertValueIntoTableRowColumn = (message, messages, millis) => Sqlite.pipe(Effect.map((sqlite) => {
283
- const now = cast(new Date(millis));
284
- return sqlite.exec({
285
- sql: `
286
- insert into
287
- "${message.table}" ("id", "${message.column}", "createdAt", "updatedAt")
288
- values
289
- (?, ?, ?, ?)
290
- on conflict do update set
291
- "${message.column}" = ?,
292
- "updatedAt" = ?
293
- `.trim(),
294
- parameters: [message.row, message.value, now, now, message.value, now],
295
- });
296
- }), Effect.flatMap((insert) => Effect.catchSomeDefect(insert, (error) => S.is(SqliteNoSuchTableOrColumnError)(error)
297
- ? Option.some(
298
- // If one message fails, we ensure schema for all messages.
299
- ensureSchemaByNewMessages(messages).pipe(Effect.zipRight(insert)))
300
- : Option.none())));
301
- const SqliteNoSuchTableOrColumnError = S.Struct({
302
- message: S.Union(S.String.pipe(S.includes("no such table")), S.String.pipe(S.includes("no such column")), S.String.pipe(S.includes("has no column"))),
303
- });
304
- const ensureSchemaByNewMessages = (messages) => Effect.gen(function* () {
305
- const tablesMap = new Map();
306
- messages.forEach((message) => {
307
- const table = tablesMap.get(message.table);
308
- if (table == null) {
309
- tablesMap.set(message.table, {
310
- name: message.table,
311
- columns: [message.column, "createdAt", "updatedAt"],
312
- });
313
- return;
314
- }
315
- if (table.columns.includes(message.column))
316
- return;
317
- tablesMap.set(message.table, {
318
- name: message.table,
319
- columns: table.columns.concat(message.column),
320
- });
321
- });
322
- const tables = Arr.fromIterable(tablesMap.values());
323
- yield* Effect.flatMap(getSchema, ensureSchema({ tables, indexes: [] }));
324
- });
325
- const writeTimestampAndMerkleTree = (timestamp, merkleTree) => Effect.flatMap(Sqlite, (sqlite) => sqlite.exec({
326
- ...Sql.updateOwnerTimestampAndMerkleTree,
327
- parameters: [
328
- merkleTreeToString(merkleTree),
329
- timestampToString(timestamp),
330
- ],
331
- }));
332
- const dropAllTables = Effect.gen(function* () {
333
- yield* Effect.logTrace("Db dropAllTables");
334
- const sqlite = yield* Sqlite;
335
- const schema = yield* getSchema;
336
- yield* Effect.forEach(schema.tables, (table) =>
337
- // The dropped table is completely removed from the database schema and
338
- // the disk file. The table can not be recovered.
339
- // All indices and triggers associated with the table are also deleted.
340
- // https://sqlite.org/lang_droptable.html
341
- sqlite.exec({ sql: `drop table "${table.name}"` }));
342
- });
343
- const forkSync = (messages = []) => SyncLock.pipe(Effect.flatMap((syncLock) => syncLock.tryAcquire), Effect.matchEffect({
344
- onFailure: () => Effect.void,
345
- onSuccess: () => syncLoop(messages),
346
- }), Effect.scoped, Effect.forkDaemon);
347
- const syncLoop = (messages = []) => Effect.gen(function* (_) {
348
- const sqlite = yield* Sqlite;
349
- const sync = yield* Sync;
350
- const callbacks = yield* Callbacks;
351
- const time = yield* Time;
352
- const syncDataRef = yield* readTimestampAndMerkleTree.pipe(sqlite.transaction("shared"), Effect.map((a) => ({ ...a, messages })), Effect.flatMap(Ref.make));
353
- callbacks.onSyncStateChange({ _tag: "SyncStateIsSyncing" });
354
- yield* Ref.get(syncDataRef).pipe(Effect.flatMap(sync.sync), Effect.flatMap(handleSyncResult), Effect.repeat({
355
- // TODO: Schedule.jittered(Schedule.exponential("10 millis"))
356
- schedule: Schedule.recurs(50),
357
- until: Option.match({
358
- onNone: () => Effect.gen(function* () {
359
- callbacks.onSyncStateChange({
360
- _tag: "SyncStateIsSynced",
361
- time: yield* time.now,
362
- });
363
- return true;
364
- }),
365
- onSome: (syncData) => Effect.gen(function* () {
366
- yield* Ref.set(syncDataRef, syncData);
367
- return false;
368
- }),
369
- }),
370
- }), Effect.catchAllDefect((error) =>
371
- // Db can run in a Web Worker, so we must ensure transferable error.
372
- Effect.fail(makeUnexpectedError(ensureTransferableError(error)))), Effect.catchTag("SyncStateIsNotSynced", (state) => Effect.succeed(callbacks.onSyncStateChange(state))), Effect.catchAll((error) => Effect.succeed(callbacks.onError(error))));
373
- });
374
- const handleSyncResult = (result) => Effect.flatMap(Sqlite, (sqlite) => Effect.gen(function* (_) {
375
- const { onReceive } = yield* Callbacks;
376
- const current = yield* readTimestampAndMerkleTree;
377
- const nextTimestamp = yield* Effect.reduce(result.messages, current.timestamp, (local, message) => receiveTimestamp({
378
- local,
379
- remote: unsafeTimestampFromString(message.timestamp),
380
- }));
381
- const nextMerkleTree = yield* applyMessages(current.merkleTree, result.messages);
382
- if (result.messages.length > 0) {
383
- yield* writeTimestampAndMerkleTree(nextTimestamp, nextMerkleTree);
384
- onReceive();
385
- }
386
- return yield* diffMerkleTrees(result.merkleTree, nextMerkleTree).pipe(Effect.flatMap((diff) => sqlite.exec({
387
- ...Sql.selectMessagesToSync,
388
- parameters: [timestampToString(makeSyncTimestamp(diff))],
389
- })), Effect.map(({ rows }) => ({
390
- messages: rows,
391
- merkleTree: nextMerkleTree,
392
- timestamp: nextTimestamp,
393
- })), Effect.option);
394
- }).pipe(sqlite.transaction("exclusive")));
395
- // We use queries as keys, hence JSON.stringify.
396
- export const serializeQuery = ({ sql, parameters = [], options, }) => {
397
- const query = {
398
- sql,
399
- parameters: parameters.map((p) => Predicate.isUint8Array(p)
400
- ? Arr.fromIterable(p)
401
- : isJsonObjectOrArray(p)
402
- ? { json: p }
403
- : p),
404
- ...(options && { options }),
405
- };
406
- return JSON.stringify(query);
407
- };
408
- export const deserializeQuery = (query) => {
409
- const serializedSqliteQuery = JSON.parse(query);
410
- return {
411
- ...serializedSqliteQuery,
412
- parameters: serializedSqliteQuery.parameters.map((p) => Arr.isArray(p)
413
- ? new Uint8Array(p)
414
- : typeof p === "object" && p != null
415
- ? p.json
416
- : p),
417
- };
418
- };
419
- // To preserve identity.
420
- const _emptyRows = [];
421
- export const emptyRows = () => _emptyRows;
422
- // To preserve identity.
423
- const queryResultCache = new WeakMap();
424
- export const queryResultFromRows = (rows) => {
425
- let queryResult = queryResultCache.get(rows);
426
- if (queryResult == null) {
427
- queryResult = { rows, row: rows[0] };
428
- queryResultCache.set(rows, queryResult);
429
- }
430
- return queryResult;
431
- };
432
- export const notSupportedPlatformWorker = {
433
- init: () => Effect.fail({
434
- _tag: "NotSupportedPlatformError",
435
- }),
436
- loadQueries: () => Effect.succeed([]),
437
- mutate: () => Effect.succeed([]),
438
- resetOwner: () => Effect.void,
439
- restoreOwner: () => Effect.void,
440
- ensureSchema: () => Effect.void,
441
- sync: () => Effect.succeed([]),
442
- exportDatabase: () => Effect.succeed(new Uint8Array()),
443
- };
@@ -1,27 +0,0 @@
1
- import type { Query, Row } from "./Db.js";
2
- import { Value } from "./Sqlite.js";
3
- export interface QueryPatches {
4
- readonly query: Query;
5
- readonly patches: ReadonlyArray<Patch>;
6
- }
7
- export type Patch = ReplaceAllPatch | ReplaceAtPatch;
8
- export interface ReplaceAllPatch {
9
- readonly op: "replaceAll";
10
- readonly value: ReadonlyArray<Row>;
11
- }
12
- export interface ReplaceAtPatch {
13
- readonly op: "replaceAt";
14
- readonly index: number;
15
- readonly value: Row;
16
- }
17
- export declare const applyPatches: (patches: ReadonlyArray<Patch>, current: ReadonlyArray<Row>) => ReadonlyArray<Row>;
18
- /**
19
- * We detect only changes in the whole result and in-place edits. In the future,
20
- * we will add more heuristics. We will probably not implement the Myers diff
21
- * algorithm because it's faster to rerender all than to compute many detailed
22
- * patches. We will only implement logic a developer would implement manually,
23
- * if necessary.
24
- */
25
- export declare const makePatches: (previousRows: ReadonlyArray<Row> | undefined, nextRows: ReadonlyArray<Row>) => readonly Patch[];
26
- export declare const areEqual: (a: Value | Row | ReadonlyArray<Row>, b: Value | Row | ReadonlyArray<Row>) => boolean;
27
- //# sourceMappingURL=Diff.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Diff.d.ts","sourceRoot":"","sources":["../../src/Diff.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;CACxC;AAED,MAAM,MAAM,KAAK,GAAG,eAAe,GAAG,cAAc,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC;CACrB;AAED,eAAO,MAAM,YAAY,YACd,aAAa,CAAC,KAAK,CAAC,WACpB,aAAa,CAAC,GAAG,CAAC,KAC1B,aAAa,CAAC,GAAG,CASP,CAAC;AAEd;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,iBACR,aAAa,CAAC,GAAG,CAAC,GAAG,SAAS,YAClC,aAAa,CAAC,GAAG,CAAC,KAC3B,SAAS,KAAK,EA2BhB,CAAC;AAEF,eAAO,MAAM,QAAQ,MAChB,KAAK,GAAG,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,KAChC,KAAK,GAAG,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,KAClC,OAkCF,CAAC"}
package/dist/src/Diff.js DELETED
@@ -1,84 +0,0 @@
1
- import * as Arr from "effect/Array";
2
- import * as Predicate from "effect/Predicate";
3
- export const applyPatches = (patches, current) => patches.reduce((next, patch) => {
4
- switch (patch.op) {
5
- case "replaceAll":
6
- return patch.value;
7
- case "replaceAt": {
8
- return Arr.replace(next, patch.index, patch.value);
9
- }
10
- }
11
- }, current);
12
- /**
13
- * We detect only changes in the whole result and in-place edits. In the future,
14
- * we will add more heuristics. We will probably not implement the Myers diff
15
- * algorithm because it's faster to rerender all than to compute many detailed
16
- * patches. We will only implement logic a developer would implement manually,
17
- * if necessary.
18
- */
19
- export const makePatches = (previousRows, nextRows) => {
20
- if (previousRows === undefined)
21
- return [{ op: "replaceAll", value: nextRows }];
22
- // TODO: Detect prepend and append, it's cheap.
23
- if (previousRows.length !== nextRows.length) {
24
- return [{ op: "replaceAll", value: nextRows }];
25
- }
26
- const length = previousRows.length;
27
- const replaceAtPatches = [];
28
- for (let i = 0; i < length; i++) {
29
- const previousRow = previousRows[i];
30
- const nextRow = nextRows[i];
31
- // We expect the same shape for both rows.
32
- for (const key in previousRow)
33
- if (!areEqual(previousRow[key], nextRow[key])) {
34
- replaceAtPatches.push({ op: "replaceAt", value: nextRow, index: i });
35
- break;
36
- }
37
- }
38
- if (length > 0 && replaceAtPatches.length === length) {
39
- return [{ op: "replaceAll", value: nextRows }];
40
- }
41
- return replaceAtPatches;
42
- };
43
- export const areEqual = (a, b) => {
44
- // Compare string, number, null ASAP.
45
- if (a === b)
46
- return true;
47
- // Different type works only for string and number, everything else is an object.
48
- if (typeof a !== typeof b)
49
- return false;
50
- // Both are nonnullable objects.
51
- if (typeof a === "object" && a !== null && b !== null) {
52
- const aIsUint8Array = Predicate.isUint8Array(a);
53
- const bIsUint8Array = Predicate.isUint8Array(b);
54
- if (aIsUint8Array && bIsUint8Array) {
55
- if (a.length !== b.length)
56
- return false;
57
- for (let i = 0; i < a.length; i++)
58
- if (a[i] !== b[i])
59
- return false;
60
- return true;
61
- }
62
- const aIsArray = Arr.isArray(a);
63
- const bIsArray = Arr.isArray(b);
64
- if (aIsArray && bIsArray) {
65
- if (a.length !== b.length)
66
- return false;
67
- for (let i = 0; i < a.length; i++)
68
- if (!areEqual(a[i], b[i]))
69
- return false;
70
- return true;
71
- }
72
- if (!aIsUint8Array && !bIsUint8Array && !aIsArray && !bIsArray) {
73
- const aKeys = Object.keys(a);
74
- const bKeys = Object.keys(b);
75
- if (aKeys.length !== bKeys.length)
76
- return false;
77
- for (const key of aKeys)
78
- if (!areEqual(a[key], b[key]))
79
- return false;
80
- return true;
81
- }
82
- }
83
- return false;
84
- };