@evolu/common 6.0.1-preview.9 → 7.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 +2 -2
- package/dist/src/Array.d.ts +256 -12
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +215 -9
- package/dist/src/Assert.d.ts +0 -13
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +0 -15
- package/dist/src/Brand.d.ts +75 -0
- package/dist/src/Brand.d.ts.map +1 -0
- package/dist/src/Brand.js +1 -0
- package/dist/src/Buffer.d.ts +1 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +8 -7
- package/dist/src/Cache.d.ts +44 -0
- package/dist/src/Cache.d.ts.map +1 -0
- package/dist/src/Cache.js +52 -0
- package/dist/src/Callbacks.d.ts +45 -12
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +14 -7
- package/dist/src/Console.d.ts +31 -6
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +72 -9
- package/dist/src/Crypto.d.ts +56 -42
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +40 -53
- package/dist/src/Evolu/Db.d.ts +162 -74
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +284 -702
- package/dist/src/Evolu/Diff.d.ts +3 -3
- package/dist/src/Evolu/Diff.d.ts.map +1 -1
- package/dist/src/Evolu/Diff.js +7 -5
- package/dist/src/Evolu/Evolu.d.ts +213 -133
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +189 -180
- package/dist/src/Evolu/Internal.d.ts +0 -2
- package/dist/src/Evolu/Internal.d.ts.map +1 -1
- package/dist/src/Evolu/Internal.js +0 -2
- package/dist/src/Evolu/LocalAuth.d.ts +150 -0
- package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
- package/dist/src/Evolu/LocalAuth.js +174 -0
- package/dist/src/Evolu/Owner.d.ts +273 -120
- package/dist/src/Evolu/Owner.d.ts.map +1 -1
- package/dist/src/Evolu/Owner.js +130 -104
- package/dist/src/Evolu/Platform.d.ts +9 -7
- package/dist/src/Evolu/Platform.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.d.ts +268 -240
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +595 -447
- package/dist/src/Evolu/Public.d.ts +6 -8
- package/dist/src/Evolu/Public.d.ts.map +1 -1
- package/dist/src/Evolu/Public.js +2 -3
- package/dist/src/Evolu/PublicKysely.d.ts.map +1 -1
- package/dist/src/Evolu/PublicKysely.js +3 -4
- package/dist/src/Evolu/Query.d.ts +2 -1
- package/dist/src/Evolu/Query.d.ts.map +1 -1
- package/dist/src/Evolu/Query.js +1 -1
- package/dist/src/Evolu/Relay.d.ts +91 -8
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +214 -88
- package/dist/src/Evolu/Schema.d.ts +125 -47
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +175 -31
- package/dist/src/Evolu/Storage.d.ts +249 -27
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +198 -92
- package/dist/src/Evolu/Sync.d.ts +68 -13
- package/dist/src/Evolu/Sync.d.ts.map +1 -1
- package/dist/src/Evolu/Sync.js +469 -20
- package/dist/src/Evolu/Timestamp.d.ts +83 -30
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +79 -34
- package/dist/src/Identicon.d.ts +35 -0
- package/dist/src/Identicon.d.ts.map +1 -0
- package/dist/src/Identicon.js +143 -0
- package/dist/src/Instances.d.ts +34 -0
- package/dist/src/Instances.d.ts.map +1 -0
- package/dist/src/Instances.js +44 -0
- package/dist/src/Number.d.ts +4 -3
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +5 -4
- package/dist/src/Object.d.ts +10 -4
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +9 -3
- package/dist/src/Platform.d.ts +20 -0
- package/dist/src/Platform.d.ts.map +1 -0
- package/dist/src/Platform.js +22 -0
- package/dist/src/Random.d.ts +3 -2
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Relation.d.ts +79 -0
- package/dist/src/Relation.d.ts.map +1 -0
- package/dist/src/Relation.js +127 -0
- package/dist/src/Resources.d.ts +118 -0
- package/dist/src/Resources.d.ts.map +1 -0
- package/dist/src/Resources.js +197 -0
- package/dist/src/Result.d.ts +184 -52
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +30 -241
- package/dist/src/Skiplist.js +2 -1
- package/dist/src/Sqlite.d.ts +89 -5
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +136 -9
- package/dist/src/Task.d.ts +586 -0
- package/dist/src/Task.d.ts.map +1 -0
- package/dist/src/Task.js +469 -0
- package/dist/src/Time.d.ts +66 -1
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +99 -5
- package/dist/src/Type.d.ts +676 -343
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +718 -467
- package/dist/src/Types.d.ts +1 -75
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/WebSocket.d.ts +5 -2
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +12 -18
- package/dist/src/Worker.d.ts +39 -11
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +22 -4
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -3
- package/package.json +14 -13
- package/src/Array.ts +301 -19
- package/src/Assert.ts +0 -21
- package/src/Brand.ts +75 -0
- package/src/Buffer.ts +7 -7
- package/src/Cache.ts +85 -0
- package/src/Callbacks.ts +62 -22
- package/src/Console.ts +91 -11
- package/src/Crypto.ts +94 -90
- package/src/Evolu/Db.ts +519 -1026
- package/src/Evolu/Diff.ts +7 -5
- package/src/Evolu/Evolu.ts +476 -360
- package/src/Evolu/Internal.ts +0 -2
- package/src/Evolu/LocalAuth.ts +463 -0
- package/src/Evolu/Owner.ts +355 -228
- package/src/Evolu/Platform.ts +9 -9
- package/src/Evolu/Protocol.ts +846 -742
- package/src/Evolu/Public.ts +7 -14
- package/src/Evolu/PublicKysely.ts +4 -5
- package/src/Evolu/Query.ts +3 -2
- package/src/Evolu/Relay.ts +406 -103
- package/src/Evolu/Schema.ts +323 -91
- package/src/Evolu/Storage.ts +559 -137
- package/src/Evolu/Sync.ts +819 -36
- package/src/Evolu/Timestamp.ts +90 -58
- package/src/Identicon.ts +197 -0
- package/src/Instances.ts +90 -0
- package/src/Number.ts +6 -10
- package/src/Object.ts +13 -5
- package/src/Platform.ts +26 -0
- package/src/Random.ts +3 -2
- package/src/Relation.ts +234 -0
- package/src/Resources.ts +367 -0
- package/src/Result.ts +191 -54
- package/src/Skiplist.ts +1 -1
- package/src/Sqlite.ts +152 -17
- package/src/Task.ts +901 -0
- package/src/Time.ts +180 -5
- package/src/Type.ts +1135 -730
- package/src/Types.ts +1 -77
- package/src/WebSocket.ts +27 -25
- package/src/Worker.ts +72 -23
- package/src/index.ts +8 -3
- package/dist/src/Evolu/Config.d.ts +0 -69
- package/dist/src/Evolu/Config.d.ts.map +0 -1
- package/dist/src/Evolu/Config.js +0 -9
- package/dist/src/Evolu/Kysely.d.ts +0 -6
- package/dist/src/Evolu/Kysely.d.ts.map +0 -1
- package/dist/src/Evolu/Kysely.js +0 -21
- package/dist/src/ManyToManyMap.d.ts +0 -26
- package/dist/src/ManyToManyMap.d.ts.map +0 -1
- package/dist/src/ManyToManyMap.js +0 -92
- package/dist/src/NanoId.d.ts +0 -27
- package/dist/src/NanoId.d.ts.map +0 -1
- package/dist/src/NanoId.js +0 -6
- package/dist/src/Promise.d.ts +0 -180
- package/dist/src/Promise.d.ts.map +0 -1
- package/dist/src/Promise.js +0 -176
- package/src/Evolu/Config.ts +0 -83
- package/src/Evolu/Kysely.ts +0 -38
- package/src/ManyToManyMap.ts +0 -140
- package/src/NanoId.ts +0 -39
- package/src/Promise.ts +0 -295
package/src/Evolu/Schema.ts
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { pack } from "msgpackr";
|
|
3
|
-
import { assert } from "../Assert.js";
|
|
1
|
+
import * as Kysely from "kysely";
|
|
4
2
|
import { mapObject, objectToEntries, ReadonlyRecord } from "../Object.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { ok, Result } from "../Result.js";
|
|
4
|
+
import {
|
|
5
|
+
SafeSql,
|
|
6
|
+
sql,
|
|
7
|
+
SqliteBoolean,
|
|
8
|
+
SqliteDep,
|
|
9
|
+
SqliteError,
|
|
10
|
+
SqliteQuery,
|
|
11
|
+
SqliteQueryOptions,
|
|
12
|
+
SqliteValue,
|
|
13
|
+
} from "../Sqlite.js";
|
|
7
14
|
import {
|
|
8
15
|
AnyType,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
IdType,
|
|
16
|
+
array,
|
|
17
|
+
createIdFromString,
|
|
18
|
+
DateIso,
|
|
19
|
+
IdBytes,
|
|
14
20
|
InferErrors,
|
|
15
21
|
InferInput,
|
|
16
22
|
InferType,
|
|
23
|
+
maxMutationSize,
|
|
17
24
|
MergeObjectTypeErrors,
|
|
18
25
|
nullableToOptional,
|
|
19
26
|
NullableToOptionalProps,
|
|
@@ -23,16 +30,18 @@ import {
|
|
|
23
30
|
omit,
|
|
24
31
|
optional,
|
|
25
32
|
OptionalType,
|
|
33
|
+
String,
|
|
34
|
+
TableId,
|
|
26
35
|
Type,
|
|
27
|
-
|
|
36
|
+
ValidMutationSize,
|
|
37
|
+
validMutationSize,
|
|
38
|
+
ValidMutationSizeError,
|
|
28
39
|
} from "../Type.js";
|
|
29
40
|
import { Simplify } from "../Types.js";
|
|
30
|
-
import {
|
|
31
|
-
import { createIndexes, DbIndexesBuilder } from "./Kysely.js";
|
|
32
|
-
import { AppOwner, ShardOwner, SharedOwner } from "./Owner.js";
|
|
33
|
-
import { BinaryId, maxProtocolMessageRangesSize } from "./Protocol.js";
|
|
41
|
+
import { AppOwner, OwnerId } from "./Owner.js";
|
|
34
42
|
import { Query, Row } from "./Query.js";
|
|
35
|
-
import {
|
|
43
|
+
import { CrdtMessage, DbChange } from "./Storage.js";
|
|
44
|
+
import { Timestamp, TimestampBytes } from "./Timestamp.js";
|
|
36
45
|
|
|
37
46
|
/**
|
|
38
47
|
* Defines the schema of an Evolu database.
|
|
@@ -71,7 +80,7 @@ import { BinaryTimestamp } from "./Timestamp.js";
|
|
|
71
80
|
export type EvoluSchema = ReadonlyRecord<
|
|
72
81
|
string,
|
|
73
82
|
// TypeScript errors are cryptic so we use ValidateSchema.
|
|
74
|
-
ReadonlyRecord<string, Type<any, any, any, any, any>>
|
|
83
|
+
ReadonlyRecord<string, Type<any, any, any, any, any, any>>
|
|
75
84
|
>;
|
|
76
85
|
|
|
77
86
|
/**
|
|
@@ -84,17 +93,17 @@ export type EvoluSchema = ReadonlyRecord<
|
|
|
84
93
|
*
|
|
85
94
|
* 1. All tables must have an 'id' column
|
|
86
95
|
* 2. The 'id' column must be a branded ID type (created with id() function)
|
|
87
|
-
* 3. Tables cannot use
|
|
96
|
+
* 3. Tables cannot use system column names (createdAt, updatedAt, isDeleted)
|
|
88
97
|
* 4. All column types must be compatible with SQLite (extend SqliteValue)
|
|
89
98
|
*/
|
|
90
99
|
export type ValidateSchema<S extends EvoluSchema> =
|
|
91
100
|
ValidateSchemaHasId<S> extends never
|
|
92
101
|
? ValidateIdColumnType<S> extends never
|
|
93
|
-
?
|
|
102
|
+
? ValidateNoSystemColumns<S> extends never
|
|
94
103
|
? ValidateColumnTypes<S> extends never
|
|
95
104
|
? S
|
|
96
105
|
: ValidateColumnTypes<S>
|
|
97
|
-
:
|
|
106
|
+
: ValidateNoSystemColumns<S>
|
|
98
107
|
: ValidateIdColumnType<S>
|
|
99
108
|
: ValidateSchemaHasId<S>;
|
|
100
109
|
|
|
@@ -111,20 +120,24 @@ export type ValidateIdColumnType<S extends EvoluSchema> =
|
|
|
111
120
|
keyof S extends infer TableName
|
|
112
121
|
? TableName extends keyof S
|
|
113
122
|
? "id" extends keyof S[TableName]
|
|
114
|
-
? S[TableName]["id"] extends
|
|
123
|
+
? S[TableName]["id"] extends TableId<any>
|
|
115
124
|
? never
|
|
116
125
|
: SchemaValidationError<`Table "${TableName & string}" id column must be a branded ID type (created with id("${TableName & string}")).`>
|
|
117
126
|
: never
|
|
118
127
|
: never
|
|
119
128
|
: never;
|
|
120
129
|
|
|
121
|
-
export type
|
|
130
|
+
export type ValidateNoSystemColumns<S extends EvoluSchema> =
|
|
122
131
|
keyof S extends infer TableName
|
|
123
132
|
? TableName extends keyof S
|
|
124
133
|
? keyof S[TableName] extends infer ColumnName
|
|
125
134
|
? ColumnName extends keyof S[TableName]
|
|
126
|
-
? ColumnName extends
|
|
127
|
-
|
|
135
|
+
? ColumnName extends
|
|
136
|
+
| "createdAt"
|
|
137
|
+
| "updatedAt"
|
|
138
|
+
| "isDeleted"
|
|
139
|
+
| "ownerId"
|
|
140
|
+
? SchemaValidationError<`Table "${TableName & string}" uses system column name "${ColumnName & string}". System columns (createdAt, updatedAt, isDeleted, ownerId) are added automatically.`>
|
|
128
141
|
: never
|
|
129
142
|
: never
|
|
130
143
|
: never
|
|
@@ -148,9 +161,13 @@ export type ValidateColumnTypes<S extends EvoluSchema> =
|
|
|
148
161
|
export type SchemaValidationError<Message extends string> =
|
|
149
162
|
`❌ Schema Error: ${Message}`;
|
|
150
163
|
|
|
164
|
+
export type IndexesConfig = (
|
|
165
|
+
create: (indexName: string) => Kysely.CreateIndexBuilder,
|
|
166
|
+
) => ReadonlyArray<Kysely.CreateIndexBuilder<any>>;
|
|
167
|
+
|
|
151
168
|
export const evoluSchemaToDbSchema = (
|
|
152
169
|
schema: EvoluSchema,
|
|
153
|
-
|
|
170
|
+
indexesConfig?: IndexesConfig,
|
|
154
171
|
): DbSchema => {
|
|
155
172
|
const tables = objectToEntries(schema).map(([tableName, table]) => ({
|
|
156
173
|
name: tableName,
|
|
@@ -159,34 +176,33 @@ export const evoluSchemaToDbSchema = (
|
|
|
159
176
|
.map(([k]) => k),
|
|
160
177
|
}));
|
|
161
178
|
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
179
|
+
const indexes = indexesConfig
|
|
180
|
+
? indexesConfig(createIndex).map(
|
|
181
|
+
(index): DbIndex => ({
|
|
182
|
+
name: index.toOperationNode().name.name,
|
|
183
|
+
sql: index.compile().sql,
|
|
184
|
+
}),
|
|
185
|
+
)
|
|
186
|
+
: [];
|
|
168
187
|
|
|
169
|
-
return
|
|
188
|
+
return { tables, indexes };
|
|
170
189
|
};
|
|
171
190
|
|
|
172
191
|
export type CreateQuery<S extends EvoluSchema> = <R extends Row>(
|
|
173
192
|
queryCallback: (
|
|
174
193
|
db: Pick<
|
|
175
|
-
Kysely<
|
|
194
|
+
Kysely.Kysely<
|
|
176
195
|
{
|
|
177
196
|
[Table in keyof S]: {
|
|
178
|
-
readonly [Column in keyof S[Table]]: Column extends
|
|
179
|
-
| "id"
|
|
180
|
-
| "createdAt"
|
|
181
|
-
| "updatedAt"
|
|
197
|
+
readonly [Column in keyof S[Table]]: Column extends "id"
|
|
182
198
|
? InferType<S[Table][Column]>
|
|
183
199
|
: InferType<S[Table][Column]> | null;
|
|
184
|
-
} &
|
|
200
|
+
} & SystemColumns;
|
|
185
201
|
} & {
|
|
186
202
|
readonly evolu_history: {
|
|
187
|
-
readonly timestamp:
|
|
203
|
+
readonly timestamp: TimestampBytes;
|
|
188
204
|
readonly table: keyof S;
|
|
189
|
-
readonly id:
|
|
205
|
+
readonly id: IdBytes;
|
|
190
206
|
readonly column: string;
|
|
191
207
|
readonly value: SqliteValue;
|
|
192
208
|
};
|
|
@@ -194,16 +210,28 @@ export type CreateQuery<S extends EvoluSchema> = <R extends Row>(
|
|
|
194
210
|
>,
|
|
195
211
|
"selectFrom" | "fn" | "with" | "withRecursive"
|
|
196
212
|
>,
|
|
197
|
-
) => SelectQueryBuilder<any, any, R>,
|
|
213
|
+
) => Kysely.SelectQueryBuilder<any, any, R>,
|
|
198
214
|
options?: SqliteQueryOptions,
|
|
199
215
|
) => Query<Simplify<R>>;
|
|
200
216
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
217
|
+
/**
|
|
218
|
+
* System columns that are implicitly defined by Evolu.
|
|
219
|
+
*
|
|
220
|
+
* - `createdAt`: Set by Evolu on row creation, derived from {@link Timestamp}.
|
|
221
|
+
* - `updatedAt`: Set by Evolu on every row change, derived from {@link Timestamp}.
|
|
222
|
+
* - `isDeleted`: Soft delete flag created by Evolu and used by the developer to
|
|
223
|
+
* mark rows as deleted.
|
|
224
|
+
* - `ownerId`: Represents ownership and logically partitions the database.
|
|
225
|
+
*/
|
|
226
|
+
export const SystemColumns = object({
|
|
227
|
+
createdAt: DateIso,
|
|
228
|
+
updatedAt: DateIso,
|
|
204
229
|
isDeleted: nullOr(SqliteBoolean),
|
|
230
|
+
ownerId: OwnerId,
|
|
205
231
|
});
|
|
206
|
-
export type
|
|
232
|
+
export type SystemColumns = typeof SystemColumns.Type;
|
|
233
|
+
|
|
234
|
+
export const systemColumns = Object.keys(SystemColumns.props);
|
|
207
235
|
|
|
208
236
|
export type MutationKind = "insert" | "update" | "upsert";
|
|
209
237
|
|
|
@@ -229,7 +257,48 @@ export type MutationMapping<
|
|
|
229
257
|
: UpsertableProps<P>;
|
|
230
258
|
|
|
231
259
|
export interface MutationOptions {
|
|
260
|
+
/**
|
|
261
|
+
* Called after the mutation is completed and the local state is updated.
|
|
262
|
+
* Useful for triggering side effects (e.g., notifications, UI updates) after
|
|
263
|
+
* insert, update, or upsert.
|
|
264
|
+
*/
|
|
232
265
|
readonly onComplete?: () => void;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Specifies the owner ID for this mutation. If omitted, the default
|
|
269
|
+
* {@link AppOwner} is used.
|
|
270
|
+
*
|
|
271
|
+
* The owner must be used with `evolu.useOwner()` to enable sync. Mutations
|
|
272
|
+
* with unused owners are stored locally but not synced until the owner is
|
|
273
|
+
* used.
|
|
274
|
+
*
|
|
275
|
+
* ### Example
|
|
276
|
+
*
|
|
277
|
+
* ```ts
|
|
278
|
+
* // Partition your own data by project (derived from your AppOwner)
|
|
279
|
+
* const projectOwner = deriveShardOwner(appOwner, [
|
|
280
|
+
* "project",
|
|
281
|
+
* projectId,
|
|
282
|
+
* ]);
|
|
283
|
+
* evolu.insert(
|
|
284
|
+
* "task",
|
|
285
|
+
* { title: "Task 1" },
|
|
286
|
+
* { ownerId: projectOwner.id },
|
|
287
|
+
* );
|
|
288
|
+
*
|
|
289
|
+
* // Collaborative data (independent owner shared with others)
|
|
290
|
+
* const sharedOwner = createSharedOwner(sharedSecret);
|
|
291
|
+
* evolu.insert(
|
|
292
|
+
* "comment",
|
|
293
|
+
* { text: "Hello" },
|
|
294
|
+
* { ownerId: sharedOwner.id },
|
|
295
|
+
* );
|
|
296
|
+
* ```
|
|
297
|
+
*
|
|
298
|
+
* @experimental
|
|
299
|
+
*/
|
|
300
|
+
readonly ownerId?: OwnerId;
|
|
301
|
+
|
|
233
302
|
/**
|
|
234
303
|
* Only validate, don't mutate.
|
|
235
304
|
*
|
|
@@ -237,50 +306,17 @@ export interface MutationOptions {
|
|
|
237
306
|
* `onlyValidate: true`.
|
|
238
307
|
*/
|
|
239
308
|
readonly onlyValidate?: boolean;
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* The owner to use for this mutation. Can be a {@link ShardOwner} for sharding
|
|
243
|
-
* app data or a {@link SharedOwner} for collaborative write access. If
|
|
244
|
-
* omitted, defaults to the app's {@link AppOwner}.
|
|
245
|
-
*/
|
|
246
|
-
readonly owner?: ShardOwner | SharedOwner;
|
|
247
309
|
}
|
|
248
310
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
* various optimizations.
|
|
254
|
-
*/
|
|
255
|
-
export const maxMutationSize = 655360;
|
|
256
|
-
|
|
257
|
-
const validMutationSize = <T extends AnyType>(type: T) =>
|
|
258
|
-
brand("ValidMutationSize", type, (value) =>
|
|
259
|
-
pack(value).byteLength <= maxMutationSize
|
|
260
|
-
? ok(value)
|
|
261
|
-
: err<ValidMutationSizeError>({ type: "ValidMutationSize", value }),
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
export interface ValidMutationSizeError
|
|
265
|
-
extends TypeError<"ValidMutationSize"> {}
|
|
266
|
-
|
|
267
|
-
export const formatValidMutationSizeError =
|
|
268
|
-
createTypeErrorFormatter<ValidMutationSizeError>(
|
|
269
|
-
(error) =>
|
|
270
|
-
`The mutation size exceeds the maximum limit of ${maxMutationSize} bytes. The provided mutation has a size of ${pack(error.value).byteLength} bytes.`,
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
export type ValidMutationSize<Props extends Record<string, AnyType>> =
|
|
274
|
-
BrandType<
|
|
275
|
-
ObjectType<Props>,
|
|
276
|
-
"ValidMutationSize",
|
|
277
|
-
ValidMutationSizeError,
|
|
278
|
-
InferErrors<ObjectType<Props>>
|
|
279
|
-
>;
|
|
311
|
+
export interface MutationChange extends DbChange {
|
|
312
|
+
/** Owner of the change. If undefined, the change belongs to the AppOwner. */
|
|
313
|
+
readonly ownerId?: OwnerId | undefined;
|
|
314
|
+
}
|
|
280
315
|
|
|
281
316
|
/**
|
|
282
317
|
* Type Factory to create insertable {@link Type}. It makes nullable Types
|
|
283
|
-
* optional, omits Id, and ensures the
|
|
318
|
+
* optional (so they are not required), omits Id, and ensures the
|
|
319
|
+
* {@link maxMutationSize}.
|
|
284
320
|
*
|
|
285
321
|
* ### Example
|
|
286
322
|
*
|
|
@@ -310,7 +346,7 @@ export type Insertable<Props extends Record<string, AnyType>> = InferInput<
|
|
|
310
346
|
|
|
311
347
|
/**
|
|
312
348
|
* Type Factory to create updateable {@link Type}. It makes everything except for
|
|
313
|
-
* the `id` column
|
|
349
|
+
* the `id` column optional (so they are not required) and ensures the
|
|
314
350
|
* {@link maxMutationSize}.
|
|
315
351
|
*
|
|
316
352
|
* ### Example
|
|
@@ -346,25 +382,44 @@ export type Updateable<Props extends Record<string, AnyType>> = InferInput<
|
|
|
346
382
|
>;
|
|
347
383
|
|
|
348
384
|
/**
|
|
349
|
-
* Type Factory to create upsertable Type. It makes nullable Types optional
|
|
350
|
-
* ensures the {@link maxMutationSize}.
|
|
385
|
+
* Type Factory to create an upsertable Type. It makes nullable Types optional
|
|
386
|
+
* (so they are not required) and ensures the {@link maxMutationSize}.
|
|
387
|
+
*
|
|
388
|
+
* Upsert is like insert, except it requires an ID. It's useful for inserting
|
|
389
|
+
* rows with external ID via {@link createIdFromString}.
|
|
390
|
+
*
|
|
391
|
+
* Note that it's not possible to upsert a row with `createdAt` nor `updatedAt`,
|
|
392
|
+
* because they are derived from {@link CrdtMessage} timestamp. For external
|
|
393
|
+
* createdAt, use a different column.
|
|
351
394
|
*
|
|
352
395
|
* ### Example
|
|
353
396
|
*
|
|
354
397
|
* ```ts
|
|
355
398
|
* const UpsertableTodo = upsertable(Schema.todo);
|
|
356
399
|
* type UpsertableTodo = typeof UpsertableTodo.Type;
|
|
357
|
-
* const todo = UpsertableTodo.from({
|
|
400
|
+
* const todo = UpsertableTodo.from({
|
|
401
|
+
* id,
|
|
402
|
+
* title,
|
|
403
|
+
* });
|
|
358
404
|
* if (!todo.ok) return; // handle errors
|
|
359
405
|
* ```
|
|
360
406
|
*/
|
|
361
407
|
export const upsertable = <Props extends Record<string, AnyType>>(
|
|
362
408
|
props: Props,
|
|
363
|
-
): ValidMutationSize<UpsertableProps<Props>> =>
|
|
364
|
-
|
|
409
|
+
): ValidMutationSize<UpsertableProps<Props>> => {
|
|
410
|
+
const propsWithDefaults = {
|
|
411
|
+
...props,
|
|
412
|
+
isDeleted: optional(SqliteBoolean),
|
|
413
|
+
};
|
|
414
|
+
return validMutationSize(nullableToOptional(propsWithDefaults));
|
|
415
|
+
};
|
|
365
416
|
|
|
366
417
|
export type UpsertableProps<Props extends Record<string, AnyType>> =
|
|
367
|
-
NullableToOptionalProps<
|
|
418
|
+
NullableToOptionalProps<
|
|
419
|
+
Props & {
|
|
420
|
+
isDeleted: OptionalType<typeof SqliteBoolean>;
|
|
421
|
+
}
|
|
422
|
+
>;
|
|
368
423
|
|
|
369
424
|
export type Upsertable<Props extends Record<string, AnyType>> = InferInput<
|
|
370
425
|
ObjectType<UpsertableProps<Props>>
|
|
@@ -387,3 +442,180 @@ export type InferColumnErrors<
|
|
|
387
442
|
MutationMapping<T, M>[Column]
|
|
388
443
|
>;
|
|
389
444
|
}[keyof MutationMapping<T, M>];
|
|
445
|
+
|
|
446
|
+
export const DbTable = object({
|
|
447
|
+
name: String,
|
|
448
|
+
columns: array(String),
|
|
449
|
+
});
|
|
450
|
+
export type DbTable = typeof DbTable.Type;
|
|
451
|
+
|
|
452
|
+
export const DbIndex = object({ name: String, sql: String });
|
|
453
|
+
export type DbIndex = typeof DbIndex.Type;
|
|
454
|
+
|
|
455
|
+
export const DbSchema = object({
|
|
456
|
+
tables: array(DbTable),
|
|
457
|
+
indexes: array(DbIndex),
|
|
458
|
+
});
|
|
459
|
+
export type DbSchema = typeof DbSchema.Type;
|
|
460
|
+
|
|
461
|
+
/** Get the current database schema by reading SQLite metadata. */
|
|
462
|
+
export const getDbSchema =
|
|
463
|
+
(deps: SqliteDep) =>
|
|
464
|
+
({ allIndexes = false }: { allIndexes?: boolean } = {}): Result<
|
|
465
|
+
DbSchema,
|
|
466
|
+
SqliteError
|
|
467
|
+
> => {
|
|
468
|
+
const map = new Map<string, Array<string>>();
|
|
469
|
+
|
|
470
|
+
const tableAndColumnInfoRows = deps.sqlite.exec(sql`
|
|
471
|
+
select
|
|
472
|
+
sqlite_master.name as tableName,
|
|
473
|
+
table_info.name as columnName
|
|
474
|
+
from
|
|
475
|
+
sqlite_master
|
|
476
|
+
join pragma_table_info(sqlite_master.name) as table_info;
|
|
477
|
+
`);
|
|
478
|
+
|
|
479
|
+
if (!tableAndColumnInfoRows.ok) return tableAndColumnInfoRows;
|
|
480
|
+
|
|
481
|
+
tableAndColumnInfoRows.value.rows.forEach((row) => {
|
|
482
|
+
const { tableName, columnName } = row as unknown as {
|
|
483
|
+
tableName: string;
|
|
484
|
+
columnName: string;
|
|
485
|
+
};
|
|
486
|
+
if (!map.has(tableName)) map.set(tableName, []);
|
|
487
|
+
map.get(tableName)?.push(columnName);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const tables = Array.from(map, ([name, columns]) => ({ name, columns }));
|
|
491
|
+
|
|
492
|
+
const indexesRows = deps.sqlite.exec(
|
|
493
|
+
allIndexes
|
|
494
|
+
? sql`
|
|
495
|
+
select name, sql
|
|
496
|
+
from sqlite_master
|
|
497
|
+
where type = 'index' and name not like 'sqlite_%';
|
|
498
|
+
`
|
|
499
|
+
: sql`
|
|
500
|
+
select name, sql
|
|
501
|
+
from sqlite_master
|
|
502
|
+
where
|
|
503
|
+
type = 'index'
|
|
504
|
+
and name not like 'sqlite_%'
|
|
505
|
+
and name not like 'evolu_%';
|
|
506
|
+
`,
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
if (!indexesRows.ok) return indexesRows;
|
|
510
|
+
|
|
511
|
+
const indexes = indexesRows.value.rows.map(
|
|
512
|
+
(row): DbIndex => ({
|
|
513
|
+
name: row.name as string,
|
|
514
|
+
/**
|
|
515
|
+
* SQLite returns "CREATE INDEX" for "create index" for some reason.
|
|
516
|
+
* Other keywords remain unchanged. We have to normalize the casing for
|
|
517
|
+
* {@link indexesAreEqual} manually.
|
|
518
|
+
*/
|
|
519
|
+
sql: (row.sql as string)
|
|
520
|
+
.replace("CREATE INDEX", "create index")
|
|
521
|
+
.replace("CREATE UNIQUE INDEX", "create unique index"),
|
|
522
|
+
}),
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
return ok({ tables, indexes });
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const indexesAreEqual = (self: DbIndex, that: DbIndex): boolean =>
|
|
529
|
+
self.name === that.name && self.sql === that.sql;
|
|
530
|
+
|
|
531
|
+
export const ensureDbSchema =
|
|
532
|
+
(deps: SqliteDep) =>
|
|
533
|
+
(
|
|
534
|
+
newSchema: DbSchema,
|
|
535
|
+
currentSchema: DbSchema,
|
|
536
|
+
options?: { ignoreIndexes: boolean },
|
|
537
|
+
): Result<void, SqliteError> => {
|
|
538
|
+
const queries: Array<SqliteQuery> = [];
|
|
539
|
+
|
|
540
|
+
newSchema.tables.forEach((newTable) => {
|
|
541
|
+
const currentTable = currentSchema.tables.find(
|
|
542
|
+
(t) => t.name === newTable.name,
|
|
543
|
+
);
|
|
544
|
+
if (!currentTable) {
|
|
545
|
+
queries.push(createAppTable(newTable));
|
|
546
|
+
} else {
|
|
547
|
+
newTable.columns
|
|
548
|
+
.filter((newColumn) => !currentTable.columns.includes(newColumn))
|
|
549
|
+
.forEach((newColumn) => {
|
|
550
|
+
queries.push(sql`
|
|
551
|
+
alter table ${sql.identifier(newTable.name)}
|
|
552
|
+
add column ${sql.identifier(newColumn)} blob;
|
|
553
|
+
`);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
if (options?.ignoreIndexes !== true) {
|
|
559
|
+
// Remove current indexes that are not in the newSchema.
|
|
560
|
+
currentSchema.indexes
|
|
561
|
+
.filter(
|
|
562
|
+
(currentIndex) =>
|
|
563
|
+
!newSchema.indexes.some((newIndex) =>
|
|
564
|
+
indexesAreEqual(newIndex, currentIndex),
|
|
565
|
+
),
|
|
566
|
+
)
|
|
567
|
+
.forEach((index) => {
|
|
568
|
+
queries.push(sql`drop index ${sql.identifier(index.name)};`);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// Add new indexes that are not in the currentSchema.
|
|
572
|
+
newSchema.indexes
|
|
573
|
+
.filter(
|
|
574
|
+
(newIndex) =>
|
|
575
|
+
!currentSchema.indexes.some((currentIndex) =>
|
|
576
|
+
indexesAreEqual(newIndex, currentIndex),
|
|
577
|
+
),
|
|
578
|
+
)
|
|
579
|
+
.forEach((newIndex) => {
|
|
580
|
+
queries.push({ sql: `${newIndex.sql};` as SafeSql, parameters: [] });
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
for (const query of queries) {
|
|
585
|
+
const result = deps.sqlite.exec(query);
|
|
586
|
+
if (!result.ok) return result;
|
|
587
|
+
}
|
|
588
|
+
return ok();
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
const createAppTable = (table: DbTable) => sql`
|
|
592
|
+
create table ${sql.identifier(table.name)} (
|
|
593
|
+
"id" text,
|
|
594
|
+
${sql.raw(
|
|
595
|
+
`${systemColumns
|
|
596
|
+
.concat(table.columns)
|
|
597
|
+
.filter((c) => c !== "id")
|
|
598
|
+
// With strict tables and any type, data is preserved exactly as received
|
|
599
|
+
// without any type affinity coercion. This allows storing any data type
|
|
600
|
+
// while maintaining strict null enforcement for primary key columns.
|
|
601
|
+
.map((name) => `${sql.identifier(name).sql} any`)
|
|
602
|
+
.join(", ")}`,
|
|
603
|
+
)},
|
|
604
|
+
primary key ("ownerId", "id")
|
|
605
|
+
)
|
|
606
|
+
without rowid, strict;
|
|
607
|
+
`;
|
|
608
|
+
|
|
609
|
+
// https://kysely.dev/docs/recipes/splitting-query-building-and-execution
|
|
610
|
+
export const kysely = new Kysely.Kysely({
|
|
611
|
+
dialect: {
|
|
612
|
+
createAdapter: () => new Kysely.SqliteAdapter(),
|
|
613
|
+
createDriver: () => new Kysely.DummyDriver(),
|
|
614
|
+
createIntrospector() {
|
|
615
|
+
throw new Error("Not implemeneted");
|
|
616
|
+
},
|
|
617
|
+
createQueryCompiler: () => new Kysely.SqliteQueryCompiler(),
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
const createIndex = kysely.schema.createIndex.bind(kysely.schema);
|