@evolu/common 7.2.2 → 7.3.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/dist/src/Function.d.ts +61 -0
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +19 -0
- package/dist/src/Object.d.ts +32 -1
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +32 -1
- package/dist/src/Order.d.ts +2 -1
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +1 -0
- package/dist/src/Result.d.ts +36 -29
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Sqlite.d.ts +9 -7
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +8 -0
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +6 -4
- package/dist/src/Type.d.ts +30 -2
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +82 -0
- package/dist/src/local-first/Db.d.ts +1 -5
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +112 -117
- package/dist/src/local-first/Evolu.d.ts +3 -0
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +14 -12
- package/dist/src/local-first/LocalAuth.d.ts +2 -2
- package/dist/src/local-first/Owner.d.ts +3 -0
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.d.ts +1 -1
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +12 -12
- package/dist/src/local-first/PublicKysely.js +1 -1
- package/dist/src/local-first/Query.d.ts +54 -6
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +130 -11
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +17 -18
- package/dist/src/local-first/Schema.d.ts +15 -14
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +43 -51
- package/dist/src/local-first/Storage.d.ts +2 -2
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +2 -6
- package/dist/src/local-first/Sync.d.ts +10 -7
- package/dist/src/local-first/Sync.d.ts.map +1 -1
- package/dist/src/local-first/Sync.js +154 -103
- package/dist/src/local-first/Timestamp.d.ts +13 -0
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +14 -1
- package/dist/src/local-first/index.d.ts +0 -1
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +0 -1
- package/package.json +1 -1
- package/src/Function.ts +74 -0
- package/src/Object.ts +40 -1
- package/src/Order.ts +2 -1
- package/src/Result.ts +38 -29
- package/src/Sqlite.ts +18 -7
- package/src/Task.ts +6 -4
- package/src/Type.ts +134 -1
- package/src/local-first/Db.ts +182 -217
- package/src/local-first/Evolu.ts +21 -10
- package/src/local-first/LocalAuth.ts +2 -2
- package/src/local-first/Owner.ts +4 -0
- package/src/local-first/Protocol.ts +13 -19
- package/src/local-first/PublicKysely.ts +1 -1
- package/src/local-first/Query.ts +216 -21
- package/src/local-first/Relay.ts +20 -24
- package/src/local-first/Schema.ts +83 -70
- package/src/local-first/Storage.ts +3 -8
- package/src/local-first/Sync.ts +215 -135
- package/src/local-first/Timestamp.ts +14 -1
- package/src/local-first/index.ts +0 -1
- package/dist/src/local-first/Diff.d.ts +0 -43
- package/dist/src/local-first/Diff.d.ts.map +0 -1
- package/dist/src/local-first/Diff.js +0 -97
- package/src/local-first/Diff.ts +0 -144
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/local-first/Storage.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAGpC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAW,MAAM,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAO,SAAS,EAAE,WAAW,EAAe,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAKL,cAAc,EAGd,WAAW,EAGX,SAAS,EACV,MAAM,YAAY,CAAC;AACpB,OAAO,
|
|
1
|
+
{"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/local-first/Storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import { decrement } from "../Number.js";
|
|
|
6
6
|
import { err, ok } from "../Result.js";
|
|
7
7
|
import { sql, SqliteValue } from "../Sqlite.js";
|
|
8
8
|
import { Boolean, brand, Id, NonNegativeInt, nullOr, object, PositiveInt, record, String, } from "../Type.js";
|
|
9
|
-
import {
|
|
9
|
+
import { systemColumnsWithId } from "./Schema.js";
|
|
10
10
|
import { orderTimestampBytes } from "./Timestamp.js";
|
|
11
11
|
export const fingerprintSize = NonNegativeInt.orThrow(12);
|
|
12
12
|
/** A fingerprint of an empty range. */
|
|
@@ -18,12 +18,8 @@ export const RangeType = {
|
|
|
18
18
|
Timestamps: 2,
|
|
19
19
|
};
|
|
20
20
|
export const DbChangeValues = record(String, SqliteValue);
|
|
21
|
-
// "createdAt" and "updatedAt" are derived from Timestamp.
|
|
22
|
-
// "id" and "isDeleted" are encoded separately to save bytes
|
|
23
|
-
// "ownerId" is part of the protocol message
|
|
24
|
-
const forbiddenSystemColumns = systemColumns.concat("id");
|
|
25
21
|
export const ValidDbChangeValues = brand("ValidDbChangeValues", DbChangeValues, (value) => {
|
|
26
|
-
const invalidColumns =
|
|
22
|
+
const invalidColumns = systemColumnsWithId.filter((key) => key in value);
|
|
27
23
|
if (invalidColumns.length > 0)
|
|
28
24
|
return err({
|
|
29
25
|
type: "ValidDbChangeValues",
|
|
@@ -6,13 +6,11 @@ import { RandomDep } from "../Random.js";
|
|
|
6
6
|
import { Result } from "../Result.js";
|
|
7
7
|
import { SqliteDep, SqliteError } from "../Sqlite.js";
|
|
8
8
|
import { TimeDep } from "../Time.js";
|
|
9
|
-
import { DateIso } from "../Type.js";
|
|
10
9
|
import { CreateWebSocketDep } from "../WebSocket.js";
|
|
11
|
-
import
|
|
12
|
-
import { AppOwner, Owner, OwnerIdBytes, OwnerTransport, ReadonlyOwner } from "./Owner.js";
|
|
10
|
+
import { AppOwner, AppOwnerDep, Owner, OwnerTransport, ReadonlyOwner } from "./Owner.js";
|
|
13
11
|
import { ProtocolError, ProtocolInvalidDataError, ProtocolTimestampMismatchError } from "./Protocol.js";
|
|
14
|
-
import { MutationChange } from "./Schema.js";
|
|
15
|
-
import { BaseSqliteStorage,
|
|
12
|
+
import { DbSchemaDep, MutationChange } from "./Schema.js";
|
|
13
|
+
import { BaseSqliteStorage, Storage } from "./Storage.js";
|
|
16
14
|
import { Millis, Timestamp, TimestampConfigDep, TimestampCounterOverflowError, TimestampDriftError, TimestampTimeOutOfRangeError } from "./Timestamp.js";
|
|
17
15
|
export interface Sync extends Disposable {
|
|
18
16
|
/**
|
|
@@ -49,7 +47,7 @@ export interface SyncConfig {
|
|
|
49
47
|
readonly onError: (error: ProtocolError | ProtocolInvalidDataError | ProtocolTimestampMismatchError | SqliteError | SymmetricCryptoDecryptError | TimestampCounterOverflowError | TimestampDriftError | TimestampTimeOutOfRangeError | TransferableError) => void;
|
|
50
48
|
readonly onReceive: () => void;
|
|
51
49
|
}
|
|
52
|
-
export declare const createSync: (deps: ClockDep & ConsoleDep & CreateWebSocketDep &
|
|
50
|
+
export declare const createSync: (deps: ClockDep & ConsoleDep & CreateWebSocketDep & DbSchemaDep & RandomBytesDep & RandomDep & SqliteDep & SymmetricCryptoDep & TimeDep & TimestampConfigDep) => (config: SyncConfig) => Result<Sync, SqliteError>;
|
|
53
51
|
export interface ClockDep {
|
|
54
52
|
readonly clock: Clock;
|
|
55
53
|
}
|
|
@@ -64,7 +62,12 @@ export interface ClientStorageDep {
|
|
|
64
62
|
readonly storage: ClientStorage;
|
|
65
63
|
}
|
|
66
64
|
export declare const applyLocalOnlyChange: (deps: SqliteDep & TimeDep & AppOwnerDep) => (change: MutationChange) => Result<void, SqliteError>;
|
|
67
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Attempts to apply quarantined messages that may now be valid after a schema
|
|
67
|
+
* update. Messages are quarantined when they reference tables or columns that
|
|
68
|
+
* don't exist in the current schema (e.g., from a newer app version).
|
|
69
|
+
*/
|
|
70
|
+
export declare const tryApplyQuarantinedMessages: (deps: DbSchemaDep & SqliteDep) => () => Result<void, SqliteError>;
|
|
68
71
|
/**
|
|
69
72
|
* TODO: Rework for the new owners API.
|
|
70
73
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sync.d.ts","sourceRoot":"","sources":["../../../src/local-first/Sync.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"Sync.d.ts","sourceRoot":"","sources":["../../../src/local-first/Sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { firstInArray, isNonEmptyReadonlyArray, } from "../Array.js";
|
|
2
|
-
import { assert } from "../Assert.js";
|
|
1
|
+
import { appendToArray, firstInArray, isNonEmptyReadonlyArray, } from "../Array.js";
|
|
2
|
+
import { assert, assertNonEmptyReadonlyArray } from "../Assert.js";
|
|
3
3
|
import { eqArrayNumber } from "../Eq.js";
|
|
4
4
|
import { createTransferableError } from "../Error.js";
|
|
5
5
|
import { constFalse, constTrue } from "../Function.js";
|
|
6
|
-
import { objectToEntries } from "../Object.js";
|
|
6
|
+
import { createRecord, getProperty, objectToEntries } from "../Object.js";
|
|
7
7
|
import { createResources } from "../Resources.js";
|
|
8
8
|
import { err, ok } from "../Result.js";
|
|
9
|
-
import { booleanToSqliteBoolean, sql, SqliteBoolean, sqliteBooleanToBoolean,
|
|
9
|
+
import { booleanToSqliteBoolean, sql, SqliteBoolean, sqliteBooleanToBoolean, } from "../Sqlite.js";
|
|
10
10
|
import { createMutex } from "../Task.js";
|
|
11
|
-
import { idBytesToId, idToIdBytes, } from "../Type.js";
|
|
11
|
+
import { Boolean, idBytesToId, idToIdBytes, } from "../Type.js";
|
|
12
12
|
import { ownerIdBytesToOwnerId, ownerIdToOwnerIdBytes, } from "./Owner.js";
|
|
13
13
|
import { applyProtocolMessageAsClient, createProtocolMessageForSync, createProtocolMessageForUnsubscribe, createProtocolMessageFromCrdtMessages, decryptAndDecodeDbChange, encodeAndEncryptDbChange, SubscriptionFlags, } from "./Protocol.js";
|
|
14
|
+
import { systemColumns } from "./Schema.js";
|
|
14
15
|
import { createBaseSqliteStorage, DbChange, getOwnerUsage, getTimestampInsertStrategy, updateOwnerUsage, } from "./Storage.js";
|
|
15
16
|
import { createInitialTimestamp, receiveTimestamp, sendTimestamp, timestampBytesToTimestamp, timestampToDateIso, timestampToTimestampBytes, } from "./Timestamp.js";
|
|
16
17
|
export const createSync = (deps) => (config) => {
|
|
@@ -237,7 +238,7 @@ const createClientStorage = (deps) => (config) => {
|
|
|
237
238
|
writeMessages: async (ownerIdBytes, encryptedMessages) => {
|
|
238
239
|
const ownerId = ownerIdBytesToOwnerId(ownerIdBytes);
|
|
239
240
|
// Everything is sync now, but we will need async crypto in the future.
|
|
240
|
-
const
|
|
241
|
+
const result = await mutex.withLock(async () => {
|
|
241
242
|
const owner = deps.getSyncOwner(ownerId);
|
|
242
243
|
// Owner can be removed during syncing.
|
|
243
244
|
// `ok(true)` means success, we just skipped the write.
|
|
@@ -267,24 +268,19 @@ const createClientStorage = (deps) => (config) => {
|
|
|
267
268
|
clockTimestamp = nextTimestamp.value;
|
|
268
269
|
}
|
|
269
270
|
if (isNonEmptyReadonlyArray(messages)) {
|
|
270
|
-
const
|
|
271
|
-
if (!
|
|
272
|
-
return
|
|
271
|
+
const result = applyMessages({ ...deps, storage })(owner.id, messages);
|
|
272
|
+
if (!result.ok)
|
|
273
|
+
return result;
|
|
273
274
|
}
|
|
274
|
-
// // Apply local mutations atomically with approved messages
|
|
275
|
-
// for (const change of localMutations) {
|
|
276
|
-
// const result = applyLocalOnlyChange(deps)(change);
|
|
277
|
-
// if (!result.ok) return result;
|
|
278
|
-
// }
|
|
279
275
|
return deps.clock.save(clockTimestamp);
|
|
280
276
|
});
|
|
281
277
|
if (!transaction.ok)
|
|
282
278
|
return transaction;
|
|
283
279
|
return ok(true);
|
|
284
280
|
})();
|
|
285
|
-
if (!
|
|
286
|
-
if (
|
|
287
|
-
config.onError(
|
|
281
|
+
if (!result.ok) {
|
|
282
|
+
if (result.error.type !== "AbortError") {
|
|
283
|
+
config.onError(result.error);
|
|
288
284
|
}
|
|
289
285
|
return err({ type: "StorageWriteError", ownerId });
|
|
290
286
|
}
|
|
@@ -299,6 +295,10 @@ const createClientStorage = (deps) => (config) => {
|
|
|
299
295
|
const result = deps.sqlite.exec(sql `
|
|
300
296
|
select "table", "id", "column", "value"
|
|
301
297
|
from evolu_history
|
|
298
|
+
where "ownerId" = ${ownerId} and "timestamp" = ${timestamp}
|
|
299
|
+
union all
|
|
300
|
+
select "table", "id", "column", "value"
|
|
301
|
+
from evolu_message_quarantine
|
|
302
302
|
where "ownerId" = ${ownerId} and "timestamp" = ${timestamp};
|
|
303
303
|
`);
|
|
304
304
|
if (!result.ok) {
|
|
@@ -306,10 +306,10 @@ const createClientStorage = (deps) => (config) => {
|
|
|
306
306
|
return null;
|
|
307
307
|
}
|
|
308
308
|
const { rows } = result.value;
|
|
309
|
-
|
|
310
|
-
const { table, id } = rows
|
|
311
|
-
const values =
|
|
312
|
-
let isInsert
|
|
309
|
+
assertNonEmptyReadonlyArray(rows, "Every timestamp must have rows in evolu_history or quarantine");
|
|
310
|
+
const { table, id } = firstInArray(rows);
|
|
311
|
+
const values = createRecord();
|
|
312
|
+
let isInsert;
|
|
313
313
|
let isDelete = null;
|
|
314
314
|
for (const r of rows) {
|
|
315
315
|
assert(r.table === table, "All rows must have the same table");
|
|
@@ -318,14 +318,18 @@ const createClientStorage = (deps) => (config) => {
|
|
|
318
318
|
case "createdAt":
|
|
319
319
|
isInsert = true;
|
|
320
320
|
break;
|
|
321
|
+
case "updatedAt":
|
|
322
|
+
isInsert = false;
|
|
323
|
+
break;
|
|
321
324
|
case "isDeleted":
|
|
322
|
-
assert(SqliteBoolean.is(r.value), "isDeleted column must contain a valid SqliteBoolean
|
|
325
|
+
assert(SqliteBoolean.is(r.value), "isDeleted column must contain a valid SqliteBoolean");
|
|
323
326
|
isDelete = sqliteBooleanToBoolean(r.value);
|
|
324
327
|
break;
|
|
325
328
|
default:
|
|
326
329
|
values[r.column] = r.value;
|
|
327
330
|
}
|
|
328
331
|
}
|
|
332
|
+
assert(Boolean.is(isInsert), "isInsert must be in evolu_history");
|
|
329
333
|
const message = {
|
|
330
334
|
timestamp: timestampBytesToTimestamp(timestamp),
|
|
331
335
|
change: DbChange.orThrow({
|
|
@@ -345,6 +349,21 @@ const createClientStorage = (deps) => (config) => {
|
|
|
345
349
|
const createTransportKey = (transport) => {
|
|
346
350
|
return `${transport.type}:${transport.url}`;
|
|
347
351
|
};
|
|
352
|
+
const dbChangeToColumns = (change, now) => {
|
|
353
|
+
let values = objectToEntries(change.values);
|
|
354
|
+
// SystemColumns are not encoded in change.values.
|
|
355
|
+
values = appendToArray(values, [
|
|
356
|
+
change.isInsert ? "createdAt" : "updatedAt",
|
|
357
|
+
now,
|
|
358
|
+
]);
|
|
359
|
+
if (change.isDelete != null) {
|
|
360
|
+
values = appendToArray(values, [
|
|
361
|
+
"isDeleted",
|
|
362
|
+
booleanToSqliteBoolean(change.isDelete),
|
|
363
|
+
]);
|
|
364
|
+
}
|
|
365
|
+
return values;
|
|
366
|
+
};
|
|
348
367
|
export const applyLocalOnlyChange = (deps) => (change) => {
|
|
349
368
|
if (change.isDelete) {
|
|
350
369
|
const result = deps.sqlite.exec(sql `
|
|
@@ -355,21 +374,15 @@ export const applyLocalOnlyChange = (deps) => (change) => {
|
|
|
355
374
|
return result;
|
|
356
375
|
}
|
|
357
376
|
else {
|
|
358
|
-
const now = deps.time.nowIso();
|
|
359
377
|
const ownerId = deps.appOwner.id;
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
entries = [...entries, ["isDeleted", sqliteFalse]];
|
|
363
|
-
}
|
|
364
|
-
for (const [column, value] of entries) {
|
|
378
|
+
const columns = dbChangeToColumns(change, deps.time.nowIso());
|
|
379
|
+
for (const [column, value] of columns) {
|
|
365
380
|
const result = deps.sqlite.exec(sql.prepared `
|
|
366
381
|
insert into ${sql.identifier(change.table)}
|
|
367
|
-
("ownerId", "id", ${sql.identifier(column)}
|
|
368
|
-
values (${ownerId}, ${change.id}, ${value}
|
|
382
|
+
("ownerId", "id", ${sql.identifier(column)})
|
|
383
|
+
values (${ownerId}, ${change.id}, ${value})
|
|
369
384
|
on conflict ("ownerId", "id") do update
|
|
370
|
-
set
|
|
371
|
-
${sql.identifier(column)} = ${value},
|
|
372
|
-
updatedAt = ${now};
|
|
385
|
+
set ${sql.identifier(column)} = ${value};
|
|
373
386
|
`);
|
|
374
387
|
if (!result.ok)
|
|
375
388
|
return result;
|
|
@@ -379,84 +392,98 @@ export const applyLocalOnlyChange = (deps) => (change) => {
|
|
|
379
392
|
};
|
|
380
393
|
const applyMessages = (deps) => (ownerId, messages) => {
|
|
381
394
|
const ownerIdBytes = ownerIdToOwnerIdBytes(ownerId);
|
|
382
|
-
const
|
|
383
|
-
if (!
|
|
384
|
-
return
|
|
385
|
-
let { firstTimestamp, lastTimestamp } =
|
|
386
|
-
for (const
|
|
387
|
-
const
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
395
|
+
const usage = getOwnerUsage(deps)(ownerIdBytes, timestampToTimestampBytes(firstInArray(messages).timestamp));
|
|
396
|
+
if (!usage.ok)
|
|
397
|
+
return usage;
|
|
398
|
+
let { firstTimestamp, lastTimestamp } = usage.value;
|
|
399
|
+
for (const { timestamp, change } of messages) {
|
|
400
|
+
const columns = dbChangeToColumns(change, timestampToDateIso(timestamp));
|
|
401
|
+
const idBytes = idToIdBytes(change.id);
|
|
402
|
+
const timestampBytes = timestampToTimestampBytes(timestamp);
|
|
403
|
+
for (const [column, value] of columns) {
|
|
404
|
+
if (validateColumnValue(deps)(change.table, column, value)) {
|
|
405
|
+
const result = applyColumnChange(deps)(ownerIdBytes, ownerId, change.table, idBytes, change.id, column, value, timestampBytes);
|
|
406
|
+
if (!result.ok)
|
|
407
|
+
return result;
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
const result = deps.sqlite.exec(sql.prepared `
|
|
411
|
+
insert into evolu_message_quarantine
|
|
412
|
+
("ownerId", "timestamp", "table", "id", "column", "value")
|
|
413
|
+
values
|
|
414
|
+
(
|
|
415
|
+
${ownerIdBytes},
|
|
416
|
+
${timestampBytes},
|
|
417
|
+
${change.table},
|
|
418
|
+
${idBytes},
|
|
419
|
+
${column},
|
|
420
|
+
${value}
|
|
421
|
+
)
|
|
422
|
+
on conflict do nothing;
|
|
423
|
+
`);
|
|
424
|
+
if (!result.ok)
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
392
428
|
let strategy;
|
|
393
|
-
[strategy, firstTimestamp, lastTimestamp] = getTimestampInsertStrategy(
|
|
394
|
-
const
|
|
395
|
-
if (!
|
|
396
|
-
return
|
|
429
|
+
[strategy, firstTimestamp, lastTimestamp] = getTimestampInsertStrategy(timestampBytes, firstTimestamp, lastTimestamp);
|
|
430
|
+
const result = deps.storage.insertTimestamp(ownerIdBytes, timestampBytes, strategy);
|
|
431
|
+
if (!result.ok)
|
|
432
|
+
return result;
|
|
397
433
|
}
|
|
398
434
|
/**
|
|
399
|
-
* TODO: Implement proper storedBytes tracking for client using
|
|
400
|
-
* message sizes
|
|
401
|
-
* client...).
|
|
435
|
+
* TODO: Implement proper storedBytes tracking for client using received and
|
|
436
|
+
* sent encrypted message sizes.
|
|
402
437
|
*/
|
|
403
|
-
|
|
438
|
+
return updateOwnerUsage(deps)(ownerIdBytes, 1, // Placeholder until proper tracking implemented
|
|
404
439
|
firstTimestamp, lastTimestamp);
|
|
405
|
-
if (!updateUsage.ok)
|
|
406
|
-
return updateUsage;
|
|
407
|
-
return ok();
|
|
408
440
|
};
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
where
|
|
419
|
-
"ownerId" = ${ownerIdBytes}
|
|
420
|
-
and "table" = ${message.change.table}
|
|
421
|
-
and "id" = ${idToIdBytes(message.change.id)}
|
|
422
|
-
and "column" = ${column}
|
|
423
|
-
and "timestamp" >= ${timestampToTimestampBytes(message.timestamp)}
|
|
424
|
-
limit 1
|
|
425
|
-
)
|
|
426
|
-
insert into ${sql.identifier(message.change.table)}
|
|
427
|
-
("ownerId", "id", ${sql.identifier(column)}, updatedAt)
|
|
428
|
-
select ${ownerId}, ${message.change.id}, ${value}, ${date}
|
|
429
|
-
where not exists (select 1 from existingTimestamp)
|
|
430
|
-
on conflict ("ownerId", "id") do update
|
|
431
|
-
set
|
|
432
|
-
${sql.identifier(column)} = ${value},
|
|
433
|
-
updatedAt = ${date}
|
|
434
|
-
where not exists (select 1 from existingTimestamp);
|
|
435
|
-
`);
|
|
436
|
-
if (!result.ok)
|
|
437
|
-
return result;
|
|
438
|
-
}
|
|
439
|
-
return ok();
|
|
441
|
+
/**
|
|
442
|
+
* System columns that can appear in sync messages. Excludes `ownerId` because
|
|
443
|
+
* it's handled separately (stored per-row, not per-column in messages).
|
|
444
|
+
*/
|
|
445
|
+
const systemColumnsWithoutOwnerId = systemColumns.difference(new Set(["ownerId"]));
|
|
446
|
+
const validateColumnValue = (deps) => (table, column, _value) => {
|
|
447
|
+
const schemaColumns = getProperty(deps.dbSchema.tables, table);
|
|
448
|
+
return (schemaColumns != null &&
|
|
449
|
+
(systemColumnsWithoutOwnerId.has(column) || schemaColumns.has(column)));
|
|
440
450
|
};
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
451
|
+
const applyColumnChange = (deps) => (ownerIdBytes, ownerId, table, idBytes, id, column, value, timestampBytes) => {
|
|
452
|
+
const result = deps.sqlite.exec(sql.prepared `
|
|
453
|
+
with
|
|
454
|
+
existingTimestamp as (
|
|
455
|
+
select 1
|
|
456
|
+
from evolu_history
|
|
457
|
+
where
|
|
458
|
+
"ownerId" = ${ownerIdBytes}
|
|
459
|
+
and "table" = ${table}
|
|
460
|
+
and "id" = ${idBytes}
|
|
461
|
+
and "column" = ${column}
|
|
462
|
+
and "timestamp" >= ${timestampBytes}
|
|
463
|
+
limit 1
|
|
464
|
+
)
|
|
465
|
+
insert into ${sql.identifier(table)}
|
|
466
|
+
("ownerId", "id", ${sql.identifier(column)})
|
|
467
|
+
select ${ownerId}, ${id}, ${value}
|
|
468
|
+
where not exists (select 1 from existingTimestamp)
|
|
469
|
+
on conflict ("ownerId", "id") do update
|
|
470
|
+
set ${sql.identifier(column)} = ${value}
|
|
471
|
+
where not exists (select 1 from existingTimestamp);
|
|
472
|
+
`);
|
|
445
473
|
if (!result.ok)
|
|
446
474
|
return result;
|
|
447
|
-
|
|
448
|
-
for (const [column, value] of columns) {
|
|
475
|
+
{
|
|
449
476
|
const result = deps.sqlite.exec(sql.prepared `
|
|
450
477
|
insert into evolu_history
|
|
451
478
|
("ownerId", "table", "id", "column", "value", "timestamp")
|
|
452
479
|
values
|
|
453
480
|
(
|
|
454
|
-
${
|
|
455
|
-
${
|
|
456
|
-
${
|
|
481
|
+
${ownerIdBytes},
|
|
482
|
+
${table},
|
|
483
|
+
${idBytes},
|
|
457
484
|
${column},
|
|
458
485
|
${value},
|
|
459
|
-
${
|
|
486
|
+
${timestampBytes}
|
|
460
487
|
)
|
|
461
488
|
on conflict do nothing;
|
|
462
489
|
`);
|
|
@@ -465,14 +492,38 @@ export const applyMessageToTimestampAndHistoryTables = (deps) => (ownerId, messa
|
|
|
465
492
|
}
|
|
466
493
|
return ok();
|
|
467
494
|
};
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
495
|
+
/**
|
|
496
|
+
* Attempts to apply quarantined messages that may now be valid after a schema
|
|
497
|
+
* update. Messages are quarantined when they reference tables or columns that
|
|
498
|
+
* don't exist in the current schema (e.g., from a newer app version).
|
|
499
|
+
*/
|
|
500
|
+
export const tryApplyQuarantinedMessages = (deps) => () => {
|
|
501
|
+
const rows = deps.sqlite.exec(sql `
|
|
502
|
+
select "ownerId", "timestamp", "table", "id", "column", "value"
|
|
503
|
+
from evolu_message_quarantine;
|
|
504
|
+
`);
|
|
505
|
+
if (!rows.ok)
|
|
506
|
+
return rows;
|
|
507
|
+
for (const row of rows.value.rows) {
|
|
508
|
+
if (!validateColumnValue(deps)(row.table, row.column, row.value))
|
|
509
|
+
continue;
|
|
510
|
+
const result = applyColumnChange(deps)(row.ownerId, ownerIdBytesToOwnerId(row.ownerId), row.table, row.id, idBytesToId(row.id), row.column, row.value, row.timestamp);
|
|
511
|
+
if (!result.ok)
|
|
512
|
+
return result;
|
|
513
|
+
{
|
|
514
|
+
const result = deps.sqlite.exec(sql `
|
|
515
|
+
delete from evolu_message_quarantine
|
|
516
|
+
where
|
|
517
|
+
"ownerId" = ${row.ownerId}
|
|
518
|
+
and "timestamp" = ${row.timestamp}
|
|
519
|
+
and "table" = ${row.table}
|
|
520
|
+
and "id" = ${row.id}
|
|
521
|
+
and "column" = ${row.column};
|
|
522
|
+
`);
|
|
523
|
+
if (!result.ok)
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
475
526
|
}
|
|
476
|
-
return
|
|
527
|
+
return ok();
|
|
477
528
|
};
|
|
478
529
|
export const initialSyncState = { type: "SyncStateInitial" };
|
|
@@ -154,6 +154,19 @@ export type TimestampBytes = typeof TimestampBytes.Type;
|
|
|
154
154
|
export declare const timestampBytesLength: number & import("../Brand.js").Brand<"Int"> & import("../Brand.js").Brand<"NonNegative">;
|
|
155
155
|
export declare const timestampToTimestampBytes: (timestamp: Timestamp) => TimestampBytes;
|
|
156
156
|
export declare const timestampBytesToTimestamp: (timestamp: TimestampBytes) => Timestamp;
|
|
157
|
+
/**
|
|
158
|
+
* An {@link Order} for {@link TimestampBytes}.
|
|
159
|
+
*
|
|
160
|
+
* This `Order` uses lexicographic byte order to compare serialized
|
|
161
|
+
* {@link TimestampBytes} produced by {@link timestampToTimestampBytes}. See
|
|
162
|
+
* {@link orderUint8Array} for the underlying implementation.
|
|
163
|
+
*/
|
|
157
164
|
export declare const orderTimestampBytes: Order<TimestampBytes>;
|
|
165
|
+
/**
|
|
166
|
+
* Convert a {@link Timestamp} to an ISO 8601 {@link DateIso} string.
|
|
167
|
+
*
|
|
168
|
+
* The conversion uses the timestamp's `millis` (a {@link Millis} value) and
|
|
169
|
+
* `Date.prototype.toISOString()` to produce a `DateIso`.
|
|
170
|
+
*/
|
|
158
171
|
export declare const timestampToDateIso: (timestamp: Timestamp) => DateIso;
|
|
159
172
|
//# sourceMappingURL=Timestamp.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/local-first/Timestamp.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/local-first/Timestamp.ts"],"names":[],"mappings":"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"}
|
|
@@ -218,7 +218,20 @@ export const timestampBytesToTimestamp = (timestamp) => {
|
|
|
218
218
|
}
|
|
219
219
|
return { millis: Number(millis), counter, nodeId };
|
|
220
220
|
};
|
|
221
|
+
/**
|
|
222
|
+
* An {@link Order} for {@link TimestampBytes}.
|
|
223
|
+
*
|
|
224
|
+
* This `Order` uses lexicographic byte order to compare serialized
|
|
225
|
+
* {@link TimestampBytes} produced by {@link timestampToTimestampBytes}. See
|
|
226
|
+
* {@link orderUint8Array} for the underlying implementation.
|
|
227
|
+
*/
|
|
221
228
|
export const orderTimestampBytes = orderUint8Array;
|
|
229
|
+
/**
|
|
230
|
+
* Convert a {@link Timestamp} to an ISO 8601 {@link DateIso} string.
|
|
231
|
+
*
|
|
232
|
+
* The conversion uses the timestamp's `millis` (a {@link Millis} value) and
|
|
233
|
+
* `Date.prototype.toISOString()` to produce a `DateIso`.
|
|
234
|
+
*/
|
|
222
235
|
export const timestampToDateIso = (timestamp) =>
|
|
223
|
-
// `as DateIso` is safe because
|
|
236
|
+
// `as DateIso` is safe because Timestamp guarantees a valid `millis`
|
|
224
237
|
new Date(timestamp.millis).toISOString();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/local-first/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/local-first/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC"}
|