@evolu/common 1.0.16 → 2.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 (75) hide show
  1. package/README.md +7 -7
  2. package/dist/src/Config.d.ts +4 -6
  3. package/dist/src/Config.d.ts.map +1 -1
  4. package/dist/src/Config.js +1 -1
  5. package/dist/src/Crdt.d.ts +15 -9
  6. package/dist/src/Crdt.d.ts.map +1 -1
  7. package/dist/src/Crdt.js +15 -14
  8. package/dist/src/Crypto.d.ts +7 -13
  9. package/dist/src/Crypto.d.ts.map +1 -1
  10. package/dist/src/Crypto.js +7 -9
  11. package/dist/src/Db.d.ts +36 -70
  12. package/dist/src/Db.d.ts.map +1 -1
  13. package/dist/src/Db.js +45 -51
  14. package/dist/src/DbWorker.d.ts +13 -14
  15. package/dist/src/DbWorker.d.ts.map +1 -1
  16. package/dist/src/DbWorker.js +56 -57
  17. package/dist/src/Diff.d.ts +3 -1
  18. package/dist/src/Diff.d.ts.map +1 -1
  19. package/dist/src/Diff.js +3 -7
  20. package/dist/src/{Errors.d.ts → ErrorStore.d.ts} +14 -7
  21. package/dist/src/ErrorStore.d.ts.map +1 -0
  22. package/dist/src/{Errors.js → ErrorStore.js} +2 -0
  23. package/dist/src/Evolu.d.ts +316 -58
  24. package/dist/src/Evolu.d.ts.map +1 -1
  25. package/dist/src/Evolu.js +199 -185
  26. package/dist/src/Model.d.ts +47 -57
  27. package/dist/src/Model.d.ts.map +1 -1
  28. package/dist/src/Model.js +30 -35
  29. package/dist/src/OnCompletes.d.ts +11 -0
  30. package/dist/src/OnCompletes.d.ts.map +1 -0
  31. package/dist/src/OnCompletes.js +21 -0
  32. package/dist/src/Owner.d.ts +31 -0
  33. package/dist/src/Owner.d.ts.map +1 -0
  34. package/dist/src/Owner.js +20 -0
  35. package/dist/src/Platform.d.ts +15 -8
  36. package/dist/src/Platform.d.ts.map +1 -1
  37. package/dist/src/Platform.js +5 -5
  38. package/dist/src/Protobuf.d.ts +25 -73
  39. package/dist/src/Protobuf.d.ts.map +1 -1
  40. package/dist/src/Protobuf.js +4 -12
  41. package/dist/src/Public.d.ts +5 -3
  42. package/dist/src/Public.d.ts.map +1 -1
  43. package/dist/src/Public.js +1 -0
  44. package/dist/src/Sqlite.d.ts +8 -11
  45. package/dist/src/Sqlite.d.ts.map +1 -1
  46. package/dist/src/Sqlite.js +7 -7
  47. package/dist/src/Store.d.ts +6 -5
  48. package/dist/src/Store.d.ts.map +1 -1
  49. package/dist/src/Store.js +21 -16
  50. package/dist/src/SyncWorker.d.ts +8 -2
  51. package/dist/src/SyncWorker.d.ts.map +1 -1
  52. package/dist/src/SyncWorker.js +12 -19
  53. package/dist/src/index.d.ts +6 -2
  54. package/dist/src/index.d.ts.map +1 -1
  55. package/dist/src/index.js +6 -2
  56. package/package.json +23 -13
  57. package/src/Config.ts +7 -7
  58. package/src/Crdt.ts +29 -26
  59. package/src/Crypto.ts +14 -19
  60. package/src/Db.ts +113 -166
  61. package/src/DbWorker.ts +110 -140
  62. package/src/Diff.ts +5 -7
  63. package/src/{Errors.ts → ErrorStore.ts} +17 -8
  64. package/src/Evolu.ts +682 -442
  65. package/src/Model.ts +60 -85
  66. package/src/OnCompletes.ts +46 -0
  67. package/src/Owner.ts +65 -0
  68. package/src/Platform.ts +24 -16
  69. package/src/Protobuf.ts +25 -73
  70. package/src/Public.ts +5 -3
  71. package/src/Sqlite.ts +13 -24
  72. package/src/Store.ts +36 -24
  73. package/src/SyncWorker.ts +30 -38
  74. package/src/index.ts +6 -2
  75. package/dist/src/Errors.d.ts.map +0 -1
