@evolu/common 1.0.17 → 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.
- package/README.md +7 -7
- package/dist/src/Config.d.ts +4 -6
- package/dist/src/Config.d.ts.map +1 -1
- package/dist/src/Config.js +1 -1
- package/dist/src/Crdt.d.ts +15 -9
- package/dist/src/Crdt.d.ts.map +1 -1
- package/dist/src/Crdt.js +15 -14
- package/dist/src/Crypto.d.ts +7 -13
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +7 -9
- package/dist/src/Db.d.ts +36 -70
- package/dist/src/Db.d.ts.map +1 -1
- package/dist/src/Db.js +45 -51
- package/dist/src/DbWorker.d.ts +13 -14
- package/dist/src/DbWorker.d.ts.map +1 -1
- package/dist/src/DbWorker.js +56 -57
- package/dist/src/Diff.d.ts +3 -1
- package/dist/src/Diff.d.ts.map +1 -1
- package/dist/src/Diff.js +3 -7
- package/dist/src/{Errors.d.ts → ErrorStore.d.ts} +14 -7
- package/dist/src/ErrorStore.d.ts.map +1 -0
- package/dist/src/{Errors.js → ErrorStore.js} +2 -0
- package/dist/src/Evolu.d.ts +316 -58
- package/dist/src/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu.js +199 -185
- package/dist/src/Model.d.ts +47 -57
- package/dist/src/Model.d.ts.map +1 -1
- package/dist/src/Model.js +30 -35
- package/dist/src/OnCompletes.d.ts +11 -0
- package/dist/src/OnCompletes.d.ts.map +1 -0
- package/dist/src/OnCompletes.js +21 -0
- package/dist/src/Owner.d.ts +31 -0
- package/dist/src/Owner.d.ts.map +1 -0
- package/dist/src/Owner.js +20 -0
- package/dist/src/Platform.d.ts +15 -8
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +5 -5
- package/dist/src/Protobuf.d.ts +25 -73
- package/dist/src/Protobuf.d.ts.map +1 -1
- package/dist/src/Protobuf.js +4 -12
- package/dist/src/Public.d.ts +5 -3
- package/dist/src/Public.d.ts.map +1 -1
- package/dist/src/Public.js +1 -0
- package/dist/src/Sqlite.d.ts +8 -11
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +7 -7
- package/dist/src/Store.d.ts +6 -5
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +21 -16
- package/dist/src/SyncWorker.d.ts +8 -2
- package/dist/src/SyncWorker.d.ts.map +1 -1
- package/dist/src/SyncWorker.js +12 -19
- package/dist/src/index.d.ts +6 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -2
- package/package.json +23 -13
- package/src/Config.ts +7 -7
- package/src/Crdt.ts +29 -26
- package/src/Crypto.ts +14 -19
- package/src/Db.ts +113 -166
- package/src/DbWorker.ts +110 -140
- package/src/Diff.ts +5 -7
- package/src/{Errors.ts → ErrorStore.ts} +17 -8
- package/src/Evolu.ts +682 -442
- package/src/Model.ts +60 -85
- package/src/OnCompletes.ts +46 -0
- package/src/Owner.ts +65 -0
- package/src/Platform.ts +24 -16
- package/src/Protobuf.ts +25 -73
- package/src/Public.ts +5 -3
- package/src/Sqlite.ts +13 -24
- package/src/Store.ts +36 -24
- package/src/SyncWorker.ts +30 -38
- package/src/index.ts +6 -2
- 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
|
|
27
|
-
import { Id
|
|
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
|
-
|
|
36
|
-
QueryObject,
|
|
37
|
-
Row,
|
|
35
|
+
JsonObjectOrArray,
|
|
38
36
|
Sqlite,
|
|
37
|
+
SqliteQuery,
|
|
39
38
|
Value,
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
69
|
-
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
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
|
|
114
|
-
Simplify<ExcludeNullAndFalse<FilterMapRow>>
|
|
115
|
-
> | null;
|
|
126
|
+
readonly row: Readonly<Kysely.Simplify<R>> | null;
|
|
116
127
|
}
|
|
117
128
|
|
|
118
|
-
type
|
|
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
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
137
|
+
const queryResultCache = new WeakMap<ReadonlyArray<Row>, QueryResult<Row>>();
|
|
126
138
|
|
|
127
|
-
const
|
|
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
|
-
|
|
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
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
167
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
158
168
|
return out as any;
|
|
159
169
|
};
|
|
160
170
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
+
);
|