@evolu/common 5.4.8 → 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.
- package/README.md +30 -34
- package/dist/src/Array.d.ts +17 -0
- package/dist/src/Array.d.ts.map +1 -0
- package/dist/src/Array.js +12 -0
- package/dist/src/Assert.d.ts +68 -0
- package/dist/src/Assert.d.ts.map +1 -0
- package/dist/src/Assert.js +77 -0
- package/dist/src/BigInt.d.ts +20 -0
- package/dist/src/BigInt.d.ts.map +1 -0
- package/dist/src/BigInt.js +18 -0
- package/dist/src/Buffer.d.ts +92 -0
- package/dist/src/Buffer.d.ts.map +1 -0
- package/dist/src/Buffer.js +62 -0
- package/dist/src/Callbacks.d.ts +20 -0
- package/dist/src/Callbacks.d.ts.map +1 -0
- package/dist/src/Callbacks.js +18 -0
- package/dist/src/Console.d.ts +78 -0
- package/dist/src/Console.d.ts.map +1 -0
- package/dist/src/Console.js +103 -0
- package/dist/src/Crypto.d.ts +72 -39
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +89 -54
- package/dist/src/Eq.d.ts +97 -0
- package/dist/src/Eq.d.ts.map +1 -0
- package/dist/src/Eq.js +167 -0
- package/dist/src/Error.d.ts +14 -10
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +43 -11
- package/dist/src/Evolu/Config.d.ts +69 -0
- package/dist/src/Evolu/Config.d.ts.map +1 -0
- package/dist/src/Evolu/Config.js +9 -0
- package/dist/src/Evolu/Db.d.ts +126 -0
- package/dist/src/Evolu/Db.d.ts.map +1 -0
- package/dist/src/Evolu/Db.js +774 -0
- package/dist/src/Evolu/Diff.d.ts +43 -0
- package/dist/src/Evolu/Diff.d.ts.map +1 -0
- package/dist/src/Evolu/Diff.js +95 -0
- package/dist/src/Evolu/Evolu.d.ts +334 -0
- package/dist/src/Evolu/Evolu.d.ts.map +1 -0
- package/dist/src/Evolu/Evolu.js +434 -0
- package/dist/src/Evolu/Internal.d.ts +26 -0
- package/dist/src/Evolu/Internal.d.ts.map +1 -0
- package/dist/src/Evolu/Internal.js +25 -0
- package/dist/src/Evolu/Kysely.d.ts +6 -0
- package/dist/src/Evolu/Kysely.d.ts.map +1 -0
- package/dist/src/Evolu/Kysely.js +21 -0
- package/dist/src/Evolu/Owner.d.ts +155 -0
- package/dist/src/Evolu/Owner.d.ts.map +1 -0
- package/dist/src/Evolu/Owner.js +126 -0
- package/dist/src/Evolu/Platform.d.ts +23 -0
- package/dist/src/Evolu/Platform.d.ts.map +1 -0
- package/dist/src/Evolu/Platform.js +1 -0
- package/dist/src/Evolu/Protocol.d.ts +401 -0
- package/dist/src/Evolu/Protocol.d.ts.map +1 -0
- package/dist/src/Evolu/Protocol.js +1151 -0
- package/dist/src/Evolu/Public.d.ts +18 -0
- package/dist/src/Evolu/Public.d.ts.map +1 -0
- package/dist/src/Evolu/Public.js +11 -0
- package/dist/src/Evolu/PublicKysely.d.ts +148 -0
- package/dist/src/Evolu/PublicKysely.d.ts.map +1 -0
- package/dist/src/Evolu/PublicKysely.js +185 -0
- package/dist/src/Evolu/Query.d.ts +63 -0
- package/dist/src/Evolu/Query.d.ts.map +1 -0
- package/dist/src/Evolu/Query.js +61 -0
- package/dist/src/Evolu/Relay.d.ts +13 -0
- package/dist/src/Evolu/Relay.d.ts.map +1 -0
- package/dist/src/Evolu/Relay.js +109 -0
- package/dist/src/Evolu/Schema.d.ts +201 -0
- package/dist/src/Evolu/Schema.d.ts.map +1 -0
- package/dist/src/Evolu/Schema.js +150 -0
- package/dist/src/Evolu/Storage.d.ts +49 -0
- package/dist/src/Evolu/Storage.d.ts.map +1 -0
- package/dist/src/Evolu/Storage.js +1111 -0
- package/dist/src/Evolu/Sync.d.ts +59 -0
- package/dist/src/Evolu/Sync.d.ts.map +1 -0
- package/dist/src/Evolu/Sync.js +29 -0
- package/dist/src/Evolu/Timestamp.d.ts +106 -0
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -0
- package/dist/src/Evolu/Timestamp.js +179 -0
- package/dist/src/Function.d.ts +54 -0
- package/dist/src/Function.d.ts.map +1 -0
- package/dist/src/Function.js +38 -0
- package/dist/src/ManyToManyMap.d.ts +26 -0
- package/dist/src/ManyToManyMap.d.ts.map +1 -0
- package/dist/src/ManyToManyMap.js +92 -0
- package/dist/src/NanoId.d.ts +27 -0
- package/dist/src/NanoId.d.ts.map +1 -0
- package/dist/src/NanoId.js +6 -0
- package/dist/src/Number.d.ts +42 -0
- package/dist/src/Number.d.ts.map +1 -0
- package/dist/src/Number.js +55 -0
- package/dist/src/Object.d.ts +35 -0
- package/dist/src/Object.d.ts.map +1 -0
- package/dist/src/Object.js +36 -0
- package/dist/src/Order.d.ts +90 -0
- package/dist/src/Order.d.ts.map +1 -0
- package/dist/src/Order.js +85 -0
- package/dist/src/Promise.d.ts +180 -0
- package/dist/src/Promise.d.ts.map +1 -0
- package/dist/src/Promise.js +176 -0
- package/dist/src/Random.d.ts +52 -0
- package/dist/src/Random.d.ts.map +1 -0
- package/dist/src/Random.js +29 -0
- package/dist/src/Ref.d.ts +40 -0
- package/dist/src/Ref.d.ts.map +1 -0
- package/dist/src/Ref.js +13 -0
- package/dist/src/Result.d.ts +421 -0
- package/dist/src/Result.d.ts.map +1 -0
- package/dist/src/Result.js +357 -0
- package/dist/src/Skiplist.d.ts +23 -0
- package/dist/src/Skiplist.d.ts.map +1 -0
- package/dist/src/Skiplist.js +58 -0
- package/dist/src/Sqlite.d.ts +116 -52
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +183 -67
- package/dist/src/Store.d.ts +45 -8
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +33 -17
- package/dist/src/String.d.ts +2 -0
- package/dist/src/String.d.ts.map +1 -0
- package/dist/src/String.js +14 -0
- package/dist/src/Time.d.ts +20 -0
- package/dist/src/Time.d.ts.map +1 -0
- package/dist/src/Time.js +25 -0
- package/dist/src/Type.d.ts +1937 -0
- package/dist/src/Type.d.ts.map +1 -0
- package/dist/src/Type.js +2002 -0
- package/dist/src/Types.d.ts +188 -0
- package/dist/src/Types.d.ts.map +1 -0
- package/dist/src/Types.js +6 -0
- package/dist/src/WebSocket.d.ts +112 -0
- package/dist/src/WebSocket.d.ts.map +1 -0
- package/dist/src/WebSocket.js +139 -0
- package/dist/src/Worker.d.ts +44 -0
- package/dist/src/Worker.d.ts.map +1 -0
- package/dist/src/Worker.js +66 -0
- package/dist/src/index.d.ts +24 -11
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +24 -11
- package/package.json +29 -38
- package/src/Array.ts +39 -0
- package/src/Assert.ts +116 -0
- package/src/BigInt.ts +29 -0
- package/src/Buffer.ts +175 -0
- package/src/Callbacks.ts +43 -0
- package/src/Console.ts +159 -0
- package/src/Crypto.ts +169 -115
- package/src/Eq.ts +204 -0
- package/src/Error.ts +57 -20
- package/src/Evolu/Config.ts +83 -0
- package/src/Evolu/Db.ts +1275 -0
- package/src/Evolu/Diff.ts +142 -0
- package/src/Evolu/Evolu.ts +947 -0
- package/src/Evolu/Internal.ts +26 -0
- package/src/Evolu/Kysely.ts +38 -0
- package/src/Evolu/Owner.ts +296 -0
- package/src/Evolu/Platform.ts +27 -0
- package/src/Evolu/Protocol.ts +1857 -0
- package/src/Evolu/Public.ts +43 -0
- package/src/Evolu/PublicKysely.ts +240 -0
- package/src/Evolu/Query.ts +167 -0
- package/src/Evolu/Relay.ts +142 -0
- package/src/Evolu/Schema.ts +417 -0
- package/src/Evolu/Storage.ts +1281 -0
- package/src/Evolu/Sync.ts +105 -0
- package/src/Evolu/Timestamp.ts +311 -0
- package/src/Function.ts +58 -0
- package/src/ManyToManyMap.ts +140 -0
- package/src/NanoId.ts +39 -0
- package/src/Number.ts +90 -0
- package/src/Object.ts +64 -0
- package/src/Order.ts +113 -0
- package/src/Promise.ts +295 -0
- package/src/Random.ts +68 -0
- package/src/Ref.ts +63 -0
- package/src/Result.ts +453 -0
- package/src/Skiplist.ts +102 -0
- package/src/Sqlite.ts +366 -153
- package/src/Store.ts +79 -36
- package/src/String.ts +10 -0
- package/src/Time.ts +36 -0
- package/src/Type.ts +3978 -0
- package/src/Types.ts +209 -0
- package/src/WebSocket.ts +273 -0
- package/src/Worker.ts +129 -0
- package/src/index.ts +24 -11
- package/dist/src/Config.d.ts +0 -56
- package/dist/src/Config.d.ts.map +0 -1
- package/dist/src/Config.js +0 -39
- package/dist/src/Crdt.d.ts +0 -89
- package/dist/src/Crdt.d.ts.map +0 -1
- package/dist/src/Crdt.js +0 -181
- package/dist/src/Db.d.ts +0 -107
- package/dist/src/Db.d.ts.map +0 -1
- package/dist/src/Db.js +0 -443
- package/dist/src/Diff.d.ts +0 -27
- package/dist/src/Diff.d.ts.map +0 -1
- package/dist/src/Diff.js +0 -84
- package/dist/src/Evolu.d.ts +0 -426
- package/dist/src/Evolu.d.ts.map +0 -1
- package/dist/src/Evolu.js +0 -333
- package/dist/src/Model.d.ts +0 -141
- package/dist/src/Model.d.ts.map +0 -1
- package/dist/src/Model.js +0 -125
- package/dist/src/Murmurhash.d.ts +0 -2
- package/dist/src/Murmurhash.d.ts.map +0 -1
- package/dist/src/Murmurhash.js +0 -60
- package/dist/src/Owner.d.ts +0 -33
- package/dist/src/Owner.d.ts.map +0 -1
- package/dist/src/Owner.js +0 -26
- package/dist/src/Platform.d.ts +0 -37
- package/dist/src/Platform.d.ts.map +0 -1
- package/dist/src/Platform.js +0 -11
- package/dist/src/Protobuf.d.ts +0 -81
- package/dist/src/Protobuf.d.ts.map +0 -1
- package/dist/src/Protobuf.js +0 -92
- package/dist/src/Public.d.ts +0 -13
- package/dist/src/Public.d.ts.map +0 -1
- package/dist/src/Public.js +0 -6
- package/dist/src/Socket.d.ts +0 -8
- package/dist/src/Socket.d.ts.map +0 -1
- package/dist/src/Socket.js +0 -51
- package/dist/src/Sql.d.ts +0 -12
- package/dist/src/Sql.d.ts.map +0 -1
- package/dist/src/Sql.js +0 -30
- package/dist/src/Sync.d.ts +0 -70
- package/dist/src/Sync.d.ts.map +0 -1
- package/dist/src/Sync.js +0 -127
- package/src/Config.ts +0 -119
- package/src/Crdt.ts +0 -361
- package/src/Db.ts +0 -955
- package/src/Diff.ts +0 -114
- package/src/Evolu.ts +0 -1016
- package/src/Model.ts +0 -233
- package/src/Murmurhash.ts +0 -70
- package/src/Owner.ts +0 -69
- package/src/Platform.ts +0 -47
- package/src/Protobuf.ts +0 -155
- package/src/Public.ts +0 -12
- package/src/Socket.ts +0 -83
- package/src/Sql.ts +0 -41
- package/src/Sync.ts +0 -315
package/src/Sqlite.ts
CHANGED
|
@@ -1,166 +1,353 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* wipe-out.
|
|
24
|
-
*/
|
|
25
|
-
mode: SqliteTransactionMode,
|
|
26
|
-
) => <A, E, R>(
|
|
27
|
-
effect: Effect.Effect<A, E, R>,
|
|
28
|
-
) => Effect.Effect<A, E, Sqlite | R>;
|
|
29
|
-
|
|
30
|
-
readonly export: () => Effect.Effect<Uint8Array>;
|
|
1
|
+
import { ConsoleDep } from "./Console.js";
|
|
2
|
+
import { createTransferableError, TransferableError } from "./Error.js";
|
|
3
|
+
import { err, ok, Result, tryAsync, trySync } from "./Result.js";
|
|
4
|
+
import {
|
|
5
|
+
Boolean,
|
|
6
|
+
Null,
|
|
7
|
+
Number,
|
|
8
|
+
SimpleName,
|
|
9
|
+
String,
|
|
10
|
+
transform,
|
|
11
|
+
Uint8Array,
|
|
12
|
+
union,
|
|
13
|
+
} from "./Type.js";
|
|
14
|
+
import { Brand, Predicate, IntentionalNever } from "./Types.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* SQLite driver interface. This is the minimal interface that platform-specific
|
|
18
|
+
* drivers must implement.
|
|
19
|
+
*/
|
|
20
|
+
export interface SqliteDriver extends Disposable {
|
|
21
|
+
readonly exec: (query: SqliteQuery, isMutation: boolean) => SqliteExecResult;
|
|
22
|
+
readonly export: () => Uint8Array;
|
|
31
23
|
}
|
|
32
24
|
|
|
33
|
-
export
|
|
25
|
+
export type CreateSqliteDriver = (
|
|
26
|
+
name: SimpleName,
|
|
27
|
+
options?: SqliteDriverOptions,
|
|
28
|
+
) => Promise<SqliteDriver>;
|
|
34
29
|
|
|
35
|
-
export
|
|
30
|
+
export interface CreateSqliteDriverDep {
|
|
31
|
+
readonly createSqliteDriver: CreateSqliteDriver;
|
|
32
|
+
}
|
|
36
33
|
|
|
37
|
-
export
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Effect.acquireUseRelease(
|
|
70
|
-
sqlite.exec({ sql: "begin" }),
|
|
71
|
-
() => effect,
|
|
72
|
-
(_, exit) =>
|
|
73
|
-
Exit.isFailure(exit)
|
|
74
|
-
? sqlite.exec({ sql: "rollback" })
|
|
75
|
-
: sqlite.exec({ sql: "commit" }),
|
|
76
|
-
),
|
|
77
|
-
).pipe(platformSqlite.transaction(mode));
|
|
78
|
-
},
|
|
79
|
-
}),
|
|
80
|
-
),
|
|
81
|
-
),
|
|
82
|
-
})),
|
|
83
|
-
);
|
|
34
|
+
export interface SqliteDriverOptions {
|
|
35
|
+
memory?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Cross-platform SQLite abstraction.
|
|
40
|
+
*
|
|
41
|
+
* This API is sync only because SQLite is an embedded, single-threaded engine.
|
|
42
|
+
* All operations are blocking and in-process, so async APIs add needless
|
|
43
|
+
* complexity without any real benefit and are also slower. Check better-sqlite3
|
|
44
|
+
* GitHub issues and docs for details.
|
|
45
|
+
*/
|
|
46
|
+
export interface Sqlite extends Disposable {
|
|
47
|
+
readonly exec: <R extends SqliteRow = SqliteRow>(
|
|
48
|
+
query: SqliteQuery,
|
|
49
|
+
) => Result<SqliteExecResult<R>, SqliteError>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Executes a transaction, running the provided callback within a begin/commit
|
|
53
|
+
* block. If the callback returns an error (E or {@link SqliteError}), the
|
|
54
|
+
* transaction is rolled back. If the rollback fails, a SqliteError is
|
|
55
|
+
* returned with both the original error and rollbackError.
|
|
56
|
+
*/
|
|
57
|
+
readonly transaction: <T, E>(
|
|
58
|
+
callback: () => Result<T, E | SqliteError>,
|
|
59
|
+
) => Result<T, E | SqliteError>;
|
|
60
|
+
|
|
61
|
+
readonly export: () => Result<Uint8Array, SqliteError>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface SqliteDep {
|
|
65
|
+
readonly sqlite: Sqlite;
|
|
84
66
|
}
|
|
85
67
|
|
|
86
68
|
export interface SqliteQuery {
|
|
87
|
-
readonly sql:
|
|
88
|
-
readonly parameters
|
|
69
|
+
readonly sql: SafeSql;
|
|
70
|
+
readonly parameters: Array<SqliteValue>;
|
|
89
71
|
readonly options?: SqliteQueryOptions;
|
|
90
72
|
}
|
|
91
73
|
|
|
74
|
+
/** A type representing a sanitized SQL string. */
|
|
75
|
+
export type SafeSql = string & Brand<"TimestampString">;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A value that can be stored in Sqlite.
|
|
79
|
+
*
|
|
80
|
+
* Note that Evolu can't support Int64 because expo-sqlite (and some others) do
|
|
81
|
+
* not support it.
|
|
82
|
+
*/
|
|
83
|
+
export const SqliteValue = union(Null, String, Number, Uint8Array);
|
|
84
|
+
export type SqliteValue = typeof SqliteValue.Type;
|
|
85
|
+
|
|
92
86
|
export interface SqliteQueryOptions {
|
|
87
|
+
/**
|
|
88
|
+
* If set to `true`, logs the time taken to execute the SQL query. Useful for
|
|
89
|
+
* performance monitoring and identifying slow queries.
|
|
90
|
+
*/
|
|
93
91
|
readonly logQueryExecutionTime?: boolean;
|
|
94
|
-
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* If set to `true`, logs the SQLite Explain Query Plan (EQP) for the query.
|
|
95
|
+
* This can help analyze how SQLite plans to execute the query and identify
|
|
96
|
+
* potential optimizations.
|
|
97
|
+
*
|
|
98
|
+
* See: {@link https://www.sqlite.org/eqp.html}.
|
|
99
|
+
*/
|
|
95
100
|
readonly logExplainQueryPlan?: boolean;
|
|
96
|
-
}
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
/**
|
|
103
|
+
* If set to `true`, explicitly prepares the query before execution. Prepared
|
|
104
|
+
* statements can improve performance for repeated queries by reusing the
|
|
105
|
+
* compiled query.
|
|
106
|
+
*
|
|
107
|
+
* See: {@link https://sqlite.org/wasm/doc/trunk/api-oo1.md#db-prepare}.
|
|
108
|
+
*/
|
|
109
|
+
readonly prepare?: boolean;
|
|
110
|
+
}
|
|
105
111
|
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
/** Result of executing a SQLite query. */
|
|
113
|
+
export interface SqliteExecResult<R extends SqliteRow = SqliteRow> {
|
|
114
|
+
readonly rows: ReadonlyArray<R>;
|
|
108
115
|
readonly changes: number;
|
|
109
116
|
}
|
|
110
117
|
|
|
118
|
+
/** Represents an error that occurred during a SQLite operation. */
|
|
119
|
+
export interface SqliteError {
|
|
120
|
+
readonly type: "SqliteError";
|
|
121
|
+
readonly error: TransferableError;
|
|
122
|
+
readonly rollbackError?: TransferableError;
|
|
123
|
+
}
|
|
124
|
+
|
|
111
125
|
export type SqliteRow = Record<string, SqliteValue>;
|
|
112
126
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Creates a fully featured {@link Sqlite} instance from a {@link SqliteDriver}
|
|
129
|
+
* implementation.
|
|
130
|
+
*/
|
|
131
|
+
export const createSqlite =
|
|
132
|
+
(deps: CreateSqliteDriverDep & Partial<ConsoleDep>) =>
|
|
133
|
+
async (
|
|
134
|
+
name: SimpleName,
|
|
135
|
+
options?: SqliteDriverOptions,
|
|
136
|
+
): Promise<Result<Sqlite, SqliteError>> =>
|
|
137
|
+
tryAsync(async () => {
|
|
138
|
+
const driver = await deps.createSqliteDriver(name, options);
|
|
139
|
+
let isDisposed = false;
|
|
140
|
+
|
|
141
|
+
const doRollback = () =>
|
|
142
|
+
trySync(() => {
|
|
143
|
+
deps.console?.log("[sql] rollback");
|
|
144
|
+
driver.exec(sql`rollback;`, true);
|
|
145
|
+
}, createSqliteError);
|
|
146
|
+
|
|
147
|
+
const sqlite: Sqlite = {
|
|
148
|
+
exec: (query) =>
|
|
149
|
+
trySync(
|
|
150
|
+
() => {
|
|
151
|
+
deps.console?.log("[sql]", query);
|
|
152
|
+
|
|
153
|
+
const result = maybeLogSqliteQueryExecutionTime(query, () =>
|
|
154
|
+
driver.exec(query, isSqlMutation(query.sql)),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
deps.console?.log("[sql]", result);
|
|
158
|
+
|
|
159
|
+
return result as IntentionalNever;
|
|
160
|
+
},
|
|
161
|
+
(error): SqliteError => ({
|
|
162
|
+
type: "SqliteError",
|
|
163
|
+
error: createTransferableError(error),
|
|
164
|
+
}),
|
|
165
|
+
),
|
|
166
|
+
|
|
167
|
+
transaction: (callback) => {
|
|
168
|
+
const transactionResult = trySync(() => {
|
|
169
|
+
deps.console?.log("[sql] begin");
|
|
170
|
+
driver.exec(sql`begin;`, true);
|
|
171
|
+
|
|
172
|
+
const result = callback();
|
|
173
|
+
if (!result.ok) return result;
|
|
174
|
+
|
|
175
|
+
deps.console?.log("[sql] commit");
|
|
176
|
+
driver.exec(sql`commit;`, true);
|
|
177
|
+
|
|
178
|
+
return result;
|
|
179
|
+
}, createSqliteError);
|
|
180
|
+
|
|
181
|
+
// There was an SqliteError during begin, callback, or commit
|
|
182
|
+
if (!transactionResult.ok) {
|
|
183
|
+
const rollback = doRollback();
|
|
184
|
+
if (!rollback.ok) {
|
|
185
|
+
deps.console?.log("[sql] rollback failed", rollback.error);
|
|
186
|
+
return err({
|
|
187
|
+
type: "SqliteError",
|
|
188
|
+
error: transactionResult.error.error,
|
|
189
|
+
rollbackError: rollback.error.error,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return transactionResult;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Callback returned an error
|
|
196
|
+
if (!transactionResult.value.ok) {
|
|
197
|
+
const rollback = doRollback();
|
|
198
|
+
if (!rollback.ok) {
|
|
199
|
+
deps.console?.log("[sql] rollback failed", rollback.error);
|
|
200
|
+
return err({
|
|
201
|
+
type: "SqliteError",
|
|
202
|
+
error: createTransferableError(transactionResult.value.error),
|
|
203
|
+
rollbackError: rollback.error.error,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return transactionResult.value;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return ok(transactionResult.value.value);
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
export: () =>
|
|
213
|
+
trySync(
|
|
214
|
+
() => {
|
|
215
|
+
return driver.export();
|
|
216
|
+
},
|
|
217
|
+
(error): SqliteError => ({
|
|
218
|
+
type: "SqliteError",
|
|
219
|
+
error: createTransferableError(error),
|
|
220
|
+
}),
|
|
221
|
+
),
|
|
130
222
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
223
|
+
[Symbol.dispose]: () => {
|
|
224
|
+
if (isDisposed) return;
|
|
225
|
+
isDisposed = true;
|
|
226
|
+
driver[Symbol.dispose]();
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return sqlite;
|
|
231
|
+
}, createSqliteError);
|
|
232
|
+
|
|
233
|
+
const createSqliteError = (error: unknown): SqliteError => ({
|
|
234
|
+
type: "SqliteError",
|
|
235
|
+
error: createTransferableError(error),
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const maybeLogSqliteQueryExecutionTime = <T>(
|
|
239
|
+
query: SqliteQuery,
|
|
240
|
+
callback: () => T,
|
|
241
|
+
): T => {
|
|
242
|
+
if (!query.options?.logQueryExecutionTime) {
|
|
243
|
+
return callback();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const start = performance.now();
|
|
247
|
+
const result = callback();
|
|
248
|
+
const elapsed = performance.now() - start;
|
|
249
|
+
|
|
250
|
+
// eslint-disable-next-line no-console
|
|
251
|
+
console.log(`SqliteQueryExecutionTime: ${elapsed.toString()}ms`, query);
|
|
252
|
+
|
|
253
|
+
return result;
|
|
134
254
|
};
|
|
135
255
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
256
|
+
export interface PreparedStatements<P> extends Disposable {
|
|
257
|
+
readonly get: <T extends boolean>(
|
|
258
|
+
query: SqliteQuery,
|
|
259
|
+
alwaysPrepare?: T,
|
|
260
|
+
) => T extends true ? P : P | null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export const createPreparedStatementsCache = <P>(
|
|
264
|
+
factory: (sql: SafeSql) => P,
|
|
265
|
+
disposeFn: (statement: P) => void,
|
|
266
|
+
): PreparedStatements<P> => {
|
|
267
|
+
let isDisposed = false;
|
|
268
|
+
const cache = new Map<SafeSql, P>();
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
get: (query, alwaysPrepare) => {
|
|
272
|
+
if (alwaysPrepare !== true && !query.options?.prepare)
|
|
273
|
+
return null as IntentionalNever;
|
|
274
|
+
let statement = cache.get(query.sql);
|
|
275
|
+
if (!statement) {
|
|
276
|
+
statement = factory(query.sql);
|
|
277
|
+
cache.set(query.sql, statement);
|
|
278
|
+
}
|
|
279
|
+
return statement as IntentionalNever;
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
[Symbol.dispose]: () => {
|
|
283
|
+
if (isDisposed) return;
|
|
284
|
+
isDisposed = true;
|
|
285
|
+
cache.forEach(disposeFn);
|
|
286
|
+
cache.clear();
|
|
287
|
+
},
|
|
288
|
+
};
|
|
143
289
|
};
|
|
144
290
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
291
|
+
export interface SqlIdentifier {
|
|
292
|
+
type: "SqlIdentifier";
|
|
293
|
+
sql: SafeSql;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface RawSql {
|
|
297
|
+
type: "RawSql";
|
|
298
|
+
sql: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export type SqlTemplateParam = SqliteValue | SqlIdentifier | RawSql;
|
|
302
|
+
|
|
303
|
+
/** TODO: Docs. */
|
|
304
|
+
export const sql = (
|
|
305
|
+
strings: TemplateStringsArray,
|
|
306
|
+
...parameters: Array<SqlTemplateParam>
|
|
307
|
+
): SqliteQuery => {
|
|
308
|
+
let sql = "";
|
|
309
|
+
const values: Array<SqliteValue> = [];
|
|
310
|
+
|
|
311
|
+
for (let i = 0; i < strings.length; i++) {
|
|
312
|
+
sql += strings[i];
|
|
313
|
+
if (i < parameters.length) {
|
|
314
|
+
const param = parameters[i];
|
|
315
|
+
if (typeof param === "object" && param != null && "type" in param) {
|
|
316
|
+
sql += param.sql;
|
|
317
|
+
} else {
|
|
318
|
+
sql += "?";
|
|
319
|
+
values.push(param);
|
|
320
|
+
}
|
|
151
321
|
}
|
|
152
|
-
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return { sql: sql as SafeSql, parameters: values };
|
|
153
325
|
};
|
|
154
326
|
|
|
155
|
-
|
|
156
|
-
|
|
327
|
+
sql.identifier = (identifier: string): SqlIdentifier => ({
|
|
328
|
+
type: "SqlIdentifier",
|
|
329
|
+
// From Kysely
|
|
330
|
+
sql: `"${identifier.replace(/"/g, '""')}"` as SafeSql,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Insert any string.
|
|
335
|
+
*
|
|
336
|
+
* Sql.raw sometimes breaks auto-formatting because the parser does not consider
|
|
337
|
+
* it valid SQL. A workaround is to remove it, format it, and put it back.
|
|
338
|
+
*
|
|
339
|
+
* **Warning**: This is not escaped.
|
|
340
|
+
*/
|
|
341
|
+
sql.raw = (raw: string): RawSql => ({ type: "RawSql", sql: raw });
|
|
157
342
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
343
|
+
sql.prepared = (
|
|
344
|
+
strings: TemplateStringsArray,
|
|
345
|
+
...parameters: Array<SqlTemplateParam>
|
|
346
|
+
): SqliteQuery => {
|
|
347
|
+
const query = sql(strings, ...parameters);
|
|
348
|
+
return { ...query, options: { prepare: true } };
|
|
161
349
|
};
|
|
162
350
|
|
|
163
|
-
/** This is good enough detection because SQL strings in Evolu are predictable. */
|
|
164
351
|
const isSqlMutationRegEx = new RegExp(
|
|
165
352
|
`\\b(${[
|
|
166
353
|
"alter",
|
|
@@ -170,34 +357,45 @@ const isSqlMutationRegEx = new RegExp(
|
|
|
170
357
|
"insert",
|
|
171
358
|
"replace",
|
|
172
359
|
"update",
|
|
360
|
+
"begin",
|
|
361
|
+
"commit",
|
|
362
|
+
"rollback",
|
|
363
|
+
"pragma",
|
|
364
|
+
"vacuum",
|
|
173
365
|
].join("|")})\\b`,
|
|
366
|
+
"i",
|
|
174
367
|
);
|
|
175
368
|
|
|
176
|
-
export const isSqlMutation = (sql
|
|
177
|
-
isSqlMutationRegEx.test(sql);
|
|
178
|
-
|
|
179
|
-
export const maybeLogSqliteQueryExecutionTime =
|
|
180
|
-
(query: SqliteQuery) =>
|
|
181
|
-
<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> => {
|
|
182
|
-
if (!query.options?.logQueryExecutionTime) return effect;
|
|
183
|
-
return Effect.Do.pipe(
|
|
184
|
-
Effect.let("start", () => performance.now()),
|
|
185
|
-
Effect.bind("result", () => effect),
|
|
186
|
-
Effect.let("elapsed", ({ start }) => performance.now() - start),
|
|
187
|
-
Effect.tap(({ elapsed }) =>
|
|
188
|
-
Console.log(`QueryExecutionTime: ${elapsed}ms`, query),
|
|
189
|
-
),
|
|
190
|
-
Effect.map(({ result }) => result),
|
|
191
|
-
);
|
|
192
|
-
};
|
|
369
|
+
export const isSqlMutation: Predicate<string> = (sql) =>
|
|
370
|
+
isSqlMutationRegEx.test(sql.replace(/--.*$/gm, ""));
|
|
193
371
|
|
|
194
|
-
export
|
|
372
|
+
export interface SqliteQueryPlanRow {
|
|
195
373
|
id: number;
|
|
196
374
|
parent: number;
|
|
197
375
|
detail: string;
|
|
198
|
-
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export const explainSqliteQueryPlan =
|
|
379
|
+
(deps: SqliteDep) =>
|
|
380
|
+
(query: SqliteQuery): Result<void, SqliteError> => {
|
|
381
|
+
const result = deps.sqlite.exec({
|
|
382
|
+
...query,
|
|
383
|
+
sql: `EXPLAIN QUERY PLAN ${query.sql}` as SafeSql,
|
|
384
|
+
});
|
|
385
|
+
if (!result.ok) return result;
|
|
386
|
+
|
|
387
|
+
// eslint-disable-next-line no-console
|
|
388
|
+
console.log("ExplainQueryPlan", query);
|
|
389
|
+
// eslint-disable-next-line no-console
|
|
390
|
+
console.log(
|
|
391
|
+
drawSqliteQueryPlan(
|
|
392
|
+
result.value.rows as unknown as Array<SqliteQueryPlanRow>,
|
|
393
|
+
),
|
|
394
|
+
);
|
|
395
|
+
return ok();
|
|
396
|
+
};
|
|
199
397
|
|
|
200
|
-
|
|
398
|
+
const drawSqliteQueryPlan = (rows: Array<SqliteQueryPlanRow>): string =>
|
|
201
399
|
rows
|
|
202
400
|
.map((row) => {
|
|
203
401
|
let parentId = row.parent;
|
|
@@ -208,9 +406,24 @@ export const drawSqliteQueryPlan = (rows: SqliteQueryPlanRow[]): string =>
|
|
|
208
406
|
if (!parent) break;
|
|
209
407
|
parentId = parent.parent;
|
|
210
408
|
indent++;
|
|
211
|
-
// eslint-disable-next-line no-constant-condition
|
|
409
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
|
|
212
410
|
} while (true);
|
|
213
411
|
|
|
214
412
|
return `${" ".repeat(indent)}${row.detail}`;
|
|
215
413
|
})
|
|
216
414
|
.join("\n");
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* SQLite represents boolean values using `0` (false) and `1` (true) instead of
|
|
418
|
+
* a dedicated boolean type. This transform Type ensures that values conform to
|
|
419
|
+
* SQLite's boolean representation.
|
|
420
|
+
*
|
|
421
|
+
* See: https://www.sqlite.org/quirks.html#no_separate_boolean_datatype
|
|
422
|
+
*/
|
|
423
|
+
export const SqliteBoolean = transform(
|
|
424
|
+
Boolean,
|
|
425
|
+
union(0, 1),
|
|
426
|
+
(value) => ok(value ? (1 as const) : (0 as const)),
|
|
427
|
+
(value) => value === 1,
|
|
428
|
+
);
|
|
429
|
+
export type SqliteBoolean = typeof SqliteBoolean.Type;
|