package/src/Db.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  Context,
8
8
  Effect,
9
9
  Exit,
10
+ Layer,
10
11
  Option,
11
12
  Predicate,
12
13
  ReadonlyArray,
@@ -15,16 +16,15 @@ import {
15
16
  pipe,
16
17
  } from "effect";
17
18
  import * as Kysely from "kysely";
18
- import { Simplify } from "kysely";
19
- import { urlAlphabet } from "nanoid";
20
19
  import {
21
20
  initialMerkleTree,
22
21
  makeInitialTimestamp,
23
22
  merkleTreeToString,
24
23
  timestampToString,
25
24
  } from "./Crdt.js";
26
- import { Bip39, Mnemonic, NanoId, slip21Derive } from "./Crypto.js";
27
- import { Id, SqliteBoolean, SqliteDate } from "./Model.js";
25
+ import { Bip39, Mnemonic, NanoId } from "./Crypto.js";
26
+ import { Id } from "./Model.js";
27
+ import { Owner, makeOwner } from "./Owner.js";
28
28
  import {
29
29
  createMessageTable,
30
30
  createMessageTableIndex,
@@ -32,136 +32,145 @@ import {
32
32
  insertOwner,
33
33
  } from "./Sql.js";
34
34
  import {
35
- Query,
36
- QueryObject,
37
- Row,
35
+ JsonObjectOrArray,
38
36
  Sqlite,
37
+ SqliteQuery,
39
38
  Value,
40
- queryObjectToQuery,
39
+ isJsonObjectOrArray,
41
40
  } from "./Sqlite.js";
41
+ import { Store, makeStore } from "./Store.js";
42
+
43
+ export type Schema = ReadonlyRecord.ReadonlyRecord<TableSchema>;
42
44
 
43
45
  export type TableSchema = ReadonlyRecord.ReadonlyRecord<Value> & {
44
46
  readonly id: Id;
45
47
  };
46
48
 
47
- export type Schema = ReadonlyRecord.ReadonlyRecord<TableSchema>;
48
-
49
- export type CreateQuery<S extends Schema> = (
50
- queryCallback: QueryCallback<S, Row>,
51
- ) => Query;
49
+ // https://blog.beraliv.dev/2021-05-07-opaque-type-in-typescript
50
+ declare const __queryBrand: unique symbol;
52
51
 
53
- export type QueryCallback<S extends Schema, QueryRow> = (
54
- db: KyselyWithoutMutation<QuerySchema<S>>,
55
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
- ) => Kysely.SelectQueryBuilder<any, any, QueryRow>;
57
-
58
- type KyselyWithoutMutation<DB> = Pick<Kysely.Kysely<DB>, "selectFrom" | "fn">;
52
+ /**
53
+ * The query is an SQL query serialized as a string with a branded type
54
+ * representing a row it returns.
55
+ */
56
+ export type Query<R extends Row = Row> = string &
57
+ Brand.Brand<"Query"> & { readonly [__queryBrand]: R };
58
+
59
+ export type Queries<R extends Row = Row> = ReadonlyArray<Query<R>>;
60
+
61
+ interface SerializedSqliteQuery {
62
+ readonly sql: string;
63
+ readonly parameters: (
64
+ | null
65
+ | string
66
+ | number
67
+ | Array<number>
68
+ | { json: JsonObjectOrArray }
69
+ )[];
70
+ }
59
71
 
60
- type QuerySchema<S extends Schema> = {
61
- readonly [Table in keyof S]: NullableExceptOfId<
62
- {
63
- readonly [Column in keyof S[Table]]: S[Table][Column];
64
- } & CommonColumns
65
- >;
72
+ // We use queries as keys, hence JSON.stringify.
73
+ export const serializeQuery = <R extends Row>({
74
+ sql,
75
+ parameters,
76
+ }: SqliteQuery): Query<R> => {
77
+ const query: SerializedSqliteQuery = {
78
+ sql,
79
+ parameters: parameters.map((p) => {
80
+ return Predicate.isUint8Array(p)
81
+ ? Array.from(p)
82
+ : isJsonObjectOrArray(p)
83
+ ? { json: p }
84
+ : p;
85
+ }),
86
+ };
87
+ return JSON.stringify(query) as Query<R>;
66
88
  };
67
89
 
68
- export type NullableExceptOfId<T> = {
69
- readonly [K in keyof T]: K extends "id" ? T[K] : T[K] | null;
90
+ export const deserializeQuery = <R extends Row>(
91
+ query: Query<R>,
92
+ ): SqliteQuery => {
93
+ const serializedSqliteQuery = JSON.parse(query) as SerializedSqliteQuery;
94
+ return {
95
+ ...serializedSqliteQuery,
96
+ parameters: serializedSqliteQuery.parameters.map((p) =>
97
+ Array.isArray(p)
98
+ ? new Uint8Array(p)
99
+ : typeof p === "object" && p != null
100
+ ? p.json
101
+ : p,
102
+ ),
103
+ };
70
104
  };
71
105
 
72
- export interface CommonColumns {
73
- readonly createdAt: SqliteDate;
74
- readonly updatedAt: SqliteDate;
75
- readonly isDeleted: SqliteBoolean;
76
- }
106
+ export type Row = ReadonlyRecord.ReadonlyRecord<
107
+ | Value
108
+ | Row // for jsonObjectFrom from kysely/helpers/sqlite
109
+ | ReadonlyArray<Row> // for jsonArrayFrom from kysely/helpers/sqlite
110
+ >;
77
111
 
78
- /**
79
- * Filter and map array items in one step with the correct return type and
80
- * without unreliable TypeScript type guards.
81
- *
82
- * ### Examples
83
- *
84
- * ```
85
- * useQuery(
86
- * (db) => db.selectFrom("todo").selectAll(),
87
- * // Filter and map nothing.
88
- * (row) => row,
89
- * );
90
- *
91
- * useQuery(
92
- * (db) => db.selectFrom("todo").selectAll(),
93
- * // Filter items with title != null.
94
- * // Note the title type isn't nullable anymore in rows.
95
- * ({ title, ...rest }) => title != null && { title, ...rest },
96
- * );
97
- * ```
98
- */
99
- export type FilterMap<QueryRow extends Row, FilterMapRow extends Row> = (
100
- row: QueryRow,
101
- ) => FilterMapRow | null | false;
112
+ const _emptyRows: ReadonlyArray<Row> = [];
113
+
114
+ export const emptyRows = <R extends Row>(): ReadonlyArray<R> =>
115
+ _emptyRows as ReadonlyArray<R>;
116
+
117
+ /** An object with rows and row properties. */
118
+ export interface QueryResult<R extends Row = Row> {
119
+ /** An array containing all the rows returned by the query. */
120
+ readonly rows: ReadonlyArray<Readonly<Kysely.Simplify<R>>>;
102
121
 
103
- export interface QueryResult<FilterMapRow extends Row> {
104
- /**
105
- * Rows from the database. They can be filtered and mapped by `filterMap`.
106
- */
107
- readonly rows: ReadonlyArray<
108
- Readonly<Simplify<ExcludeNullAndFalse<FilterMapRow>>>
109
- >;
110
122
  /**
111
- * The first row from `rows`. For empty rows, it's null.
123
+ * The first row returned by the query, or null if no rows were returned. This
124
+ * property is useful for queries that are expected to return a single row.
112
125
  */
113
- readonly firstRow: Readonly<
114
- Simplify<ExcludeNullAndFalse<FilterMapRow>>
115
- > | null;
126
+ readonly row: Readonly<Kysely.Simplify<R>> | null;
116
127
  }
117
128
 
118
- type ExcludeNullAndFalse<T> = Exclude<T, null | false>;
129
+ export type QueryResultsFromQueries<Q extends Queries> = {
130
+ [P in keyof Q]: Q[P] extends Query<infer R> ? QueryResult<R> : never;
131
+ };
119
132
 
120
- export interface Table {
121
- readonly name: string;
122
- readonly columns: ReadonlyArray<string>;
123
- }
133
+ export type QueryResultsPromisesFromQueries<Q extends Queries> = {
134
+ [P in keyof Q]: Q[P] extends Query<infer R> ? Promise<QueryResult<R>> : never;
135
+ };
124
136
 
125
- export type Tables = ReadonlyArray<Table>;
137
+ const queryResultCache = new WeakMap<ReadonlyArray<Row>, QueryResult<Row>>();
126
138
 
127
- const commonColumns = ["createdAt", "updatedAt", "isDeleted"];
139
+ export const queryResultFromRows = <R extends Row>(
140
+ rows: ReadonlyArray<R>,
141
+ ): QueryResult<R> => {
142
+ let queryResult = queryResultCache.get(rows);
143
+ if (queryResult == null) {
144
+ queryResult = { rows, row: rows[0] };
145
+ queryResultCache.set(rows, queryResult);
146
+ }
147
+ return queryResult as QueryResult<R>;
148
+ };
128
149
 
129
- const kysely: Kysely.Kysely<QuerySchema<Schema>> = new Kysely.Kysely({
130
- dialect: {
131
- createAdapter: () => new Kysely.SqliteAdapter(),
132
- createDriver: () => new Kysely.DummyDriver(),
133
- createIntrospector(): Kysely.DatabaseIntrospector {
134
- throw "Not implemeneted";
135
- },
136
- createQueryCompiler: () => new Kysely.SqliteQueryCompiler(),
137
- },
138
- });
150
+ export type Tables = ReadonlyArray<Table>;
139
151
 
140
- export const makeCreateQuery =
141
- <S extends Schema>(): CreateQuery<S> =>
142
- (queryCallback) =>
143
- queryObjectToQuery(queryCallback(kysely as never).compile() as QueryObject);
152
+ export interface Table {
153
+ readonly name: string;
154
+ readonly columns: ReadonlyArray<string>;
155
+ }
144
156
 
145
157
  // https://github.com/Effect-TS/schema/releases/tag/v0.18.0
146
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
158
  const getPropertySignatures = <I extends { [K in keyof A]: any }, A>(
148
159
  schema: S.Schema<I, A>,
149
160
  ): { [K in keyof A]: S.Schema<I[K], A[K]> } => {
150
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
151
161
  const out: Record<PropertyKey, S.Schema<any>> = {};
152
162
  const propertySignatures = AST.getPropertySignatures(schema.ast);
153
163
  for (let i = 0; i < propertySignatures.length; i++) {
154
164
  const propertySignature = propertySignatures[i];
155
165
  out[propertySignature.name] = make(propertySignature.type);
156
166
  }
157
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-explicit-any
167
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
158
168
  return out as any;
159
169
  };
160
170
 
161
- export const schemaToTables = (
162
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
- schema: S.Schema<any, any>,
164
- ): Tables =>
171
+ const commonColumns = ["createdAt", "updatedAt", "isDeleted"];
172
+
173
+ export const schemaToTables = (schema: S.Schema<any, any>): Tables =>
165
174
  pipe(
166
175
  getPropertySignatures(schema),
167
176
  ReadonlyRecord.toEntries,
@@ -175,27 +184,6 @@ export const schemaToTables = (
175
184
  ),
176
185
  );
177
186
 
178
- /**
179
- * `Owner` represents the Evolu database owner. Evolu auto-generates `Owner`
180
- * on the first run. `Owner` can be reset on the current device and restored
181
- * on a different one.
182
- */
183
- export interface Owner {
184
- /** The `Mnemonic` associated with `Owner`. */
185
- readonly mnemonic: Mnemonic;
186
- /** The unique identifier of `Owner` safely derived from its `Mnemonic`. */
187
- readonly id: OwnerId;
188
- /* The encryption key used by `Owner` derived from its `Mnemonic`. */
189
- readonly encryptionKey: Uint8Array;
190
- }
191
-
192
- export const Owner = Context.Tag<Owner>("evolu/Owner");
193
-
194
- /**
195
- * The unique identifier of `Owner` safely derived from its `Mnemonic`.
196
- */
197
- export type OwnerId = Id & Brand.Brand<"Owner">;
198
-
199
187
  export const transaction = <R, E, A>(
200
188
  effect: Effect.Effect<R, E, A>,
201
189
  ): Effect.Effect<Sqlite | R, E, A> =>
@@ -234,36 +222,6 @@ export const someDefectToNoSuchTableOrColumnError = Effect.catchSomeDefect(
234
222
  : Option.none(),
235
223
  );
236
224
 
237
- export const makeOwner = (
238
- mnemonic?: Mnemonic,
239
- ): Effect.Effect<Bip39, never, Owner> =>
240
- Effect.gen(function* (_) {
241
- const bip39 = yield* _(Bip39);
242
-
243
- if (mnemonic == null) mnemonic = yield* _(bip39.make);
244
-
245
- const seed = yield* _(bip39.toSeed(mnemonic));
246
-
247
- const id = yield* _(
248
- slip21Derive(seed, ["Evolu", "Owner Id"]).pipe(
249
- Effect.map((key) => {
250
- // convert key to nanoid
251
- let id = "";
252
- for (let i = 0; i < 21; i++) {
253
- id += urlAlphabet[key[i] & 63];
254
- }
255
- return id as OwnerId;
256
- }),
257
- ),
258
- );
259
-
260
- const encryptionKey = yield* _(
261
- slip21Derive(seed, ["Evolu", "Encryption Key"]),
262
- );
263
-
264
- return { mnemonic, id, encryptionKey };
265
- });
266
-
267
225
  export const lazyInit = (
268
226
  mnemonic?: Mnemonic,
269
227
  ): Effect.Effect<Sqlite | Bip39 | NanoId, never, Owner> =>
@@ -369,23 +327,12 @@ export const ensureSchema = (
369
327
  ),
370
328
  );
371
329
 
372
- export const makeCacheFilterMap = (): (<
373
- QueryRow extends Row,
374
- FilterMapRow extends Row,
375
- >(
376
- filterMap: FilterMap<QueryRow, FilterMapRow>,
377
- ) => FilterMap<QueryRow, FilterMapRow>) => {
378
- const cache = new WeakMap<Row, Row | null | false>();
379
-
380
- return <QueryRow extends Row, FilterMapRow extends Row>(
381
- filterMap: FilterMap<QueryRow, FilterMapRow>,
382
- ): FilterMap<QueryRow, FilterMapRow> =>
383
- (row: QueryRow) => {
384
- let cachedRow = cache.get(row);
385
- if (cachedRow === undefined) {
386
- cachedRow = filterMap(row);
387
- cache.set(row, cachedRow);
388
- }
389
- return cachedRow as FilterMapRow | null | false;
390
- };
391
- };
330
+ export type RowsStore = Store<RowsStoreValue>;
331
+ export const RowsStore = Context.Tag<RowsStore>();
332
+
333
+ type RowsStoreValue = ReadonlyMap<Query, ReadonlyArray<Row>>;
334
+
335
+ export const RowsStoreLive = Layer.effect(
336
+ RowsStore,
337
+ makeStore<RowsStoreValue>(new Map()),
338
+ );