@evolu/common 4.0.5 → 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/src/Sql.ts CHANGED
@@ -1,123 +1,41 @@
1
- // TODO: This file should be generated from a script via Kysely.
2
- // The reason for not using Kysely directly is bundle size.
3
- // [Playground Link](https://kyse.link/?p=b&i=haFkpnNxbGl0ZaF2pjAuMjYuMKFz2gFoaW1wb3J0IHsgR2VuZXJhdGVkIH0gZnJvbSAna3lzZWx5JwoKZGVjbGFyZSBnbG9iYWwgewogIGludGVyZmFjZSBEQiB7CiAgICBldm9sdV9tZXNzYWdlOiB7CiAgICAgIHRpbWVzdGFtcDogc3RyaW5nLAogICAgICB0YWJsZTogc3RyaW5nLAogICAgICByb3c6IHN0cmluZywKICAgICAgY29sdW1uOiBzdHJpbmcsCiAgICAgIHZhbHVlOiB1bmtub3duCiAgICB9LAoKICAgIGV2b2x1X293bmVyOiB7CiAgICAgIGlkOiBzdHJpbmcKICAgICAgbW5lbW9uaWM6IHN0cmluZwogICAgICBlbmNyeXB0aW9uS2V5OiBVaW50OEFycmF5LAogICAgICB0aW1lc3RhbXA6IHN0cmluZywKICAgICAgbWVya2xlVHJlZTogc3RyaW5nCiAgICB9CiAgfQp9oXHaBnhhd2FpdCBreXNlbHkuc2VsZWN0RnJvbSgiZXZvbHVfb3duZXIiKQogIC5zZWxlY3QoWyJpZCIsICJtbmVtb25pYyIsICJlbmNyeXB0aW9uS2V5Il0pCiAgLmV4ZWN1dGUoKQoKYXdhaXQga3lzZWx5LnNjaGVtYQogIC5jcmVhdGVUYWJsZSgnZXZvbHVfbWVzc2FnZScpCiAgLmFkZENvbHVtbigndGltZXN0YW1wJywgJ2Jsb2InLCBjb2wgPT4gY29sLnByaW1hcnlLZXkoKSkKICAuYWRkQ29sdW1uKCd0YWJsZScsICdibG9iJykKICAuYWRkQ29sdW1uKCdyb3cnLCAnYmxvYicpCiAgLmFkZENvbHVtbignY29sdW1uJywgJ2Jsb2InKQogIC5hZGRDb2x1bW4oJ3ZhbHVlJywgJ2Jsb2InKQogIC5leGVjdXRlKCkKCmF3YWl0IGt5c2VseS5zY2hlbWEKICAuY3JlYXRlSW5kZXgoImluZGV4X2V2b2x1X21lc3NhZ2UiKQogIC5vbigiZXZvbHVfbWVzc2FnZSIpCiAgLmNvbHVtbnMoWyJ0YWJsZSIsICJyb3ciLCAiY29sdW1uIiwgInRpbWVzdGFtcCJdKQogIC5leGVjdXRlKCkKCmF3YWl0IGt5c2VseS5zY2hlbWEKICAuY3JlYXRlVGFibGUoJ2V2b2x1X19vd25lcicpCiAgLmFkZENvbHVtbignaWQnLCAnYmxvYicpCiAgLmFkZENvbHVtbignbW5lbW9uaWMnLCAnYmxvYicpCiAgLmFkZENvbHVtbignZW5jcnlwdGlvbktleScsICdibG9iJykKICAuYWRkQ29sdW1uKCd0aW1lc3RhbXAnLCAnYmxvYicpCiAgLmFkZENvbHVtbignbWVya2xlVHJlZScsICdibG9iJykKICAuZXhlY3V0ZSgpCgphd2FpdCBreXNlbHkuaW5zZXJ0SW50bygiZXZvbHVfb3duZXIiKQogIC52YWx1ZXMoewogICAgImlkIjogImIiLAogICAgIm1uZW1vbmljIjogImEiLAogICAgImVuY3J5cHRpb25LZXkiOiBuZXcgVWludDhBcnJheSgpLAogICAgInRpbWVzdGFtcCI6ICJhIiwKICAgICJtZXJrbGVUcmVlIjogImIiCiAgfSkKICAuZXhlY3V0ZSgpCgphd2FpdCBreXNlbHkuc2VsZWN0RnJvbSgiZXZvbHVfb3duZXIiKQogIC5zZWxlY3QoWyJ0aW1lc3RhbXAiLCAibWVya2xlVHJlZSJdKQogIC5leGVjdXRlKCkKCmF3YWl0IGt5c2VseS5zZWxlY3RGcm9tKCJldm9sdV9tZXNzYWdlIikKICAuc2VsZWN0KCJ0aW1lc3RhbXAiKQogIC53aGVyZSgndGFibGUnLCAnPScsICcxJykKICAud2hlcmUoJ3JvdycsICc9JywgJzInKQogIC53aGVyZSgnY29sdW1uJywgJz0nLCAnMycpCiAgLm9yZGVyQnkoInRpbWVzdGFtcCIsICJkZXNjIikKICAubGltaXQoMSkKICAuZXhlY3V0ZVRha2VGaXJzdCgpCgphd2FpdCBreXNlbHkuaW5zZXJ0SW50bygiZXZvbHVfbWVzc2FnZSIpCiAgLnZhbHVlcyh7CiAgICAidGltZXN0YW1wIjogJzEnLAogICAgInRhYmxlIjogJzInLAogICAgInJvdyI6ICczJywKICAgICJjb2x1bW4iOiAnNCcsCiAgICAidmFsdWUiOiAnNScsCiAgfSkKICAub25Db25mbGljdChvYyA9PiBvYy5kb05vdGhpbmcoKSkKICAuZXhlY3V0ZSgpCgphd2FpdCBreXNlbHkudXBkYXRlVGFibGUoImV2b2x1X293bmVyIikKICAuc2V0KHsKICAgICJtZXJrbGVUcmVlIjogJzEnLAogICAgInRpbWVzdGFtcCI6ICIyIgogIH0pCiAgLmV4ZWN1dGUoKQoKYXdhaXQga3lzZWx5LnNlbGVjdEZyb20oImV2b2x1X21lc3NhZ2UiKQogIC5zZWxlY3RBbGwoKQogIC53aGVyZSgidGltZXN0YW1wIiwgIj49IiwgJzEnKQogIC5vcmRlckJ5KCJ0aW1lc3RhbXAiKQogIC5leGVjdXRlKCmhY8M=)
4
-
5
- export const selectOwner = `
6
- select
7
- "id",
8
- "mnemonic",
9
- "encryptionKey"
10
- from
11
- "evolu_owner"
12
- `.trim();
13
-
14
- export const createMessageTable = `
15
- create table
16
- "evolu_message" (
17
- "timestamp" blob primary key,
18
- "table" blob,
19
- "row" blob,
20
- "column" blob,
21
- "value" blob
22
- );
23
- `.trim();
24
-
25
- export const createMessageTableIndex = `
26
- create index "index_evolu_message" on "evolu_message" (
27
- "table", "row", "column", "timestamp"
28
- );
29
- `.trim();
30
-
31
- export const createOwnerTable = `
32
- create table
33
- "evolu_owner" (
34
- "id" blob,
35
- "mnemonic" blob,
36
- "encryptionKey" blob,
37
- "timestamp" blob,
38
- "merkleTree" blob
39
- );
40
- `.trim();
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: (arg: string | SqliteQuery) => Effect.Effect<SqliteExecResult>;
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: Value[];
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;