@evolu/common 7.4.1 → 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 +851 -430
- 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 -824
- 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 +206 -277
- 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 -526
- package/src/Instances.ts +0 -92
- 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 -949
|
@@ -1,86 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database schema definition and validation.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import * as Kysely from "kysely";
|
|
8
|
+
import { getProperty, mapObject, type ReadonlyRecord } from "../Object.js";
|
|
2
9
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
ReadonlyRecord,
|
|
7
|
-
} from "../Object.js";
|
|
8
|
-
import { ok, Result } from "../Result.js";
|
|
9
|
-
import {
|
|
10
|
-
SafeSql,
|
|
10
|
+
eqSqliteIndex,
|
|
11
|
+
getSqliteSchema,
|
|
12
|
+
type SafeSql,
|
|
11
13
|
sql,
|
|
14
|
+
sqliteQueryToSqliteQueryString,
|
|
12
15
|
SqliteBoolean,
|
|
13
|
-
SqliteDep,
|
|
14
|
-
|
|
15
|
-
SqliteQuery,
|
|
16
|
-
SqliteQueryOptions,
|
|
16
|
+
type SqliteDep,
|
|
17
|
+
type SqliteIndex,
|
|
18
|
+
type SqliteQuery,
|
|
19
|
+
type SqliteQueryOptions,
|
|
20
|
+
type SqliteSchema,
|
|
17
21
|
SqliteValue,
|
|
18
22
|
} from "../Sqlite.js";
|
|
23
|
+
import type { InferType } from "../Type.js";
|
|
19
24
|
import {
|
|
20
|
-
AnyType,
|
|
21
|
-
array,
|
|
22
|
-
createIdFromString,
|
|
23
25
|
DateIso,
|
|
26
|
+
type Id,
|
|
24
27
|
IdBytes,
|
|
25
|
-
InferErrors,
|
|
26
|
-
InferInput,
|
|
27
|
-
InferType,
|
|
28
|
-
maxMutationSize,
|
|
29
|
-
MergeObjectTypeErrors,
|
|
30
|
-
nullableToOptional,
|
|
31
|
-
NullableToOptionalProps,
|
|
32
28
|
nullOr,
|
|
33
29
|
object,
|
|
34
|
-
|
|
35
|
-
omit,
|
|
36
|
-
optional,
|
|
37
|
-
OptionalType,
|
|
38
|
-
record,
|
|
39
|
-
set,
|
|
40
|
-
String,
|
|
41
|
-
TableId,
|
|
42
|
-
Type,
|
|
43
|
-
ValidMutationSize,
|
|
44
|
-
validMutationSize,
|
|
45
|
-
ValidMutationSizeError,
|
|
30
|
+
type StandardSchemaV1,
|
|
46
31
|
} from "../Type.js";
|
|
47
|
-
import { Simplify } from "../Types.js";
|
|
48
|
-
import { AppOwner
|
|
49
|
-
import {
|
|
32
|
+
import type { Simplify } from "../Types.js";
|
|
33
|
+
import type { AppOwner } from "./Owner.js";
|
|
34
|
+
import { OwnerId } from "./Owner.js";
|
|
35
|
+
import type {
|
|
36
|
+
evoluJsonArrayFrom,
|
|
37
|
+
evoluJsonObjectFrom,
|
|
38
|
+
Query,
|
|
39
|
+
Row,
|
|
40
|
+
} from "./Query.js";
|
|
50
41
|
import type { CrdtMessage, DbChange } from "./Storage.js";
|
|
51
|
-
import {
|
|
52
|
-
|
|
42
|
+
import { TimestampBytes } from "./Timestamp.js";
|
|
43
|
+
|
|
44
|
+
/** Any {@link StandardSchemaV1}. */
|
|
45
|
+
export type AnyStandardSchemaV1 = StandardSchemaV1<any, any>;
|
|
53
46
|
|
|
54
47
|
/**
|
|
55
48
|
* Defines the schema of an Evolu database.
|
|
56
49
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
50
|
+
* Column types are Standard Schema v1 compatible — use Evolu Type, Zod,
|
|
51
|
+
* Valibot, ArkType, or any library that implements Standard Schema.
|
|
52
|
+
*
|
|
53
|
+
* Table schema defines columns that are required for table rows. For optional
|
|
54
|
+
* columns, use a schema whose output type includes `null`.
|
|
59
55
|
*
|
|
60
56
|
* ### Example
|
|
61
57
|
*
|
|
62
58
|
* ```ts
|
|
59
|
+
* // With Evolu Type
|
|
63
60
|
* const TodoId = id("Todo");
|
|
64
61
|
* type TodoId = typeof TodoId.Type;
|
|
65
62
|
*
|
|
66
|
-
* const TodoCategoryId = id("TodoCategory");
|
|
67
|
-
* type TodoCategoryId = typeof TodoCategoryId.Type;
|
|
68
|
-
*
|
|
69
|
-
* const NonEmptyString50 = maxLength(50)(NonEmptyString);
|
|
70
|
-
* type NonEmptyString50 = typeof NonEmptyString50.Type;
|
|
71
|
-
*
|
|
72
|
-
* // Database schema.
|
|
73
63
|
* const Schema = {
|
|
74
64
|
* todo: {
|
|
75
65
|
* id: TodoId,
|
|
76
|
-
* title:
|
|
77
|
-
* isCompleted:
|
|
78
|
-
* categoryId: nullable(TodoCategoryId),
|
|
66
|
+
* title: NonEmptyString100,
|
|
67
|
+
* isCompleted: nullOr(SqliteBoolean),
|
|
79
68
|
* },
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
69
|
+
* };
|
|
70
|
+
*
|
|
71
|
+
* // With Zod (or any Standard Schema library)
|
|
72
|
+
* const Schema = {
|
|
73
|
+
* todo: {
|
|
74
|
+
* id: TodoId, // Evolu id() for branded IDs
|
|
75
|
+
* title: z.string().min(1).max(100),
|
|
76
|
+
* isCompleted: z.union([z.literal(0), z.literal(1)]).nullable(),
|
|
84
77
|
* },
|
|
85
78
|
* };
|
|
86
79
|
* ```
|
|
@@ -88,9 +81,16 @@ import { readonly } from "../Function.js";
|
|
|
88
81
|
export type EvoluSchema = ReadonlyRecord<
|
|
89
82
|
string,
|
|
90
83
|
// TypeScript errors are cryptic so we use ValidateSchema.
|
|
91
|
-
|
|
84
|
+
TableSchema
|
|
92
85
|
>;
|
|
93
86
|
|
|
87
|
+
/** A table schema: column names mapped to Standard Schema validators. */
|
|
88
|
+
export type TableSchema = ReadonlyRecord<string, AnyStandardSchemaV1>;
|
|
89
|
+
|
|
90
|
+
export interface SqliteSchemaDep {
|
|
91
|
+
readonly sqliteSchema: SqliteSchema;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
94
|
/**
|
|
95
95
|
* Validates an {@link EvoluSchema} at compile time, returning the first error
|
|
96
96
|
* found as a readable string literal type. This approach provides much clearer
|
|
@@ -100,9 +100,9 @@ export type EvoluSchema = ReadonlyRecord<
|
|
|
100
100
|
* Validates the following schema requirements:
|
|
101
101
|
*
|
|
102
102
|
* 1. All tables must have an 'id' column
|
|
103
|
-
* 2. The 'id' column
|
|
103
|
+
* 2. The 'id' column output type must extend {@link Id}
|
|
104
104
|
* 3. Tables cannot use system column names (createdAt, updatedAt, isDeleted)
|
|
105
|
-
* 4. All column types must be compatible with SQLite (extend SqliteValue)
|
|
105
|
+
* 4. All column output types must be compatible with SQLite (extend SqliteValue)
|
|
106
106
|
*/
|
|
107
107
|
export type ValidateSchema<S extends EvoluSchema> =
|
|
108
108
|
ValidateSchemaHasId<S> extends never
|
|
@@ -115,85 +115,10 @@ export type ValidateSchema<S extends EvoluSchema> =
|
|
|
115
115
|
: ValidateIdColumnType<S>
|
|
116
116
|
: ValidateSchemaHasId<S>;
|
|
117
117
|
|
|
118
|
-
export type ValidateSchemaHasId<S extends EvoluSchema> =
|
|
119
|
-
keyof S extends infer TableName
|
|
120
|
-
? TableName extends keyof S
|
|
121
|
-
? "id" extends keyof S[TableName]
|
|
122
|
-
? never
|
|
123
|
-
: SchemaValidationError<`Table "${TableName & string}" is missing required id column.`>
|
|
124
|
-
: never
|
|
125
|
-
: never;
|
|
126
|
-
|
|
127
|
-
export type ValidateIdColumnType<S extends EvoluSchema> =
|
|
128
|
-
keyof S extends infer TableName
|
|
129
|
-
? TableName extends keyof S
|
|
130
|
-
? "id" extends keyof S[TableName]
|
|
131
|
-
? S[TableName]["id"] extends TableId<any>
|
|
132
|
-
? never
|
|
133
|
-
: SchemaValidationError<`Table "${TableName & string}" id column must be a branded ID type (created with id("${TableName & string}")).`>
|
|
134
|
-
: never
|
|
135
|
-
: never
|
|
136
|
-
: never;
|
|
137
|
-
|
|
138
|
-
export type ValidateNoSystemColumns<S extends EvoluSchema> =
|
|
139
|
-
keyof S extends infer TableName
|
|
140
|
-
? TableName extends keyof S
|
|
141
|
-
? keyof S[TableName] extends infer ColumnName
|
|
142
|
-
? ColumnName extends keyof S[TableName]
|
|
143
|
-
? ColumnName extends
|
|
144
|
-
| "createdAt"
|
|
145
|
-
| "updatedAt"
|
|
146
|
-
| "isDeleted"
|
|
147
|
-
| "ownerId"
|
|
148
|
-
? SchemaValidationError<`Table "${TableName & string}" uses system column name "${ColumnName & string}". System columns (createdAt, updatedAt, isDeleted, ownerId) are added automatically.`>
|
|
149
|
-
: never
|
|
150
|
-
: never
|
|
151
|
-
: never
|
|
152
|
-
: never
|
|
153
|
-
: never;
|
|
154
|
-
|
|
155
|
-
export type ValidateColumnTypes<S extends EvoluSchema> =
|
|
156
|
-
keyof S extends infer TableName
|
|
157
|
-
? TableName extends keyof S
|
|
158
|
-
? keyof S[TableName] extends infer ColumnName
|
|
159
|
-
? ColumnName extends keyof S[TableName]
|
|
160
|
-
? InferType<S[TableName][ColumnName]> extends SqliteValue
|
|
161
|
-
? never
|
|
162
|
-
: SchemaValidationError<`Table "${TableName & string}" column "${ColumnName & string}" type is not compatible with SQLite. Column types must extend SqliteValue (string, number, Uint8Array, or null).`>
|
|
163
|
-
: never
|
|
164
|
-
: never
|
|
165
|
-
: never
|
|
166
|
-
: never;
|
|
167
|
-
|
|
168
|
-
/** Schema validation error that shows clear, readable messages */
|
|
169
|
-
export type SchemaValidationError<Message extends string> =
|
|
170
|
-
`❌ Schema Error: ${Message}`;
|
|
171
|
-
|
|
172
118
|
export type IndexesConfig = (
|
|
173
119
|
create: (indexName: string) => Kysely.CreateIndexBuilder,
|
|
174
120
|
) => ReadonlyArray<Kysely.CreateIndexBuilder<any>>;
|
|
175
121
|
|
|
176
|
-
export const evoluSchemaToDbSchema = (
|
|
177
|
-
schema: EvoluSchema,
|
|
178
|
-
indexesConfig?: IndexesConfig,
|
|
179
|
-
): DbSchema => {
|
|
180
|
-
const tables = mapObject(
|
|
181
|
-
schema,
|
|
182
|
-
(table) => new Set(Object.keys(table).filter((k) => k !== "id")),
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
const indexes = indexesConfig
|
|
186
|
-
? indexesConfig(createIndex).map(
|
|
187
|
-
(index): DbIndex => ({
|
|
188
|
-
name: index.toOperationNode().name.name,
|
|
189
|
-
sql: index.compile().sql,
|
|
190
|
-
}),
|
|
191
|
-
)
|
|
192
|
-
: [];
|
|
193
|
-
|
|
194
|
-
return { tables, indexes };
|
|
195
|
-
};
|
|
196
|
-
|
|
197
122
|
export type CreateQuery<S extends EvoluSchema> = <R extends Row>(
|
|
198
123
|
queryCallback: (
|
|
199
124
|
db: Pick<
|
|
@@ -201,8 +126,8 @@ export type CreateQuery<S extends EvoluSchema> = <R extends Row>(
|
|
|
201
126
|
{
|
|
202
127
|
[Table in keyof S]: {
|
|
203
128
|
readonly [Column in keyof S[Table]]: Column extends "id"
|
|
204
|
-
?
|
|
205
|
-
:
|
|
129
|
+
? StandardSchemaV1.InferOutput<S[Table][Column]>
|
|
130
|
+
: StandardSchemaV1.InferOutput<S[Table][Column]> | null;
|
|
206
131
|
} & SystemColumns;
|
|
207
132
|
} & {
|
|
208
133
|
readonly evolu_history: {
|
|
@@ -225,53 +150,54 @@ export type CreateQuery<S extends EvoluSchema> = <R extends Row>(
|
|
|
225
150
|
>,
|
|
226
151
|
) => Kysely.SelectQueryBuilder<any, any, R>,
|
|
227
152
|
options?: SqliteQueryOptions,
|
|
228
|
-
) => Query<Simplify<R>>;
|
|
153
|
+
) => Query<S, Simplify<R>>;
|
|
229
154
|
|
|
230
155
|
/**
|
|
231
156
|
* System columns that are implicitly defined by Evolu.
|
|
232
157
|
*
|
|
233
|
-
* - `createdAt`: Set by Evolu on row creation, derived from
|
|
234
|
-
* - `updatedAt`: Set by Evolu on every row change, derived from
|
|
158
|
+
* - `createdAt`: Set by Evolu on row creation, derived from Timestamp.
|
|
159
|
+
* - `updatedAt`: Set by Evolu on every row change, derived from Timestamp.
|
|
235
160
|
* - `isDeleted`: Soft delete flag created by Evolu and used by the developer to
|
|
236
161
|
* mark rows as deleted.
|
|
237
162
|
* - `ownerId`: Represents ownership and logically partitions the database.
|
|
238
163
|
*/
|
|
239
|
-
export const SystemColumns = object({
|
|
164
|
+
export const SystemColumns = /*#__PURE__*/ object({
|
|
240
165
|
createdAt: DateIso,
|
|
241
166
|
updatedAt: DateIso,
|
|
242
|
-
isDeleted: nullOr(SqliteBoolean),
|
|
167
|
+
isDeleted: /*#__PURE__*/ nullOr(SqliteBoolean),
|
|
243
168
|
ownerId: OwnerId,
|
|
244
169
|
});
|
|
245
|
-
export
|
|
246
|
-
|
|
247
|
-
export const systemColumns = readonly(
|
|
248
|
-
new Set(Object.keys(SystemColumns.props)),
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
export const systemColumnsWithId = readonly([...systemColumns, "id"]);
|
|
170
|
+
export interface SystemColumns extends InferType<typeof SystemColumns> {}
|
|
252
171
|
|
|
253
172
|
export type MutationKind = "insert" | "update" | "upsert";
|
|
254
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Mutation function type. Accepts already-validated values — validation is the
|
|
176
|
+
* caller's responsibility using any Standard Schema library (Evolu Type, Zod,
|
|
177
|
+
* Valibot, ArkType, etc.).
|
|
178
|
+
*
|
|
179
|
+
* Evolu does not use SQL for mutations to ensure data can be deterministically
|
|
180
|
+
* merged without conflicts. Explicit mutations also allow Evolu to
|
|
181
|
+
* automatically update {@link SystemColumns} and encourage developers to
|
|
182
|
+
* consider the number of changes produced, unlike SQL where a single query can
|
|
183
|
+
* inadvertently generate a large volume of CRDT messages. Each mutation
|
|
184
|
+
* produces exactly one {@link CrdtMessage} containing all provided columns.
|
|
185
|
+
*
|
|
186
|
+
* Mutations never fail — values are already validated by the caller, and
|
|
187
|
+
* changes are stored locally in SQLite.
|
|
188
|
+
*
|
|
189
|
+
* - **insert**: all non-nullable columns required, nullable columns optional,
|
|
190
|
+
* `id` omitted (auto-generated)
|
|
191
|
+
* - **update**: only `id` required, everything else optional
|
|
192
|
+
* - **upsert**: like insert but `id` required too
|
|
193
|
+
*/
|
|
255
194
|
export type Mutation<S extends EvoluSchema, Kind extends MutationKind> = <
|
|
256
195
|
TableName extends keyof S,
|
|
257
196
|
>(
|
|
258
197
|
table: TableName,
|
|
259
|
-
|
|
198
|
+
values: MutationValues<S[TableName], Kind>,
|
|
260
199
|
options?: MutationOptions,
|
|
261
|
-
) =>
|
|
262
|
-
{ readonly id: S[TableName]["id"]["Type"] },
|
|
263
|
-
| ValidMutationSizeError
|
|
264
|
-
| MergeObjectTypeErrors<ObjectType<MutationMapping<S[TableName], Kind>>>
|
|
265
|
-
>;
|
|
266
|
-
|
|
267
|
-
export type MutationMapping<
|
|
268
|
-
P extends Record<string, AnyType>,
|
|
269
|
-
M extends MutationKind,
|
|
270
|
-
> = M extends "insert"
|
|
271
|
-
? InsertableProps<P>
|
|
272
|
-
: M extends "update"
|
|
273
|
-
? UpdateableProps<P>
|
|
274
|
-
: UpsertableProps<P>;
|
|
200
|
+
) => { readonly id: StandardSchemaV1.InferOutput<S[TableName]["id"]> };
|
|
275
201
|
|
|
276
202
|
export interface MutationOptions {
|
|
277
203
|
/**
|
|
@@ -311,250 +237,216 @@ export interface MutationOptions {
|
|
|
311
237
|
* { ownerId: sharedOwner.id },
|
|
312
238
|
* );
|
|
313
239
|
* ```
|
|
314
|
-
*
|
|
315
|
-
* @experimental
|
|
316
240
|
*/
|
|
317
241
|
readonly ownerId?: OwnerId;
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Only validate, don't mutate.
|
|
321
|
-
*
|
|
322
|
-
* For example, `onChange` handler can call `insert`/`update`/`upsert` with
|
|
323
|
-
* `onlyValidate: true`.
|
|
324
|
-
*/
|
|
325
|
-
readonly onlyValidate?: boolean;
|
|
326
242
|
}
|
|
327
243
|
|
|
328
244
|
export interface MutationChange extends DbChange {
|
|
329
|
-
|
|
330
|
-
readonly ownerId?: OwnerId | undefined;
|
|
245
|
+
readonly ownerId: OwnerId;
|
|
331
246
|
}
|
|
332
247
|
|
|
333
248
|
/**
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
* {@link maxMutationSize}.
|
|
337
|
-
*
|
|
338
|
-
* ### Example
|
|
339
|
-
*
|
|
340
|
-
* ```ts
|
|
341
|
-
* const InsertableTodo = insertable(Schema.todo);
|
|
342
|
-
* type InsertableTodo = typeof InsertableTodo.Type;
|
|
343
|
-
* const todo = InsertableTodo.from({ title });
|
|
344
|
-
* if (!todo.ok) return; // handle errors
|
|
345
|
-
* ```
|
|
249
|
+
* Derives the expected values type for a mutation from a table's column schemas
|
|
250
|
+
* and a {@link MutationKind}.
|
|
346
251
|
*/
|
|
347
|
-
export
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
252
|
+
export type MutationValues<
|
|
253
|
+
T extends TableSchema,
|
|
254
|
+
M extends MutationKind,
|
|
255
|
+
> = Simplify<
|
|
256
|
+
M extends "insert"
|
|
257
|
+
? InsertValues<T>
|
|
258
|
+
: M extends "update"
|
|
259
|
+
? UpdateValues<T>
|
|
260
|
+
: UpsertValues<T>
|
|
261
|
+
>;
|
|
354
262
|
|
|
355
|
-
|
|
356
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Insert values: `id` omitted (auto-generated), nullable columns optional,
|
|
265
|
+
* non-nullable columns required.
|
|
266
|
+
*/
|
|
267
|
+
export type InsertValues<T extends TableSchema> = Omit<
|
|
268
|
+
NullableColumnsToOptional<T>,
|
|
357
269
|
"id"
|
|
358
270
|
>;
|
|
359
271
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Update values: `id` required, all other columns optional. Includes
|
|
274
|
+
* `isDeleted` for soft deletes.
|
|
275
|
+
*/
|
|
276
|
+
export type UpdateValues<T extends TableSchema> = {
|
|
277
|
+
readonly id: StandardSchemaV1.InferOutput<T["id"]>;
|
|
278
|
+
} & {
|
|
279
|
+
readonly [K in Exclude<keyof T, "id">]?: StandardSchemaV1.InferOutput<T[K]>;
|
|
280
|
+
} & {
|
|
281
|
+
readonly isDeleted?: SqliteBoolean;
|
|
282
|
+
};
|
|
363
283
|
|
|
364
284
|
/**
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
* {@link maxMutationSize}.
|
|
368
|
-
*
|
|
369
|
-
* ### Example
|
|
370
|
-
*
|
|
371
|
-
* ```ts
|
|
372
|
-
* const UpdateableTodo = updateable(Schema.todo);
|
|
373
|
-
* type UpdateableTodo = typeof UpdateableTodo.Type;
|
|
374
|
-
*
|
|
375
|
-
* // `id` is required; all other fields are optional.
|
|
376
|
-
* const todoResult = UpdateableTodo.from({
|
|
377
|
-
* id: "123",
|
|
378
|
-
* title: "New Title",
|
|
379
|
-
* });
|
|
380
|
-
* if (!todo.ok) return; // handle errors
|
|
381
|
-
* ```
|
|
285
|
+
* Upsert values: `id` required, nullable columns optional, non-nullable columns
|
|
286
|
+
* required. Includes `isDeleted` for soft deletes.
|
|
382
287
|
*/
|
|
383
|
-
export
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
288
|
+
export type UpsertValues<T extends TableSchema> =
|
|
289
|
+
NullableColumnsToOptional<T> & {
|
|
290
|
+
readonly isDeleted?: SqliteBoolean;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export type ValidateSchemaHasId<S extends EvoluSchema> =
|
|
294
|
+
keyof S extends infer TableName
|
|
295
|
+
? TableName extends keyof S
|
|
296
|
+
? "id" extends keyof S[TableName]
|
|
297
|
+
? never
|
|
298
|
+
: SchemaValidationError<`Table "${TableName & string}" is missing required id column.`>
|
|
299
|
+
: never
|
|
300
|
+
: never;
|
|
301
|
+
|
|
302
|
+
export type ValidateIdColumnType<S extends EvoluSchema> =
|
|
303
|
+
keyof S extends infer TableName
|
|
304
|
+
? TableName extends keyof S
|
|
305
|
+
? "id" extends keyof S[TableName]
|
|
306
|
+
? StandardSchemaV1.InferOutput<S[TableName]["id"]> extends Id
|
|
307
|
+
? never
|
|
308
|
+
: SchemaValidationError<`Table "${TableName & string}" id column output type must extend Id. Use id("${TableName & string}") from Evolu Type.`>
|
|
309
|
+
: never
|
|
310
|
+
: never
|
|
311
|
+
: never;
|
|
312
|
+
|
|
313
|
+
export type ValidateNoSystemColumns<S extends EvoluSchema> =
|
|
314
|
+
keyof S extends infer TableName
|
|
315
|
+
? TableName extends keyof S
|
|
316
|
+
? keyof S[TableName] extends infer ColumnName
|
|
317
|
+
? ColumnName extends keyof S[TableName]
|
|
318
|
+
? ColumnName extends
|
|
319
|
+
| "createdAt"
|
|
320
|
+
| "updatedAt"
|
|
321
|
+
| "isDeleted"
|
|
322
|
+
| "ownerId"
|
|
323
|
+
? SchemaValidationError<`Table "${TableName & string}" uses system column name "${ColumnName & string}". System columns (createdAt, updatedAt, isDeleted, ownerId) are added automatically.`>
|
|
324
|
+
: never
|
|
325
|
+
: never
|
|
326
|
+
: never
|
|
327
|
+
: never
|
|
328
|
+
: never;
|
|
329
|
+
|
|
330
|
+
export type ValidateColumnTypes<S extends EvoluSchema> =
|
|
331
|
+
keyof S extends infer TableName
|
|
332
|
+
? TableName extends keyof S
|
|
333
|
+
? keyof S[TableName] extends infer ColumnName
|
|
334
|
+
? ColumnName extends keyof S[TableName]
|
|
335
|
+
? StandardSchemaV1.InferOutput<
|
|
336
|
+
S[TableName][ColumnName]
|
|
337
|
+
> extends SqliteValue
|
|
338
|
+
? never
|
|
339
|
+
: SchemaValidationError<`Table "${TableName & string}" column "${ColumnName & string}" type is not compatible with SQLite. Column types must extend SqliteValue (string, number, Uint8Array, or null).`>
|
|
340
|
+
: never
|
|
341
|
+
: never
|
|
342
|
+
: never
|
|
343
|
+
: never;
|
|
344
|
+
|
|
345
|
+
/** Schema validation error that shows clear, readable messages */
|
|
346
|
+
export type SchemaValidationError<Message extends string> =
|
|
347
|
+
`❌ Schema Error: ${Message}`;
|
|
348
|
+
|
|
349
|
+
/** Makes columns whose output type includes `null` optional. */
|
|
350
|
+
export type NullableColumnsToOptional<T extends TableSchema> = {
|
|
351
|
+
readonly [K in RequiredColumnKeys<T>]: StandardSchemaV1.InferOutput<T[K]>;
|
|
352
|
+
} & {
|
|
353
|
+
readonly [K in OptionalColumnKeys<T>]?: StandardSchemaV1.InferOutput<T[K]>;
|
|
391
354
|
};
|
|
392
355
|
|
|
393
|
-
export type
|
|
394
|
-
[K in keyof
|
|
395
|
-
}
|
|
356
|
+
export type RequiredColumnKeys<T extends TableSchema> = {
|
|
357
|
+
[K in keyof T]: null extends StandardSchemaV1.InferOutput<T[K]> ? never : K;
|
|
358
|
+
}[keyof T];
|
|
396
359
|
|
|
397
|
-
export type
|
|
398
|
-
|
|
399
|
-
|
|
360
|
+
export type OptionalColumnKeys<T extends TableSchema> = {
|
|
361
|
+
[K in keyof T]: null extends StandardSchemaV1.InferOutput<T[K]> ? K : never;
|
|
362
|
+
}[keyof T];
|
|
363
|
+
|
|
364
|
+
export const systemColumns: ReadonlySet<string> = /*#__PURE__*/ new Set(
|
|
365
|
+
/*#__PURE__*/ Object.keys(SystemColumns.props),
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
export const systemColumnsWithId: ReadonlyArray<string> = [
|
|
369
|
+
...systemColumns,
|
|
370
|
+
"id",
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
export const evoluSchemaToSqliteSchema = <S extends EvoluSchema>(
|
|
374
|
+
schema: ValidateSchema<S> extends never ? S : ValidateSchema<S>,
|
|
375
|
+
indexesConfig?: IndexesConfig,
|
|
376
|
+
): SqliteSchema => {
|
|
377
|
+
const validSchema = schema as EvoluSchema;
|
|
378
|
+
|
|
379
|
+
const tables = mapObject(
|
|
380
|
+
validSchema,
|
|
381
|
+
(table) => new Set(Object.keys(table).filter((k) => k !== "id")),
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
const indexes = indexesConfig
|
|
385
|
+
? indexesConfig(createIndex).map(
|
|
386
|
+
(index): SqliteIndex => ({
|
|
387
|
+
name: index.toOperationNode().name.name,
|
|
388
|
+
sql: index.compile().sql,
|
|
389
|
+
}),
|
|
390
|
+
)
|
|
391
|
+
: [];
|
|
392
|
+
|
|
393
|
+
return { tables, indexes };
|
|
394
|
+
};
|
|
400
395
|
|
|
401
396
|
/**
|
|
402
|
-
*
|
|
403
|
-
* (so they are not required) and ensures the {@link maxMutationSize}.
|
|
404
|
-
*
|
|
405
|
-
* Upsert is like insert, except it requires an ID. It's useful for inserting
|
|
406
|
-
* rows with external ID via {@link createIdFromString}.
|
|
397
|
+
* Creates a query builder from a {@link EvoluSchema}.
|
|
407
398
|
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
399
|
+
* Supports Kysely relation-style query composition (nested objects/arrays via
|
|
400
|
+
* JSON subqueries), such as {@link evoluJsonObjectFrom} and
|
|
401
|
+
* {@link evoluJsonArrayFrom}. These helpers are Evolu's safer SQLite variants of
|
|
402
|
+
* the
|
|
403
|
+
* {@link https://kysely.dev/docs/recipes/relations | Kysely relations recipe}.
|
|
411
404
|
*
|
|
412
405
|
* ### Example
|
|
413
406
|
*
|
|
414
407
|
* ```ts
|
|
415
|
-
* const
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
408
|
+
* const Schema = {
|
|
409
|
+
* todo: {
|
|
410
|
+
* id: id("Todo"),
|
|
411
|
+
* title: NonEmptyString100,
|
|
412
|
+
* isCompleted: nullOr(SqliteBoolean),
|
|
413
|
+
* },
|
|
414
|
+
* };
|
|
415
|
+
*
|
|
416
|
+
* // Create a typed query builder (once per schema)
|
|
417
|
+
* const createQuery = createQueryBuilder(Schema);
|
|
418
|
+
*
|
|
419
|
+
* // Use it for all queries
|
|
420
|
+
* const todosQuery = createQuery((db) =>
|
|
421
|
+
* db.selectFrom("todo").select(["id", "title", "isCompleted"]),
|
|
422
|
+
* );
|
|
422
423
|
* ```
|
|
423
424
|
*/
|
|
424
|
-
export const
|
|
425
|
-
|
|
426
|
-
):
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
Props & {
|
|
437
|
-
isDeleted: OptionalType<typeof SqliteBoolean>;
|
|
438
|
-
}
|
|
439
|
-
>;
|
|
440
|
-
|
|
441
|
-
export type Upsertable<Props extends Record<string, AnyType>> = InferInput<
|
|
442
|
-
ObjectType<UpsertableProps<Props>>
|
|
443
|
-
>;
|
|
444
|
-
|
|
445
|
-
export type InferEvoluSchemaError<S extends EvoluSchema> = {
|
|
446
|
-
[Table in keyof S]: InferMutationTypeErrors<S[Table]>;
|
|
447
|
-
}[keyof S];
|
|
448
|
-
|
|
449
|
-
export type InferMutationTypeErrors<T extends Record<string, AnyType>> =
|
|
450
|
-
| InferColumnErrors<T, "insert">
|
|
451
|
-
| InferColumnErrors<T, "update">
|
|
452
|
-
| InferColumnErrors<T, "upsert">;
|
|
453
|
-
|
|
454
|
-
export type InferColumnErrors<
|
|
455
|
-
T extends Record<string, AnyType>,
|
|
456
|
-
M extends MutationKind,
|
|
457
|
-
> = {
|
|
458
|
-
[Column in keyof MutationMapping<T, M>]: InferErrors<
|
|
459
|
-
MutationMapping<T, M>[Column]
|
|
460
|
-
>;
|
|
461
|
-
}[keyof MutationMapping<T, M>];
|
|
462
|
-
|
|
463
|
-
export const DbIndex = object({ name: String, sql: String });
|
|
464
|
-
export type DbIndex = typeof DbIndex.Type;
|
|
465
|
-
|
|
466
|
-
export const DbSchema = object({
|
|
467
|
-
tables: record(String, set(String)),
|
|
468
|
-
indexes: array(DbIndex),
|
|
469
|
-
});
|
|
470
|
-
export type DbSchema = typeof DbSchema.Type;
|
|
471
|
-
|
|
472
|
-
// TODO: Use a ref and update dbSchema on hot reloading to support
|
|
473
|
-
// development workflows where schema changes without full app restart.
|
|
474
|
-
export interface DbSchemaDep {
|
|
475
|
-
readonly dbSchema: DbSchema;
|
|
476
|
-
}
|
|
425
|
+
export const createQueryBuilder = <S extends EvoluSchema>(
|
|
426
|
+
_schema: S,
|
|
427
|
+
): CreateQuery<S> => {
|
|
428
|
+
const createQuery: CreateQuery<S> = (queryCallback, options) => {
|
|
429
|
+
const compiledQuery = queryCallback(kysely as never).compile();
|
|
430
|
+
const sqliteQuery: SqliteQuery = {
|
|
431
|
+
sql: compiledQuery.sql as SafeSql,
|
|
432
|
+
parameters: compiledQuery.parameters as NonNullable<
|
|
433
|
+
SqliteQuery["parameters"]
|
|
434
|
+
>,
|
|
435
|
+
...(options && { options }),
|
|
436
|
+
};
|
|
477
437
|
|
|
478
|
-
|
|
479
|
-
export const getDbSchema =
|
|
480
|
-
(deps: SqliteDep) =>
|
|
481
|
-
({ allIndexes = false }: { allIndexes?: boolean } = {}): Result<
|
|
482
|
-
DbSchema,
|
|
483
|
-
SqliteError
|
|
484
|
-
> => {
|
|
485
|
-
const tables = createRecord<string, Set<string>>();
|
|
486
|
-
|
|
487
|
-
const tableAndColumnInfoRows = deps.sqlite.exec(sql`
|
|
488
|
-
select
|
|
489
|
-
sqlite_master.name as tableName,
|
|
490
|
-
table_info.name as columnName
|
|
491
|
-
from
|
|
492
|
-
sqlite_master
|
|
493
|
-
join pragma_table_info(sqlite_master.name) as table_info;
|
|
494
|
-
`);
|
|
495
|
-
|
|
496
|
-
if (!tableAndColumnInfoRows.ok) return tableAndColumnInfoRows;
|
|
497
|
-
|
|
498
|
-
tableAndColumnInfoRows.value.rows.forEach((row) => {
|
|
499
|
-
const { tableName, columnName } = row as unknown as {
|
|
500
|
-
tableName: string;
|
|
501
|
-
columnName: string;
|
|
502
|
-
};
|
|
503
|
-
(tables[tableName] ??= new Set()).add(columnName);
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
const indexesRows = deps.sqlite.exec(
|
|
507
|
-
allIndexes
|
|
508
|
-
? sql`
|
|
509
|
-
select name, sql
|
|
510
|
-
from sqlite_master
|
|
511
|
-
where type = 'index' and name not like 'sqlite_%';
|
|
512
|
-
`
|
|
513
|
-
: sql`
|
|
514
|
-
select name, sql
|
|
515
|
-
from sqlite_master
|
|
516
|
-
where
|
|
517
|
-
type = 'index'
|
|
518
|
-
and name not like 'sqlite_%'
|
|
519
|
-
and name not like 'evolu_%';
|
|
520
|
-
`,
|
|
521
|
-
);
|
|
522
|
-
|
|
523
|
-
if (!indexesRows.ok) return indexesRows;
|
|
524
|
-
|
|
525
|
-
const indexes = indexesRows.value.rows.map(
|
|
526
|
-
(row): DbIndex => ({
|
|
527
|
-
name: row.name as string,
|
|
528
|
-
/**
|
|
529
|
-
* SQLite returns "CREATE INDEX" for "create index" for some reason.
|
|
530
|
-
* Other keywords remain unchanged. We have to normalize the casing for
|
|
531
|
-
* {@link indexesAreEqual} manually.
|
|
532
|
-
*/
|
|
533
|
-
sql: (row.sql as string)
|
|
534
|
-
.replace("CREATE INDEX", "create index")
|
|
535
|
-
.replace("CREATE UNIQUE INDEX", "create unique index"),
|
|
536
|
-
}),
|
|
537
|
-
);
|
|
538
|
-
|
|
539
|
-
return ok({ tables, indexes });
|
|
438
|
+
return sqliteQueryToSqliteQueryString(sqliteQuery) as never;
|
|
540
439
|
};
|
|
541
440
|
|
|
542
|
-
|
|
543
|
-
|
|
441
|
+
return createQuery;
|
|
442
|
+
};
|
|
544
443
|
|
|
545
|
-
export const
|
|
444
|
+
export const ensureSqliteSchema =
|
|
546
445
|
(deps: SqliteDep) =>
|
|
547
|
-
(
|
|
548
|
-
newSchema: DbSchema,
|
|
549
|
-
currentSchema?: DbSchema,
|
|
550
|
-
): Result<void, SqliteError> => {
|
|
446
|
+
(newSchema: SqliteSchema, currentSchema?: SqliteSchema): void => {
|
|
551
447
|
const queries: Array<SqliteQuery> = [];
|
|
552
448
|
|
|
553
|
-
|
|
554
|
-
const dbSchema = getDbSchema(deps)();
|
|
555
|
-
if (!dbSchema.ok) return dbSchema;
|
|
556
|
-
currentSchema = dbSchema.value;
|
|
557
|
-
}
|
|
449
|
+
currentSchema ??= getEvoluSqliteSchema(deps)();
|
|
558
450
|
|
|
559
451
|
for (const [tableName, newColumns] of Object.entries(newSchema.tables)) {
|
|
560
452
|
const currentColumns = getProperty(currentSchema.tables, tableName);
|
|
@@ -575,7 +467,7 @@ export const ensureDbSchema =
|
|
|
575
467
|
.filter(
|
|
576
468
|
(currentIndex) =>
|
|
577
469
|
!newSchema.indexes.some((newIndex) =>
|
|
578
|
-
|
|
470
|
+
eqSqliteIndex(newIndex, currentIndex),
|
|
579
471
|
),
|
|
580
472
|
)
|
|
581
473
|
.forEach((index) => {
|
|
@@ -587,7 +479,7 @@ export const ensureDbSchema =
|
|
|
587
479
|
.filter(
|
|
588
480
|
(newIndex) =>
|
|
589
481
|
!currentSchema.indexes.some((currentIndex) =>
|
|
590
|
-
|
|
482
|
+
eqSqliteIndex(newIndex, currentIndex),
|
|
591
483
|
),
|
|
592
484
|
)
|
|
593
485
|
.forEach((newIndex) => {
|
|
@@ -595,32 +487,15 @@ export const ensureDbSchema =
|
|
|
595
487
|
});
|
|
596
488
|
|
|
597
489
|
for (const query of queries) {
|
|
598
|
-
|
|
599
|
-
if (!result.ok) return result;
|
|
490
|
+
deps.sqlite.exec(query);
|
|
600
491
|
}
|
|
601
|
-
return ok();
|
|
602
492
|
};
|
|
603
493
|
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
"id" text,
|
|
607
|
-
${sql.raw(
|
|
608
|
-
`${[...systemColumns, ...columns]
|
|
609
|
-
// With strict tables and any type, data is preserved exactly as received
|
|
610
|
-
// without any type affinity coercion. This allows storing any data type
|
|
611
|
-
// while maintaining strict null enforcement for primary key columns.
|
|
612
|
-
// TODO: Use proper SQLite types for system columns (text for createdAt,
|
|
613
|
-
// updatedAt, ownerId, integer for isDeleted) instead of "any".
|
|
614
|
-
.map((name) => `${sql.identifier(name).sql} any`)
|
|
615
|
-
.join(", ")}, `,
|
|
616
|
-
)}
|
|
617
|
-
primary key ("ownerId", "id")
|
|
618
|
-
)
|
|
619
|
-
without rowid, strict;
|
|
620
|
-
`;
|
|
494
|
+
export const getEvoluSqliteSchema = (deps: SqliteDep) => (): SqliteSchema =>
|
|
495
|
+
getSqliteSchema(deps)({ excludeIndexNamePrefix: "evolu_" });
|
|
621
496
|
|
|
622
497
|
// https://kysely.dev/docs/recipes/splitting-query-building-and-execution
|
|
623
|
-
export const kysely = new Kysely.Kysely({
|
|
498
|
+
export const kysely = /*#__PURE__*/ new Kysely.Kysely({
|
|
624
499
|
dialect: {
|
|
625
500
|
createAdapter: () => new Kysely.SqliteAdapter(),
|
|
626
501
|
createDriver: () => new Kysely.DummyDriver(),
|
|
@@ -632,3 +507,18 @@ export const kysely = new Kysely.Kysely({
|
|
|
632
507
|
});
|
|
633
508
|
|
|
634
509
|
const createIndex = kysely.schema.createIndex.bind(kysely.schema);
|
|
510
|
+
|
|
511
|
+
const createAppTable = (tableName: string, columns: ReadonlySet<string>) => sql`
|
|
512
|
+
create table ${sql.identifier(tableName)} (
|
|
513
|
+
"id" text,
|
|
514
|
+
${sql.raw(
|
|
515
|
+
[...systemColumns, ...columns]
|
|
516
|
+
// In STRICT tables, ANY columns accept any SQLite storage class without
|
|
517
|
+
// affinity coercion, while the primary key still enforces non-nullness.
|
|
518
|
+
.map((name) => `${sql.identifier(name).sql} any`)
|
|
519
|
+
.join(", "),
|
|
520
|
+
)},
|
|
521
|
+
primary key ("ownerId", "id")
|
|
522
|
+
)
|
|
523
|
+
without rowid, strict;
|
|
524
|
+
`;
|