@evolu/common 7.4.0 → 8.0.0-next.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 +4 -61
- package/dist/src/Array.d.ts +343 -102
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +181 -85
- package/dist/src/Assert.d.ts +57 -11
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +47 -11
- package/dist/src/BigInt.d.ts +6 -1
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -0
- package/dist/src/Brand.d.ts +40 -12
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Brand.js +5 -0
- package/dist/src/Buffer.d.ts +6 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +7 -2
- package/dist/src/Cache.d.ts +10 -2
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +6 -0
- package/dist/src/Callbacks.d.ts +10 -7
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +23 -13
- package/dist/src/Console.d.ts +361 -69
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +217 -117
- package/dist/src/Crypto.d.ts +70 -21
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +72 -27
- package/dist/src/Eq.d.ts +22 -2
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +21 -1
- package/dist/src/Error.d.ts +23 -12
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +27 -11
- package/dist/src/Function.d.ts +128 -56
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +118 -14
- package/dist/src/Identicon.d.ts +5 -0
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +6 -1
- package/dist/src/Lookup.d.ts +160 -0
- package/dist/src/Lookup.d.ts.map +1 -0
- package/dist/src/Lookup.js +192 -0
- package/dist/src/Microtask.d.ts +21 -0
- package/dist/src/Microtask.d.ts.map +1 -0
- package/dist/src/Microtask.js +37 -0
- package/dist/src/Number.d.ts +18 -4
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +23 -1
- package/dist/src/Object.d.ts +110 -5
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +108 -5
- package/dist/src/Option.d.ts +58 -0
- package/dist/src/Option.d.ts.map +1 -0
- package/dist/src/Option.js +43 -0
- package/dist/src/Order.d.ts +5 -0
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +8 -3
- package/dist/src/Platform.d.ts +36 -2
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +10 -6
- package/dist/src/Polyfills.d.ts +27 -0
- package/dist/src/Polyfills.d.ts.map +1 -0
- package/dist/src/Polyfills.js +299 -0
- package/dist/src/Random.d.ts +18 -7
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +9 -7
- package/dist/src/Redacted.d.ts +7 -4
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +5 -0
- package/dist/src/Ref.d.ts +31 -16
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +35 -7
- package/dist/src/RefCount.d.ts +62 -0
- package/dist/src/RefCount.d.ts.map +1 -0
- package/dist/src/RefCount.js +83 -0
- package/dist/src/Relation.d.ts +84 -29
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +83 -105
- package/dist/src/Resource.d.ts +263 -0
- package/dist/src/Resource.d.ts.map +1 -0
- package/dist/src/Resource.js +389 -0
- package/dist/src/Result.d.ts +390 -374
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +142 -70
- package/dist/src/Schedule.d.ts +953 -0
- package/dist/src/Schedule.d.ts.map +1 -0
- package/dist/src/Schedule.js +1199 -0
- package/dist/src/Set.d.ts +181 -0
- package/dist/src/Set.d.ts.map +1 -0
- package/dist/src/Set.js +137 -0
- package/dist/src/Skiplist.d.ts +6 -1
- package/dist/src/Skiplist.d.ts.map +1 -1
- package/dist/src/Skiplist.js +5 -0
- package/dist/src/Sqlite.d.ts +160 -60
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +290 -190
- package/dist/src/Store.d.ts +36 -28
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +55 -15
- package/dist/src/String.d.ts +5 -0
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +5 -0
- package/dist/src/Task.d.ts +2263 -423
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1488 -348
- package/dist/src/Test.d.ts +122 -0
- package/dist/src/Test.d.ts.map +1 -0
- package/dist/src/Test.js +66 -0
- package/dist/src/Time.d.ts +201 -48
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +190 -86
- package/dist/src/Tracer.d.ts +48 -0
- package/dist/src/Tracer.d.ts.map +1 -0
- package/dist/src/Tracer.js +6 -0
- package/dist/src/Type.d.ts +582 -209
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +362 -240
- package/dist/src/Types.d.ts +160 -6
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +22 -2
- package/dist/src/WebSocket.d.ts +113 -58
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +210 -118
- package/dist/src/Worker.d.ts +245 -60
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +220 -64
- package/dist/src/index.d.ts +25 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19 -3
- package/dist/src/local-first/Db.d.ts +24 -213
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +622 -293
- package/dist/src/local-first/Error.d.ts +12 -0
- package/dist/src/local-first/Error.d.ts.map +1 -0
- package/dist/src/local-first/Error.js +6 -0
- package/dist/src/local-first/Evolu.d.ts +330 -285
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +472 -402
- package/dist/src/local-first/LocalAuth.d.ts +10 -10
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/LocalAuth.js +6 -1
- package/dist/src/local-first/Owner.d.ts +70 -27
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +27 -14
- package/dist/src/local-first/Protocol.d.ts +58 -50
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +253 -155
- package/dist/src/local-first/Query.d.ts +133 -43
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +139 -90
- package/dist/src/local-first/Relay.d.ts +18 -38
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +59 -171
- package/dist/src/local-first/Schema.d.ts +139 -144
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +78 -163
- package/dist/src/local-first/Shared.d.ts +125 -0
- package/dist/src/local-first/Shared.d.ts.map +1 -0
- package/dist/src/local-first/Shared.js +569 -0
- package/dist/src/local-first/Storage.d.ts +50 -53
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +121 -173
- package/dist/src/local-first/Timestamp.d.ts +28 -35
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +24 -25
- package/dist/src/local-first/index.d.ts +2 -15
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +2 -15
- package/package.json +36 -26
- package/src/Array.ts +467 -106
- package/src/Assert.ts +87 -12
- package/src/BigInt.ts +7 -1
- package/src/Brand.ts +41 -12
- package/src/Buffer.ts +10 -4
- package/src/Cache.ts +11 -2
- package/src/Callbacks.ts +33 -23
- package/src/Console.ts +567 -178
- package/src/Crypto.ts +111 -62
- package/src/Eq.ts +30 -3
- package/src/Error.ts +50 -34
- package/src/Function.ts +140 -68
- package/src/Identicon.ts +7 -1
- package/src/Lookup.ts +415 -0
- package/src/Microtask.ts +58 -0
- package/src/Number.ts +40 -7
- package/src/Object.ts +155 -16
- package/src/Option.ts +74 -0
- package/src/Order.ts +11 -3
- package/src/Platform.ts +43 -7
- package/src/Polyfills.ts +465 -0
- package/src/Random.ts +25 -11
- package/src/Redacted.ts +8 -4
- package/src/Ref.ts +72 -21
- package/src/RefCount.ts +170 -0
- package/src/Relation.ts +188 -136
- package/src/Resource.ts +864 -0
- package/src/Result.ts +548 -381
- package/src/Schedule.ts +1471 -0
- package/src/Set.ts +247 -0
- package/src/Skiplist.ts +7 -1
- package/src/Sqlite.ts +409 -304
- package/src/Store.ts +94 -48
- package/src/String.ts +6 -0
- package/src/Task.ts +3685 -740
- package/src/Test.ts +162 -0
- package/src/Time.ts +348 -145
- package/src/Tracer.ts +54 -0
- package/src/Type.ts +1064 -630
- package/src/Types.ts +202 -7
- package/src/WebSocket.ts +311 -178
- package/src/Worker.ts +527 -147
- package/src/index.ts +74 -3
- package/src/local-first/Db.ts +871 -597
- package/src/local-first/Error.ts +17 -0
- package/src/local-first/Evolu.ts +884 -823
- package/src/local-first/LocalAuth.ts +10 -10
- package/src/local-first/Owner.ts +94 -42
- package/src/local-first/Protocol.ts +440 -303
- package/src/local-first/Query.ts +243 -194
- package/src/local-first/Relay.ts +91 -252
- package/src/local-first/Schema.ts +284 -394
- package/src/local-first/Shared.ts +932 -0
- package/src/local-first/Storage.ts +209 -282
- package/src/local-first/Timestamp.ts +38 -45
- package/src/local-first/index.ts +2 -16
- package/LICENSE +0 -21
- package/dist/src/Instances.d.ts +0 -34
- package/dist/src/Instances.d.ts.map +0 -1
- package/dist/src/Instances.js +0 -44
- package/dist/src/Resources.d.ts +0 -118
- package/dist/src/Resources.d.ts.map +0 -1
- package/dist/src/Resources.js +0 -197
- package/dist/src/local-first/Platform.d.ts +0 -25
- package/dist/src/local-first/Platform.d.ts.map +0 -1
- package/dist/src/local-first/Platform.js +0 -1
- package/dist/src/local-first/Public.d.ts +0 -22
- package/dist/src/local-first/Public.d.ts.map +0 -1
- package/dist/src/local-first/Public.js +0 -15
- package/dist/src/local-first/PublicKysely.d.ts +0 -148
- package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
- package/dist/src/local-first/PublicKysely.js +0 -184
- package/dist/src/local-first/Sync.d.ts +0 -112
- package/dist/src/local-first/Sync.d.ts.map +0 -1
- package/dist/src/local-first/Sync.js +0 -529
- package/src/Instances.ts +0 -90
- package/src/Resources.ts +0 -367
- package/src/local-first/Platform.ts +0 -27
- package/src/local-first/Public.ts +0 -42
- package/src/local-first/PublicKysely.ts +0 -239
- package/src/local-first/Sync.ts +0 -960
package/src/Sqlite.ts
CHANGED
|
@@ -1,102 +1,108 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
1
|
+
/**
|
|
2
|
+
* SQLite database abstraction and query execution.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { assertNotDisposed } from "./Assert.js";
|
|
8
|
+
import type { Brand } from "./Brand.js";
|
|
9
|
+
import { bytesToHex, hexToBytes } from "./Buffer.js";
|
|
10
|
+
import type { EncryptionKey } from "./Crypto.js";
|
|
11
|
+
import type { Eq } from "./Eq.js";
|
|
12
|
+
import { createEqObject, eqArrayNumber, eqString } from "./Eq.js";
|
|
13
|
+
import { createRecord, objectToEntries } from "./Object.js";
|
|
14
|
+
import type { Result } from "./Result.js";
|
|
15
|
+
import { ok } from "./Result.js";
|
|
16
|
+
import type { Run, Task } from "./Task.js";
|
|
17
|
+
import { testCreateRun, type TestDeps } from "./Test.js";
|
|
18
|
+
import type { InferType, Name, Typed } from "./Type.js";
|
|
8
19
|
import {
|
|
20
|
+
array,
|
|
9
21
|
Null,
|
|
10
22
|
Number,
|
|
11
|
-
|
|
12
|
-
|
|
23
|
+
object,
|
|
24
|
+
record,
|
|
25
|
+
set,
|
|
13
26
|
String,
|
|
27
|
+
testName,
|
|
14
28
|
Uint8Array,
|
|
15
29
|
union,
|
|
16
30
|
} from "./Type.js";
|
|
17
|
-
import { IntentionalNever, Predicate } from "./Types.js";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* SQLite driver interface. This is the minimal interface that platform-specific
|
|
21
|
-
* drivers must implement.
|
|
22
|
-
*/
|
|
23
|
-
export interface SqliteDriver extends Disposable {
|
|
24
|
-
readonly exec: (query: SqliteQuery, isMutation: boolean) => SqliteExecResult;
|
|
25
|
-
readonly export: () => Uint8Array;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export type CreateSqliteDriver = (
|
|
29
|
-
name: SimpleName,
|
|
30
|
-
options?: SqliteDriverOptions,
|
|
31
|
-
) => Promise<SqliteDriver>;
|
|
32
|
-
|
|
33
|
-
export interface CreateSqliteDriverDep {
|
|
34
|
-
readonly createSqliteDriver: CreateSqliteDriver;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface SqliteDriverOptions {
|
|
38
|
-
readonly memory?: boolean;
|
|
39
|
-
readonly encryptionKey?: EncryptionKey | undefined;
|
|
40
|
-
}
|
|
41
31
|
|
|
42
32
|
/**
|
|
43
|
-
*
|
|
33
|
+
* Platform-agnostic SQLite wrapping a {@link SqliteDriver}.
|
|
44
34
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* GitHub issues and docs for details.
|
|
35
|
+
* API is synchronous because it provides
|
|
36
|
+
* {@link https://github.com/WiseLibs/better-sqlite3/issues/262 | better concurrency}
|
|
37
|
+
* for SQLite.
|
|
49
38
|
*/
|
|
50
|
-
export interface Sqlite extends
|
|
39
|
+
export interface Sqlite extends AsyncDisposable {
|
|
51
40
|
readonly exec: <R extends SqliteRow = SqliteRow>(
|
|
52
41
|
query: SqliteQuery,
|
|
53
|
-
) =>
|
|
42
|
+
) => SqliteExecResult<R>;
|
|
54
43
|
|
|
55
44
|
/**
|
|
56
45
|
* Executes a transaction, running the provided callback within a begin/commit
|
|
57
|
-
* block.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
46
|
+
* block.
|
|
47
|
+
*
|
|
48
|
+
* If the callback returns a {@link Result} error, the transaction is rolled
|
|
49
|
+
* back and the error result is returned.
|
|
50
|
+
*
|
|
51
|
+
* If the callback returns `void`, the transaction is committed unless the
|
|
52
|
+
* callback throws.
|
|
60
53
|
*/
|
|
61
|
-
readonly transaction:
|
|
62
|
-
callback: () => Result<T, E | SqliteError>,
|
|
63
|
-
) => Result<T, E | SqliteError>;
|
|
54
|
+
readonly transaction: SqliteTransaction;
|
|
64
55
|
|
|
65
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Returns bytes backed by {@link ArrayBuffer}.
|
|
58
|
+
*
|
|
59
|
+
* Exported databases are forwarded through worker `postMessage` transfer
|
|
60
|
+
* lists, which require transferable `ArrayBuffer` backing.
|
|
61
|
+
*/
|
|
62
|
+
readonly export: () => Uint8Array<ArrayBuffer>;
|
|
66
63
|
}
|
|
67
64
|
|
|
68
65
|
export interface SqliteDep {
|
|
69
66
|
readonly sqlite: Sqlite;
|
|
70
67
|
}
|
|
71
68
|
|
|
69
|
+
export interface SqliteTransaction {
|
|
70
|
+
<T, E>(callback: () => Result<T, E>): Result<T, E>;
|
|
71
|
+
(callback: () => void): void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Represents a SQL query to be executed on a {@link Sqlite} database. */
|
|
72
75
|
export interface SqliteQuery {
|
|
73
76
|
readonly sql: SafeSql;
|
|
74
77
|
readonly parameters: Array<SqliteValue>;
|
|
75
78
|
readonly options?: SqliteQueryOptions;
|
|
76
79
|
}
|
|
77
80
|
|
|
78
|
-
/**
|
|
81
|
+
/** Serialized {@link SqliteQuery} used as a stable string key. */
|
|
82
|
+
export type SqliteQueryString = string & Brand<"SqliteQueryString">;
|
|
83
|
+
|
|
84
|
+
/** A sanitized SQL string for {@link SqliteQuery}. */
|
|
79
85
|
export type SafeSql = string & Brand<"SafeSql">;
|
|
80
86
|
|
|
81
87
|
/**
|
|
82
|
-
* A value that can be stored in Sqlite.
|
|
88
|
+
* A value that can be stored in {@link Sqlite}.
|
|
83
89
|
*
|
|
84
90
|
* Note that Evolu can't support Int64 because expo-sqlite (and some others) do
|
|
85
91
|
* not support it.
|
|
86
92
|
*/
|
|
87
|
-
export const SqliteValue = union(
|
|
93
|
+
export const SqliteValue = /*#__PURE__*/ union(
|
|
94
|
+
Null,
|
|
95
|
+
String,
|
|
96
|
+
Number,
|
|
97
|
+
Uint8Array,
|
|
98
|
+
);
|
|
88
99
|
export type SqliteValue = typeof SqliteValue.Type;
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
y instanceof globalThis.Uint8Array
|
|
94
|
-
) {
|
|
95
|
-
return eqArrayNumber(x, y);
|
|
96
|
-
}
|
|
97
|
-
return x === y;
|
|
98
|
-
};
|
|
101
|
+
/** Equality comparison for {@link SqliteValue}. */
|
|
102
|
+
export const eqSqliteValue: Eq<SqliteValue> = (x, y) =>
|
|
103
|
+
Uint8Array.is(x) && Uint8Array.is(y) ? eqArrayNumber(x, y) : x === y;
|
|
99
104
|
|
|
105
|
+
/** Options for configuring {@link SqliteQuery} execution behavior. */
|
|
100
106
|
export interface SqliteQueryOptions {
|
|
101
107
|
/**
|
|
102
108
|
* If set to `true`, logs the time taken to execute the SQL query. Useful for
|
|
@@ -123,150 +129,239 @@ export interface SqliteQueryOptions {
|
|
|
123
129
|
readonly prepare?: boolean;
|
|
124
130
|
}
|
|
125
131
|
|
|
132
|
+
/** Converts a {@link SqliteQuery} into a stable {@link SqliteQueryString}. */
|
|
133
|
+
export const sqliteQueryToSqliteQueryString = (
|
|
134
|
+
query: SqliteQuery,
|
|
135
|
+
): SqliteQueryString => {
|
|
136
|
+
const params = query.parameters.map((value) =>
|
|
137
|
+
value instanceof globalThis.Uint8Array
|
|
138
|
+
? (["b", bytesToHex(value)] as const)
|
|
139
|
+
: (["j", value] as const),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const options = query.options
|
|
143
|
+
? objectToEntries(query.options).toSorted(([a], [b]) => a.localeCompare(b))
|
|
144
|
+
: [];
|
|
145
|
+
|
|
146
|
+
return JSON.stringify([query.sql, params, options]) as SqliteQueryString;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** Converts a {@link SqliteQueryString} back into a {@link SqliteQuery}. */
|
|
150
|
+
export const sqliteQueryStringToSqliteQuery = (
|
|
151
|
+
query: SqliteQueryString,
|
|
152
|
+
): SqliteQuery => {
|
|
153
|
+
const [sql, paramsArr, optionsArr] = JSON.parse(query) as [
|
|
154
|
+
SafeSql,
|
|
155
|
+
Array<readonly ["b", string] | readonly ["j", string | number | null]>,
|
|
156
|
+
Array<Array<string | number | null>>,
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
const parameters = paramsArr.map(([type, value]) =>
|
|
160
|
+
type === "b" ? hexToBytes(value) : value,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const options = optionsArr.length
|
|
164
|
+
? (Object.fromEntries(optionsArr) as SqliteQueryOptions)
|
|
165
|
+
: undefined;
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
sql,
|
|
169
|
+
parameters,
|
|
170
|
+
...(options !== undefined && { options }),
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
126
174
|
/** Result of executing a SQLite query. */
|
|
127
175
|
export interface SqliteExecResult<R extends SqliteRow = SqliteRow> {
|
|
128
176
|
readonly rows: ReadonlyArray<R>;
|
|
129
177
|
readonly changes: number;
|
|
130
178
|
}
|
|
131
179
|
|
|
132
|
-
/**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
180
|
+
/**
|
|
181
|
+
* A row returned from a {@link Sqlite} query, mapping column names to
|
|
182
|
+
* {@link SqliteValue}.
|
|
183
|
+
*/
|
|
184
|
+
export type SqliteRow = Record<string, SqliteValue>;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* SQLite driver interface.
|
|
188
|
+
*
|
|
189
|
+
* Platform-specific drivers must implement this interface.
|
|
190
|
+
*/
|
|
191
|
+
export interface SqliteDriver extends Disposable {
|
|
192
|
+
readonly exec: (query: SqliteQuery) => SqliteExecResult;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Returns bytes backed by {@link ArrayBuffer}.
|
|
196
|
+
*
|
|
197
|
+
* Exported databases are forwarded through worker `postMessage` transfer
|
|
198
|
+
* lists, which require transferable `ArrayBuffer` backing.
|
|
199
|
+
*/
|
|
200
|
+
readonly export: () => Uint8Array<ArrayBuffer>;
|
|
137
201
|
}
|
|
138
202
|
|
|
139
|
-
|
|
203
|
+
/** Creates a {@link SqliteDriver}. */
|
|
204
|
+
export type CreateSqliteDriver = (
|
|
205
|
+
name: Name,
|
|
206
|
+
options?: SqliteDriverOptions,
|
|
207
|
+
) => Task<SqliteDriver>;
|
|
208
|
+
|
|
209
|
+
export interface CreateSqliteDriverDep {
|
|
210
|
+
createSqliteDriver: CreateSqliteDriver;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Options for creating a {@link CreateSqliteDriver}.
|
|
215
|
+
*
|
|
216
|
+
* Three mutually exclusive modes: in-memory (for testing), encrypted persistent
|
|
217
|
+
* (OPFS/file with encryption key), or persistent (default when omitted).
|
|
218
|
+
*/
|
|
219
|
+
export type SqliteDriverOptions =
|
|
220
|
+
| { readonly mode: "memory" }
|
|
221
|
+
| { readonly mode: "encrypted"; readonly encryptionKey: EncryptionKey };
|
|
140
222
|
|
|
141
223
|
/**
|
|
142
|
-
* Creates a
|
|
143
|
-
*
|
|
224
|
+
* Creates a {@link Sqlite} instance backed by a platform-specific driver.
|
|
225
|
+
*
|
|
226
|
+
* The driver is created via {@link CreateSqliteDriver} and wrapped with logging,
|
|
227
|
+
* error handling, and transaction helpers.
|
|
144
228
|
*/
|
|
145
229
|
export const createSqlite =
|
|
146
|
-
(
|
|
147
|
-
|
|
148
|
-
name: SimpleName,
|
|
230
|
+
(
|
|
231
|
+
name: Name,
|
|
149
232
|
options?: SqliteDriverOptions,
|
|
150
|
-
):
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
trySync(
|
|
228
|
-
() => {
|
|
229
|
-
return driver.export();
|
|
230
|
-
},
|
|
231
|
-
(error): SqliteError => ({
|
|
232
|
-
type: "SqliteError",
|
|
233
|
-
error: createTransferableError(error),
|
|
234
|
-
}),
|
|
235
|
-
),
|
|
236
|
-
|
|
237
|
-
[Symbol.dispose]: () => {
|
|
238
|
-
if (isDisposed) return;
|
|
239
|
-
isDisposed = true;
|
|
240
|
-
driver[Symbol.dispose]();
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
return sqlite;
|
|
245
|
-
}, createSqliteError);
|
|
246
|
-
|
|
247
|
-
const createSqliteError = (error: unknown): SqliteError => ({
|
|
248
|
-
type: "SqliteError",
|
|
249
|
-
error: createTransferableError(error),
|
|
250
|
-
});
|
|
233
|
+
): Task<Sqlite, never, CreateSqliteDriverDep> =>
|
|
234
|
+
async (run) => {
|
|
235
|
+
const { createSqliteDriver } = run.deps;
|
|
236
|
+
const console = run.deps.console.child("sql");
|
|
237
|
+
await using stack = new AsyncDisposableStack();
|
|
238
|
+
|
|
239
|
+
const driverResult = await run(createSqliteDriver(name, options));
|
|
240
|
+
if (!driverResult.ok) return driverResult;
|
|
241
|
+
const driver = stack.use(driverResult.value);
|
|
242
|
+
console.debug("SQLite driver created");
|
|
243
|
+
|
|
244
|
+
const moved = stack.move();
|
|
245
|
+
|
|
246
|
+
const sqlite: Sqlite = {
|
|
247
|
+
exec: <R extends SqliteRow = SqliteRow>(query: SqliteQuery) => {
|
|
248
|
+
assertNotDisposed(moved);
|
|
249
|
+
console.debug({ query });
|
|
250
|
+
|
|
251
|
+
const label =
|
|
252
|
+
query.options?.logQueryExecutionTime &&
|
|
253
|
+
`SqliteQueryExecutionTime ${query.sql}`;
|
|
254
|
+
|
|
255
|
+
if (label) console.time(label);
|
|
256
|
+
const result = driver.exec(query);
|
|
257
|
+
if (label) console.timeEnd(label);
|
|
258
|
+
|
|
259
|
+
if (query.options?.logExplainQueryPlan) {
|
|
260
|
+
const result = driver.exec({
|
|
261
|
+
...query,
|
|
262
|
+
sql: `EXPLAIN QUERY PLAN ${query.sql}` as SafeSql,
|
|
263
|
+
});
|
|
264
|
+
console.log("[logExplainQueryPlan]", query);
|
|
265
|
+
console.log(
|
|
266
|
+
drawSqliteQueryPlan(
|
|
267
|
+
result.rows as unknown as Array<SqliteQueryPlanRow>,
|
|
268
|
+
),
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
console.debug({ result });
|
|
273
|
+
return result as SqliteExecResult<R>;
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
transaction: ((callback: () => Result<unknown, unknown> | void) => {
|
|
277
|
+
assertNotDisposed(moved);
|
|
278
|
+
console.debug("begin");
|
|
279
|
+
driver.exec(sql`begin;`);
|
|
280
|
+
|
|
281
|
+
let shouldRollback = true;
|
|
282
|
+
using _rollback = {
|
|
283
|
+
[Symbol.dispose]: () => {
|
|
284
|
+
if (!shouldRollback) return;
|
|
285
|
+
console.debug("rollback");
|
|
286
|
+
driver.exec(sql`rollback;`);
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const result = callback();
|
|
291
|
+
if (result != null && !result.ok) return result;
|
|
292
|
+
|
|
293
|
+
console.debug("commit");
|
|
294
|
+
driver.exec(sql`commit;`);
|
|
295
|
+
shouldRollback = false;
|
|
296
|
+
|
|
297
|
+
return result;
|
|
298
|
+
}) as SqliteTransaction,
|
|
299
|
+
|
|
300
|
+
export: () => {
|
|
301
|
+
assertNotDisposed(moved);
|
|
302
|
+
return driver.export();
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
[Symbol.asyncDispose]: () => moved.disposeAsync(),
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
return ok(sqlite);
|
|
309
|
+
};
|
|
251
310
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
):
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
311
|
+
/** Creates a test setup with a in-memory {@link Sqlite}. */
|
|
312
|
+
export const testSetupSqlite = async (
|
|
313
|
+
deps: CreateSqliteDriverDep,
|
|
314
|
+
): Promise<
|
|
315
|
+
AsyncDisposable &
|
|
316
|
+
SqliteDep & {
|
|
317
|
+
readonly run: Run<TestDeps & CreateSqliteDriverDep & SqliteDep>;
|
|
318
|
+
readonly sqlite: Sqlite;
|
|
319
|
+
}
|
|
320
|
+
> => {
|
|
321
|
+
await using stack = new AsyncDisposableStack();
|
|
322
|
+
const run = stack.use(testCreateRun(deps));
|
|
323
|
+
const sqlite = stack.use(
|
|
324
|
+
await run.orThrow(createSqlite(testName, { mode: "memory" })),
|
|
325
|
+
);
|
|
326
|
+
const moved = stack.move();
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
run: run.addDeps({ sqlite }),
|
|
330
|
+
sqlite,
|
|
331
|
+
[Symbol.asyncDispose]: () => moved.disposeAsync(),
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
interface SqliteQueryPlanRow {
|
|
336
|
+
id: number;
|
|
337
|
+
parent: number;
|
|
338
|
+
detail: string;
|
|
339
|
+
}
|
|
259
340
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
341
|
+
const drawSqliteQueryPlan = (rows: Array<SqliteQueryPlanRow>): string =>
|
|
342
|
+
rows
|
|
343
|
+
.map((row) => {
|
|
344
|
+
let parentId = row.parent;
|
|
345
|
+
let indent = 0;
|
|
263
346
|
|
|
264
|
-
|
|
265
|
-
|
|
347
|
+
do {
|
|
348
|
+
const parent = rows.find((r) => r.id === parentId);
|
|
349
|
+
if (!parent) break;
|
|
350
|
+
parentId = parent.parent;
|
|
351
|
+
indent++;
|
|
352
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
|
|
353
|
+
} while (true);
|
|
266
354
|
|
|
267
|
-
|
|
268
|
-
}
|
|
355
|
+
return `${" ".repeat(indent)}${row.detail}`;
|
|
356
|
+
})
|
|
357
|
+
.join("\n");
|
|
269
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Cache for compiled prepared statements.
|
|
361
|
+
*
|
|
362
|
+
* Statements are created on first access and reused for subsequent calls with
|
|
363
|
+
* the same SQL. Disposing the cache finalizes all cached statements.
|
|
364
|
+
*/
|
|
270
365
|
export interface PreparedStatements<P> extends Disposable {
|
|
271
366
|
readonly get: <T extends boolean>(
|
|
272
367
|
query: SqliteQuery,
|
|
@@ -274,6 +369,10 @@ export interface PreparedStatements<P> extends Disposable {
|
|
|
274
369
|
) => T extends true ? P : P | null;
|
|
275
370
|
}
|
|
276
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Creates a {@link PreparedStatements} cache backed by the given factory and
|
|
374
|
+
* dispose function.
|
|
375
|
+
*/
|
|
277
376
|
export const createPreparedStatementsCache = <P>(
|
|
278
377
|
factory: (sql: SafeSql) => P,
|
|
279
378
|
disposeFn: (statement: P) => void,
|
|
@@ -284,13 +383,13 @@ export const createPreparedStatementsCache = <P>(
|
|
|
284
383
|
return {
|
|
285
384
|
get: (query, alwaysPrepare) => {
|
|
286
385
|
if (alwaysPrepare !== true && !query.options?.prepare)
|
|
287
|
-
return null as
|
|
386
|
+
return null as never;
|
|
288
387
|
let statement = cache.get(query.sql);
|
|
289
388
|
if (!statement) {
|
|
290
389
|
statement = factory(query.sql);
|
|
291
390
|
cache.set(query.sql, statement);
|
|
292
391
|
}
|
|
293
|
-
return statement as
|
|
392
|
+
return statement as never;
|
|
294
393
|
},
|
|
295
394
|
|
|
296
395
|
[Symbol.dispose]: () => {
|
|
@@ -302,16 +401,21 @@ export const createPreparedStatementsCache = <P>(
|
|
|
302
401
|
};
|
|
303
402
|
};
|
|
304
403
|
|
|
305
|
-
|
|
306
|
-
|
|
404
|
+
/** A double-quoted SQL identifier for safe column or table name interpolation. */
|
|
405
|
+
export interface SqlIdentifier extends Typed<"SqlIdentifier"> {
|
|
307
406
|
readonly sql: SafeSql;
|
|
308
407
|
}
|
|
309
408
|
|
|
310
|
-
|
|
311
|
-
|
|
409
|
+
/**
|
|
410
|
+
* An unescaped SQL fragment inserted verbatim into a query.
|
|
411
|
+
*
|
|
412
|
+
* **Warning**: Use only with trusted, constant strings to avoid SQL injection.
|
|
413
|
+
*/
|
|
414
|
+
export interface RawSql extends Typed<"RawSql"> {
|
|
312
415
|
readonly sql: string;
|
|
313
416
|
}
|
|
314
417
|
|
|
418
|
+
/** A parameter accepted by the {@link sql} tagged template. */
|
|
315
419
|
export type SqlTemplateParam = SqliteValue | SqlIdentifier | RawSql;
|
|
316
420
|
|
|
317
421
|
/**
|
|
@@ -346,7 +450,7 @@ export type SqlTemplateParam = SqliteValue | SqlIdentifier | RawSql;
|
|
|
346
450
|
* sqlite.exec(sql`select * from users order by ${sql.raw(orderBy)};`);
|
|
347
451
|
* ```
|
|
348
452
|
*
|
|
349
|
-
*
|
|
453
|
+
* ## TIP
|
|
350
454
|
*
|
|
351
455
|
* Use `prettier-plugin-sql-cst` for SQL formatting. Like Prettier for
|
|
352
456
|
* JavaScript, this plugin formats SQL expressions differently depending on
|
|
@@ -372,7 +476,7 @@ export const sql = (
|
|
|
372
476
|
}
|
|
373
477
|
}
|
|
374
478
|
|
|
375
|
-
return { sql: sql as SafeSql, parameters: values };
|
|
479
|
+
return { sql: sql.trim() as SafeSql, parameters: values };
|
|
376
480
|
};
|
|
377
481
|
|
|
378
482
|
sql.identifier = (identifier: string): SqlIdentifier => ({
|
|
@@ -382,15 +486,14 @@ sql.identifier = (identifier: string): SqlIdentifier => ({
|
|
|
382
486
|
});
|
|
383
487
|
|
|
384
488
|
/**
|
|
385
|
-
* Insert any string.
|
|
386
|
-
*
|
|
387
|
-
* Sql.raw sometimes breaks auto-formatting because the parser does not consider
|
|
388
|
-
* it valid SQL. A workaround is to remove it, format it, and put it back.
|
|
489
|
+
* Insert any string verbatim.
|
|
389
490
|
*
|
|
390
|
-
* **Warning**: This is not escaped.
|
|
491
|
+
* **Warning**: This is not escaped. Use only with trusted, constant SQL
|
|
492
|
+
* fragments to avoid SQL injection.
|
|
391
493
|
*/
|
|
392
494
|
sql.raw = (raw: string): RawSql => ({ type: "RawSql", sql: raw });
|
|
393
495
|
|
|
496
|
+
/** Tagged template that creates a {@link SqliteQuery} with `prepare: true`. */
|
|
394
497
|
sql.prepared = (
|
|
395
498
|
strings: TemplateStringsArray,
|
|
396
499
|
...parameters: Array<SqlTemplateParam>
|
|
@@ -399,124 +502,126 @@ sql.prepared = (
|
|
|
399
502
|
return { ...query, options: { prepare: true } };
|
|
400
503
|
};
|
|
401
504
|
|
|
505
|
+
/** Index metadata stored in `sqlite_master` for a {@link Sqlite} database. */
|
|
506
|
+
export const SqliteIndex = /*#__PURE__*/ object({ name: String, sql: String });
|
|
507
|
+
export interface SqliteIndex extends InferType<typeof SqliteIndex> {}
|
|
508
|
+
|
|
509
|
+
/** {@link Eq} instance for {@link SqliteIndex}. */
|
|
510
|
+
export const eqSqliteIndex: Eq<SqliteIndex> = /*#__PURE__*/ createEqObject({
|
|
511
|
+
name: eqString,
|
|
512
|
+
sql: eqString,
|
|
513
|
+
});
|
|
514
|
+
|
|
402
515
|
/**
|
|
403
|
-
*
|
|
404
|
-
*
|
|
516
|
+
* Full schema metadata for a {@link Sqlite} database.
|
|
517
|
+
*
|
|
518
|
+
* Includes table-column mappings and user-visible indexes.
|
|
405
519
|
*/
|
|
406
|
-
export const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
* option. LRU cache restores performance.
|
|
412
|
-
*/
|
|
413
|
-
const cached = isSqlMutationCache.get(sql);
|
|
414
|
-
if (cached !== undefined) return cached;
|
|
520
|
+
export const SqliteSchema = /*#__PURE__*/ object({
|
|
521
|
+
tables: /*#__PURE__*/ record(String, /*#__PURE__*/ set(String)),
|
|
522
|
+
indexes: /*#__PURE__*/ array(SqliteIndex),
|
|
523
|
+
});
|
|
524
|
+
export interface SqliteSchema extends InferType<typeof SqliteSchema> {}
|
|
415
525
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
526
|
+
/** Get the current SQLite schema by reading SQLite metadata. */
|
|
527
|
+
export const getSqliteSchema =
|
|
528
|
+
(deps: SqliteDep) =>
|
|
529
|
+
({
|
|
530
|
+
excludeIndexNamePrefix,
|
|
531
|
+
}: {
|
|
532
|
+
/**
|
|
533
|
+
* If provided, indexes with names starting with this prefix are excluded in
|
|
534
|
+
* SQLite query.
|
|
535
|
+
*/
|
|
536
|
+
excludeIndexNamePrefix?: string;
|
|
537
|
+
} = {}): SqliteSchema => {
|
|
538
|
+
const tables = createRecord<string, Set<string>>();
|
|
539
|
+
|
|
540
|
+
const tableAndColumnInfoRows = deps.sqlite.exec<{
|
|
541
|
+
tableName: string;
|
|
542
|
+
columnName: string;
|
|
543
|
+
}>(sql`
|
|
544
|
+
select
|
|
545
|
+
sqlite_master.name as tableName,
|
|
546
|
+
table_info.name as columnName
|
|
547
|
+
from
|
|
548
|
+
sqlite_master
|
|
549
|
+
join pragma_table_info(sqlite_master.name) as table_info;
|
|
550
|
+
`);
|
|
551
|
+
|
|
552
|
+
tableAndColumnInfoRows.rows.forEach(({ tableName, columnName }) => {
|
|
553
|
+
(tables[tableName] ??= new Set()).add(columnName);
|
|
554
|
+
});
|
|
420
555
|
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
)
|
|
556
|
+
const indexNamePrefixFilter =
|
|
557
|
+
excludeIndexNamePrefix != null
|
|
558
|
+
? ` and name not like '${excludeIndexNamePrefix.replaceAll("'", "''")}%'`
|
|
559
|
+
: "";
|
|
560
|
+
|
|
561
|
+
const indexesRows = deps.sqlite.exec<{ name: string; sql: string }>(
|
|
562
|
+
sql`
|
|
563
|
+
select name, sql
|
|
564
|
+
from sqlite_master
|
|
565
|
+
where
|
|
566
|
+
type = 'index'
|
|
567
|
+
and name not like 'sqlite_%'
|
|
568
|
+
${sql.raw(indexNamePrefixFilter)};
|
|
569
|
+
`,
|
|
570
|
+
);
|
|
424
571
|
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
);
|
|
572
|
+
const indexes = indexesRows.rows.map(
|
|
573
|
+
(row): SqliteIndex => ({
|
|
574
|
+
name: row.name,
|
|
575
|
+
/**
|
|
576
|
+
* SQLite returns "CREATE INDEX" for "create index" for some reason.
|
|
577
|
+
* Other keywords remain unchanged. We have to normalize the casing for
|
|
578
|
+
* schema comparison manually.
|
|
579
|
+
*/
|
|
580
|
+
sql: row.sql
|
|
581
|
+
.replace("CREATE INDEX", "create index")
|
|
582
|
+
.replace("CREATE UNIQUE INDEX", "create unique index"),
|
|
583
|
+
}),
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
return { tables, indexes };
|
|
587
|
+
};
|
|
442
588
|
|
|
443
589
|
/**
|
|
444
|
-
*
|
|
445
|
-
*
|
|
590
|
+
* Returns {@link SqliteSchema} and full {@link SqliteRow} table contents for
|
|
591
|
+
* inspection and testing.
|
|
446
592
|
*/
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
while (i < sql.length) {
|
|
455
|
-
// Check for comment start
|
|
456
|
-
if (i < sql.length - 1 && sql[i] === "-" && sql[i + 1] === "-") {
|
|
457
|
-
// Skip until end of line or end of string
|
|
458
|
-
i += 2;
|
|
459
|
-
while (i < sql.length && sql[i] !== "\n") {
|
|
460
|
-
i++;
|
|
461
|
-
}
|
|
462
|
-
// Keep the newline if present
|
|
463
|
-
if (i < sql.length && sql[i] === "\n") {
|
|
464
|
-
result += "\n";
|
|
465
|
-
i++;
|
|
466
|
-
}
|
|
467
|
-
} else {
|
|
468
|
-
result += sql[i];
|
|
469
|
-
i++;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
return result;
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
export interface SqliteQueryPlanRow {
|
|
477
|
-
id: number;
|
|
478
|
-
parent: number;
|
|
479
|
-
detail: string;
|
|
593
|
+
export interface SqliteSnapshot {
|
|
594
|
+
readonly schema: SqliteSchema;
|
|
595
|
+
readonly tables: Array<{
|
|
596
|
+
readonly name: string;
|
|
597
|
+
readonly rows: ReadonlyArray<SqliteRow>;
|
|
598
|
+
}>;
|
|
480
599
|
}
|
|
481
600
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
// eslint-disable-next-line no-console
|
|
492
|
-
console.log("[explainSqliteQueryPlan]", query);
|
|
493
|
-
// eslint-disable-next-line no-console
|
|
494
|
-
console.log(
|
|
495
|
-
drawSqliteQueryPlan(
|
|
496
|
-
result.value.rows as unknown as Array<SqliteQueryPlanRow>,
|
|
497
|
-
),
|
|
498
|
-
);
|
|
601
|
+
/**
|
|
602
|
+
* Captures a full {@link SqliteSnapshot} for testing and diagnostics.
|
|
603
|
+
*
|
|
604
|
+
* The snapshot includes current {@link SqliteSchema} and all rows from every
|
|
605
|
+
* discovered table. Table order follows `schema.tables` iteration order.
|
|
606
|
+
*/
|
|
607
|
+
export const getSqliteSnapshot = (deps: SqliteDep): SqliteSnapshot => {
|
|
608
|
+
const schema = getSqliteSchema(deps)();
|
|
499
609
|
|
|
500
|
-
|
|
501
|
-
};
|
|
610
|
+
const tables: SqliteSnapshot["tables"] = [];
|
|
502
611
|
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
let indent = 0;
|
|
612
|
+
for (const tableName in schema.tables) {
|
|
613
|
+
const result = deps.sqlite.exec(sql`
|
|
614
|
+
select * from ${sql.identifier(tableName)};
|
|
615
|
+
`);
|
|
508
616
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
|
|
515
|
-
} while (true);
|
|
617
|
+
tables.push({
|
|
618
|
+
name: tableName,
|
|
619
|
+
rows: result.rows,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
516
622
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
.join("\n");
|
|
623
|
+
return { schema, tables };
|
|
624
|
+
};
|
|
520
625
|
|
|
521
626
|
/**
|
|
522
627
|
* SQLite represents boolean values using `0` (false) and `1` (true) instead of
|
|
@@ -524,14 +629,14 @@ const drawSqliteQueryPlan = (rows: Array<SqliteQueryPlanRow>): string =>
|
|
|
524
629
|
*
|
|
525
630
|
* See: https://www.sqlite.org/quirks.html#no_separate_boolean_datatype
|
|
526
631
|
*
|
|
527
|
-
*
|
|
632
|
+
* ## Tips
|
|
528
633
|
*
|
|
529
634
|
* - Use {@link sqliteTrue} and {@link sqliteFalse} constants for better
|
|
530
635
|
* readability.
|
|
531
636
|
* - Use {@link booleanToSqliteBoolean} and {@link sqliteBooleanToBoolean} for
|
|
532
637
|
* converting between JavaScript booleans and SQLite boolean values.
|
|
533
638
|
*/
|
|
534
|
-
export const SqliteBoolean = union(0, 1);
|
|
639
|
+
export const SqliteBoolean = /*#__PURE__*/ union(0, 1);
|
|
535
640
|
export type SqliteBoolean = typeof SqliteBoolean.Type;
|
|
536
641
|
|
|
537
642
|
/**
|