@evolu/common 4.0.4 → 4.1.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 +6 -0
- package/dist/src/Config.d.ts +18 -0
- package/dist/src/Config.d.ts.map +1 -1
- package/dist/src/Config.js +1 -0
- package/dist/src/Crdt.d.ts +4 -4
- package/dist/src/Crdt.d.ts.map +1 -1
- package/dist/src/Crypto.d.ts +2 -2
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Db.d.ts +19 -29
- package/dist/src/Db.d.ts.map +1 -1
- package/dist/src/Db.js +111 -34
- package/dist/src/DbWorker.d.ts +3 -4
- package/dist/src/DbWorker.d.ts.map +1 -1
- package/dist/src/DbWorker.js +42 -22
- package/dist/src/Evolu.d.ts +4 -2
- package/dist/src/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu.js +18 -6
- package/dist/src/Model.d.ts +17 -21
- package/dist/src/Model.d.ts.map +1 -1
- package/dist/src/Model.js +2 -5
- package/dist/src/Public.d.ts +2 -0
- package/dist/src/Public.d.ts.map +1 -1
- package/dist/src/Public.js +2 -0
- package/dist/src/Sql.d.ts +11 -12
- package/dist/src/Sql.d.ts.map +1 -1
- package/dist/src/Sql.js +30 -106
- package/dist/src/Sqlite.d.ts +29 -3
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +25 -5
- package/package.json +13 -13
- package/src/Config.ts +20 -0
- package/src/Crdt.ts +2 -2
- package/src/Crypto.ts +1 -1
- package/src/Db.ts +160 -84
- package/src/DbWorker.ts +57 -38
- package/src/Evolu.ts +36 -9
- package/src/Model.ts +10 -14
- package/src/Public.ts +2 -0
- package/src/Sql.ts +41 -123
- package/src/Sqlite.ts +65 -7
package/src/Evolu.ts
CHANGED
|
@@ -32,7 +32,12 @@ import { SqliteBoolean, SqliteDate } from "./Model.js";
|
|
|
32
32
|
import { OnCompletes, OnCompletesLive } from "./OnCompletes.js";
|
|
33
33
|
import { Owner } from "./Owner.js";
|
|
34
34
|
import { AppState, FlushSync } from "./Platform.js";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
Index,
|
|
37
|
+
SqliteQuery,
|
|
38
|
+
SqliteQueryOptions,
|
|
39
|
+
isSqlMutation,
|
|
40
|
+
} from "./Sqlite.js";
|
|
36
41
|
import { Store, Unsubscribe, makeStore } from "./Store.js";
|
|
37
42
|
import { SyncState } from "./SyncWorker.js";
|
|
38
43
|
|
|
@@ -297,6 +302,7 @@ export interface Evolu<S extends DatabaseSchema = DatabaseSchema> {
|
|
|
297
302
|
*/
|
|
298
303
|
readonly ensureSchema: <From, To extends S>(
|
|
299
304
|
schema: S.Schema<To, From>,
|
|
305
|
+
indexes?: ReadonlyArray<Index>,
|
|
300
306
|
) => void;
|
|
301
307
|
|
|
302
308
|
/**
|
|
@@ -313,6 +319,7 @@ export const Evolu = Context.GenericTag<Evolu>("@services/Evolu");
|
|
|
313
319
|
|
|
314
320
|
type CreateQuery<S extends DatabaseSchema> = <R extends Row>(
|
|
315
321
|
queryCallback: QueryCallback<S, R>,
|
|
322
|
+
options?: SqliteQueryOptions,
|
|
316
323
|
) => Query<R>;
|
|
317
324
|
|
|
318
325
|
type QueryCallback<S extends DatabaseSchema, R extends Row> = (
|
|
@@ -334,6 +341,7 @@ type NullableExceptForIdAndAutomaticColumns<T> = {
|
|
|
334
341
|
: T[K] | null;
|
|
335
342
|
};
|
|
336
343
|
|
|
344
|
+
// https://kysely.dev/docs/recipes/splitting-query-building-and-execution
|
|
337
345
|
const kysely = new Kysely.Kysely<QuerySchema<DatabaseSchema>>({
|
|
338
346
|
dialect: {
|
|
339
347
|
createAdapter: (): Kysely.DialectAdapter => new Kysely.SqliteAdapter(),
|
|
@@ -346,18 +354,29 @@ const kysely = new Kysely.Kysely<QuerySchema<DatabaseSchema>>({
|
|
|
346
354
|
},
|
|
347
355
|
});
|
|
348
356
|
|
|
357
|
+
export const createIndex = kysely.schema.createIndex.bind(kysely.schema);
|
|
358
|
+
|
|
349
359
|
export const makeCreateQuery =
|
|
350
360
|
<S extends DatabaseSchema = DatabaseSchema>(): CreateQuery<S> =>
|
|
351
|
-
<R extends Row>(
|
|
361
|
+
<R extends Row>(
|
|
362
|
+
queryCallback: QueryCallback<S, R>,
|
|
363
|
+
options?: SqliteQueryOptions,
|
|
364
|
+
) =>
|
|
352
365
|
pipe(
|
|
353
366
|
queryCallback(kysely as Kysely.Kysely<QuerySchema<S>>).compile(),
|
|
354
|
-
(
|
|
355
|
-
if (isSqlMutation(sql))
|
|
367
|
+
(compiledQuery): SqliteQuery => {
|
|
368
|
+
if (isSqlMutation(compiledQuery.sql))
|
|
356
369
|
throw new Error(
|
|
357
370
|
"SQL mutation (INSERT, UPDATE, DELETE, etc.) isn't allowed in the Evolu `createQuery` function. Kysely suggests it because there is no read-only Kysely yet, and removing such an API is not possible. For mutations, use Evolu mutation API.",
|
|
358
371
|
);
|
|
359
|
-
|
|
360
|
-
|
|
372
|
+
const parameters = compiledQuery.parameters as NonNullable<
|
|
373
|
+
SqliteQuery["parameters"]
|
|
374
|
+
>;
|
|
375
|
+
return {
|
|
376
|
+
sql: compiledQuery.sql,
|
|
377
|
+
parameters,
|
|
378
|
+
...(options && { options }),
|
|
379
|
+
};
|
|
361
380
|
},
|
|
362
381
|
(query) => serializeQuery<R>(query),
|
|
363
382
|
);
|
|
@@ -739,7 +758,6 @@ const EvoluCommon = Layer.effect(
|
|
|
739
758
|
};
|
|
740
759
|
|
|
741
760
|
appState.init({ onRequestSync: sync });
|
|
742
|
-
|
|
743
761
|
sync();
|
|
744
762
|
|
|
745
763
|
return Evolu.of({
|
|
@@ -770,10 +788,11 @@ const EvoluCommon = Layer.effect(
|
|
|
770
788
|
restoreOwner: (mnemonic) =>
|
|
771
789
|
dbWorker.postMessage({ _tag: "reset", mnemonic }),
|
|
772
790
|
|
|
773
|
-
ensureSchema: (schema) =>
|
|
791
|
+
ensureSchema: (schema, indexes = []) =>
|
|
774
792
|
dbWorker.postMessage({
|
|
775
793
|
_tag: "ensureSchema",
|
|
776
794
|
tables: schemaToTables(schema),
|
|
795
|
+
indexes,
|
|
777
796
|
}),
|
|
778
797
|
|
|
779
798
|
sync,
|
|
@@ -815,6 +834,14 @@ export const makeCreateEvolu =
|
|
|
815
834
|
Effect.runSync,
|
|
816
835
|
),
|
|
817
836
|
);
|
|
818
|
-
|
|
837
|
+
|
|
838
|
+
const indexes = config?.indexes?.map(
|
|
839
|
+
(index): Index => ({
|
|
840
|
+
name: index.toOperationNode().name.name,
|
|
841
|
+
sql: index.compile().sql,
|
|
842
|
+
}),
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
evolu.ensureSchema(schema, indexes);
|
|
819
846
|
return evolu as Evolu<To>;
|
|
820
847
|
};
|
package/src/Model.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import * as S from "@effect/schema/Schema";
|
|
2
|
-
import * as Brand from "effect/Brand";
|
|
3
2
|
import { maybeJson } from "./Sqlite.js";
|
|
4
3
|
|
|
5
|
-
/**
|
|
6
|
-
* Branded Id Schema for any table Id. To create Id Schema for a specific table,
|
|
7
|
-
* use {@link id}.
|
|
8
|
-
*/
|
|
4
|
+
/** Branded Id Schema. To create Id Schema for a specific table, use {@link id}. */
|
|
9
5
|
export const Id = S.string.pipe(S.pattern(/^[\w-]{21}$/), S.brand("Id"));
|
|
10
|
-
export type Id = S.Schema.
|
|
6
|
+
export type Id = S.Schema.Type<typeof Id>;
|
|
11
7
|
|
|
12
8
|
/**
|
|
13
9
|
* A factory function to create {@link Id} Schema for a specific table.
|
|
@@ -17,11 +13,11 @@ export type Id = S.Schema.To<typeof Id>;
|
|
|
17
13
|
* import { id } from "@evolu/react";
|
|
18
14
|
*
|
|
19
15
|
* const TodoId = id("Todo");
|
|
20
|
-
* type TodoId = S.Schema.
|
|
16
|
+
* type TodoId = S.Schema.Type<typeof TodoId>;
|
|
21
17
|
*/
|
|
22
18
|
export const id = <T extends string>(
|
|
23
19
|
table: T,
|
|
24
|
-
): S.
|
|
20
|
+
): S.brand<S.brand<S.Schema<string, string, never>, "Id">, T> =>
|
|
25
21
|
Id.pipe(S.brand(table));
|
|
26
22
|
|
|
27
23
|
/**
|
|
@@ -33,7 +29,7 @@ export const SqliteDate = S.string.pipe(
|
|
|
33
29
|
S.filter((s) => !isNaN(Date.parse(s))),
|
|
34
30
|
S.brand("SqliteDate"),
|
|
35
31
|
);
|
|
36
|
-
export type SqliteDate = S.Schema.
|
|
32
|
+
export type SqliteDate = S.Schema.Type<typeof SqliteDate>;
|
|
37
33
|
|
|
38
34
|
/**
|
|
39
35
|
* SQLite doesn't support the boolean type, so Evolu uses SqliteBoolean instead.
|
|
@@ -45,7 +41,7 @@ export const SqliteBoolean = S.number.pipe(
|
|
|
45
41
|
S.filter((s) => s === 0 || s === 1),
|
|
46
42
|
S.brand("SqliteBoolean"),
|
|
47
43
|
);
|
|
48
|
-
export type SqliteBoolean = S.Schema.
|
|
44
|
+
export type SqliteBoolean = S.Schema.Type<typeof SqliteBoolean>;
|
|
49
45
|
|
|
50
46
|
/**
|
|
51
47
|
* A helper for casting types not supported by SQLite. SQLite doesn't support
|
|
@@ -96,7 +92,7 @@ export const String = S.string.pipe(
|
|
|
96
92
|
),
|
|
97
93
|
S.brand("String"),
|
|
98
94
|
);
|
|
99
|
-
export type String = S.Schema.
|
|
95
|
+
export type String = S.Schema.Type<typeof String>;
|
|
100
96
|
|
|
101
97
|
/**
|
|
102
98
|
* A string with a maximum length of 1000 characters.
|
|
@@ -108,7 +104,7 @@ export type String = S.Schema.To<typeof String>;
|
|
|
108
104
|
* S.decode(String1000)(value);
|
|
109
105
|
*/
|
|
110
106
|
export const String1000 = String.pipe(S.maxLength(1000), S.brand("String1000"));
|
|
111
|
-
export type String1000 = S.Schema.
|
|
107
|
+
export type String1000 = S.Schema.Type<typeof String1000>;
|
|
112
108
|
|
|
113
109
|
/**
|
|
114
110
|
* A nonempty string with a maximum length of 1000 characters.
|
|
@@ -124,7 +120,7 @@ export const NonEmptyString1000 = String.pipe(
|
|
|
124
120
|
S.maxLength(1000),
|
|
125
121
|
S.brand("NonEmptyString1000"),
|
|
126
122
|
);
|
|
127
|
-
export type NonEmptyString1000 = S.Schema.
|
|
123
|
+
export type NonEmptyString1000 = S.Schema.Type<typeof NonEmptyString1000>;
|
|
128
124
|
|
|
129
125
|
/**
|
|
130
126
|
* A positive integer.
|
|
@@ -140,4 +136,4 @@ export const PositiveInt = S.number.pipe(
|
|
|
140
136
|
S.positive(),
|
|
141
137
|
S.brand("PositiveInt"),
|
|
142
138
|
);
|
|
143
|
-
export type PositiveInt = S.Schema.
|
|
139
|
+
export type PositiveInt = S.Schema.Type<typeof PositiveInt>;
|
package/src/Public.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { sql } from "kysely";
|
|
1
2
|
export type { NotNull } from "kysely";
|
|
2
3
|
export { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
|
|
3
4
|
export type { Timestamp, TimestampError } from "./Crdt.js";
|
|
@@ -5,6 +6,7 @@ export type { InvalidMnemonicError, Mnemonic } from "./Crypto.js";
|
|
|
5
6
|
export { database, table } from "./Db.js";
|
|
6
7
|
export type { ExtractRow, QueryResult } from "./Db.js";
|
|
7
8
|
export type { EvoluError, UnexpectedError } from "./ErrorStore.js";
|
|
9
|
+
export { createIndex } from "./Evolu.js";
|
|
8
10
|
export * from "./Model.js";
|
|
9
11
|
export type { Owner, OwnerId } from "./Owner.js";
|
|
10
12
|
export { canUseDom } from "./Platform.js";
|
package/src/Sql.ts
CHANGED
|
@@ -1,123 +1,41 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
export const insertOwner = `
|
|
43
|
-
insert into
|
|
44
|
-
"evolu_owner" (
|
|
45
|
-
"id",
|
|
46
|
-
"mnemonic",
|
|
47
|
-
"encryptionKey",
|
|
48
|
-
"timestamp",
|
|
49
|
-
"merkleTree"
|
|
50
|
-
)
|
|
51
|
-
values
|
|
52
|
-
(?, ?, ?, ?, ?);
|
|
53
|
-
`.trim();
|
|
54
|
-
|
|
55
|
-
export const selectOwnerTimestampAndMerkleTree = `
|
|
56
|
-
select
|
|
57
|
-
"timestamp",
|
|
58
|
-
"merkleTree"
|
|
59
|
-
from
|
|
60
|
-
"evolu_owner"
|
|
61
|
-
`.trim();
|
|
62
|
-
|
|
63
|
-
export const selectLastTimestampForTableRowColumn = `
|
|
64
|
-
select
|
|
65
|
-
"timestamp"
|
|
66
|
-
from
|
|
67
|
-
"evolu_message"
|
|
68
|
-
where
|
|
69
|
-
"table" = ?
|
|
70
|
-
and "row" = ?
|
|
71
|
-
and "column" = ?
|
|
72
|
-
order by
|
|
73
|
-
"timestamp" desc
|
|
74
|
-
limit
|
|
75
|
-
1
|
|
76
|
-
`.trim();
|
|
77
|
-
|
|
78
|
-
export const upsertValueIntoTableRowColumn = (
|
|
79
|
-
table: string,
|
|
80
|
-
column: string,
|
|
81
|
-
): string =>
|
|
82
|
-
`
|
|
83
|
-
insert into
|
|
84
|
-
"${table}" ("id", "${column}", "createdAt", "updatedAt")
|
|
85
|
-
values
|
|
86
|
-
(?, ?, ?, ?)
|
|
87
|
-
on conflict do update set
|
|
88
|
-
"${column}" = ?,
|
|
89
|
-
"updatedAt" = ?
|
|
90
|
-
`.trim();
|
|
91
|
-
|
|
92
|
-
export const deleteTableRow = (table: string): string =>
|
|
93
|
-
`
|
|
94
|
-
delete from "${table}"
|
|
95
|
-
where
|
|
96
|
-
"id" = ?;
|
|
97
|
-
`.trim();
|
|
98
|
-
|
|
99
|
-
export const insertIntoMessagesIfNew = `
|
|
100
|
-
insert into
|
|
101
|
-
"evolu_message" ("timestamp", "table", "row", "column", "value")
|
|
102
|
-
values
|
|
103
|
-
(?, ?, ?, ?, ?)
|
|
104
|
-
on conflict do nothing
|
|
105
|
-
`.trim();
|
|
106
|
-
|
|
107
|
-
export const updateOwnerTimestampAndMerkleTree = `
|
|
108
|
-
update "evolu_owner"
|
|
109
|
-
set
|
|
110
|
-
"timestamp" = ?,
|
|
111
|
-
"merkleTree" = ?
|
|
112
|
-
`.trim();
|
|
113
|
-
|
|
114
|
-
export const selectMessagesToSync = `
|
|
115
|
-
select
|
|
116
|
-
*
|
|
117
|
-
from
|
|
118
|
-
"evolu_message"
|
|
119
|
-
where
|
|
120
|
-
"timestamp" >= ?
|
|
121
|
-
order by
|
|
122
|
-
"timestamp"
|
|
123
|
-
`.trim();
|
|
1
|
+
// this file is generated by generateSql script
|
|
2
|
+
import { SqliteQuery } from "./Sqlite.js";
|
|
3
|
+
export const selectOwner: SqliteQuery = {
|
|
4
|
+
sql: `select "id", "mnemonic", "encryptionKey" from "evolu_owner"`,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const createMessageTable: SqliteQuery = {
|
|
8
|
+
sql: `create table "evolu_message" ("timestamp" blob primary key, "table" blob, "row" blob, "column" blob, "value" blob)`,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const createMessageTableIndex: SqliteQuery = {
|
|
12
|
+
sql: `create index "index_evolu_message" on "evolu_message" ("table", "row", "column", "timestamp" desc)`,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const createOwnerTable: SqliteQuery = {
|
|
16
|
+
sql: `create table "evolu_owner" ("id" blob, "mnemonic" blob, "encryptionKey" blob, "timestamp" blob, "merkleTree" blob)`,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const insertOwner: SqliteQuery = {
|
|
20
|
+
sql: `insert into "evolu_owner" ("id", "mnemonic", "encryptionKey", "timestamp", "merkleTree") values (?, ?, ?, ?, ?)`,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const selectOwnerTimestampAndMerkleTree: SqliteQuery = {
|
|
24
|
+
sql: `select "timestamp", "merkleTree" from "evolu_owner"`,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const selectLastTimestampForTableRowColumn: SqliteQuery = {
|
|
28
|
+
sql: `select "timestamp" from "evolu_message" where "table" = ? and "row" = ? and "column" = ? order by "timestamp" desc limit ?`,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const insertIntoMessagesIfNew: SqliteQuery = {
|
|
32
|
+
sql: `insert into "evolu_message" ("timestamp", "table", "row", "column", "value") values (?, ?, ?, ?, ?) on conflict do nothing`,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const updateOwnerTimestampAndMerkleTree: SqliteQuery = {
|
|
36
|
+
sql: `update "evolu_owner" set "merkleTree" = ?, "timestamp" = ?`,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const selectMessagesToSync: SqliteQuery = {
|
|
40
|
+
sql: `select * from "evolu_message" where "timestamp" >= ? order by "timestamp"`,
|
|
41
|
+
};
|
package/src/Sqlite.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import * as Context from "effect/Context";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
|
+
import { Equivalence } from "effect/Equivalence";
|
|
3
4
|
import * as Predicate from "effect/Predicate";
|
|
4
5
|
|
|
5
6
|
export interface Sqlite {
|
|
6
|
-
readonly exec: (
|
|
7
|
+
readonly exec: (query: SqliteQuery) => Effect.Effect<SqliteExecResult>;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export const Sqlite = Context.GenericTag<Sqlite>("@services/Sqlite");
|
|
10
11
|
|
|
11
12
|
export interface SqliteQuery {
|
|
12
13
|
readonly sql: string;
|
|
13
|
-
readonly parameters
|
|
14
|
+
readonly parameters?: Value[];
|
|
15
|
+
readonly options?: SqliteQueryOptions;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SqliteQueryOptions {
|
|
19
|
+
readonly logQueryExecutionTime?: boolean;
|
|
20
|
+
/** https://www.sqlite.org/eqp.html */
|
|
21
|
+
readonly logExplainQueryPlan?: boolean;
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
export type Value = SqliteValue | JsonObjectOrArray;
|
|
@@ -41,11 +49,6 @@ export const valuesToSqliteValues = (
|
|
|
41
49
|
isJsonObjectOrArray(value) ? JSON.stringify(value) : value,
|
|
42
50
|
);
|
|
43
51
|
|
|
44
|
-
export const ensureSqliteQuery = (arg: string | SqliteQuery): SqliteQuery => {
|
|
45
|
-
if (typeof arg !== "string") return arg;
|
|
46
|
-
return { sql: arg, parameters: [] };
|
|
47
|
-
};
|
|
48
|
-
|
|
49
52
|
export const maybeParseJson = (rows: SqliteRow[]): SqliteRow[] =>
|
|
50
53
|
parseArray(rows);
|
|
51
54
|
|
|
@@ -96,3 +99,58 @@ const isSqlMutationRegEx = new RegExp(
|
|
|
96
99
|
|
|
97
100
|
export const isSqlMutation = (sql: string): boolean =>
|
|
98
101
|
isSqlMutationRegEx.test(sql);
|
|
102
|
+
|
|
103
|
+
export const maybeLogSqliteQueryExecutionTime =
|
|
104
|
+
(query: SqliteQuery) =>
|
|
105
|
+
<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> => {
|
|
106
|
+
if (!query.options?.logQueryExecutionTime) return effect;
|
|
107
|
+
return effect.pipe(
|
|
108
|
+
Effect.tap(() => Effect.log("QueryExecutionTime")),
|
|
109
|
+
// Not using Effect.log because of formating
|
|
110
|
+
// eslint-disable-next-line no-console
|
|
111
|
+
Effect.tap(() => console.log(query.sql)),
|
|
112
|
+
Effect.withLogSpan("duration"),
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type SqliteQueryPlanRow = {
|
|
117
|
+
id: number;
|
|
118
|
+
parent: number;
|
|
119
|
+
detail: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const drawSqliteQueryPlan = (rows: SqliteQueryPlanRow[]): string =>
|
|
123
|
+
rows
|
|
124
|
+
.map((row) => {
|
|
125
|
+
let parentId = row.parent;
|
|
126
|
+
let indent = 0;
|
|
127
|
+
|
|
128
|
+
do {
|
|
129
|
+
const parent = rows.find((r) => r.id === parentId);
|
|
130
|
+
if (!parent) break;
|
|
131
|
+
parentId = parent.parent;
|
|
132
|
+
indent++;
|
|
133
|
+
// eslint-disable-next-line no-constant-condition
|
|
134
|
+
} while (true);
|
|
135
|
+
|
|
136
|
+
return `${" ".repeat(indent)}${row.detail}`;
|
|
137
|
+
})
|
|
138
|
+
.join("\n");
|
|
139
|
+
|
|
140
|
+
export interface SqliteSchema {
|
|
141
|
+
readonly tables: ReadonlyArray<Table>;
|
|
142
|
+
readonly indexes: ReadonlyArray<Index>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface Table {
|
|
146
|
+
readonly name: string;
|
|
147
|
+
readonly columns: ReadonlyArray<string>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface Index {
|
|
151
|
+
readonly name: string;
|
|
152
|
+
readonly sql: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const indexEquivalence: Equivalence<Index> = (self, that) =>
|
|
156
|
+
self.name === that.name && self.sql === that.sql;
|