@evolu/common 7.2.3 → 7.4.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/Buffer.d.ts +2 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +1 -0
- package/dist/src/Callbacks.d.ts +1 -1
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +1 -1
- package/dist/src/Console.d.ts +1 -9
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +1 -41
- 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 +16 -0
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +16 -0
- package/dist/src/Redacted.d.ts +89 -0
- package/dist/src/Redacted.d.ts.map +1 -0
- package/dist/src/Redacted.js +48 -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 +34 -10
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +82 -202
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -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 +109 -108
- 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 +6 -7
- package/dist/src/local-first/LocalAuth.d.ts +2 -2
- package/dist/src/local-first/Owner.d.ts +7 -0
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +10 -10
- package/dist/src/local-first/Public.d.ts +6 -1
- package/dist/src/local-first/Public.d.ts.map +1 -1
- package/dist/src/local-first/Public.js +6 -1
- 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 +9 -4
- package/dist/src/local-first/Sync.d.ts.map +1 -1
- package/dist/src/local-first/Sync.js +132 -59
- package/dist/src/local-first/index.d.ts +9 -2
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +9 -2
- package/package.json +3 -4
- package/src/Buffer.ts +2 -1
- package/src/Callbacks.ts +1 -1
- package/src/Console.ts +1 -10
- package/src/Function.ts +74 -0
- package/src/Object.ts +20 -0
- package/src/Redacted.ts +118 -0
- package/src/Result.ts +38 -29
- package/src/Sqlite.ts +18 -7
- package/src/Task.ts +6 -4
- package/src/Type.ts +148 -20
- package/src/index.ts +1 -0
- package/src/local-first/Db.ts +181 -209
- package/src/local-first/Evolu.ts +9 -6
- package/src/local-first/LocalAuth.ts +2 -2
- package/src/local-first/Owner.ts +8 -0
- package/src/local-first/Protocol.ts +10 -16
- package/src/local-first/Public.ts +6 -1
- 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 +190 -66
- package/src/local-first/index.ts +9 -2
- 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",
|
|
@@ -7,10 +7,9 @@ import { Result } from "../Result.js";
|
|
|
7
7
|
import { SqliteDep, SqliteError } from "../Sqlite.js";
|
|
8
8
|
import { TimeDep } from "../Time.js";
|
|
9
9
|
import { CreateWebSocketDep } from "../WebSocket.js";
|
|
10
|
-
import
|
|
11
|
-
import { AppOwner, Owner, OwnerTransport, ReadonlyOwner } from "./Owner.js";
|
|
10
|
+
import { AppOwner, AppOwnerDep, Owner, OwnerTransport, ReadonlyOwner } from "./Owner.js";
|
|
12
11
|
import { ProtocolError, ProtocolInvalidDataError, ProtocolTimestampMismatchError } from "./Protocol.js";
|
|
13
|
-
import { MutationChange } from "./Schema.js";
|
|
12
|
+
import { DbSchemaDep, MutationChange } from "./Schema.js";
|
|
14
13
|
import { BaseSqliteStorage, Storage } from "./Storage.js";
|
|
15
14
|
import { Millis, Timestamp, TimestampConfigDep, TimestampCounterOverflowError, TimestampDriftError, TimestampTimeOutOfRangeError } from "./Timestamp.js";
|
|
16
15
|
export interface Sync extends Disposable {
|
|
@@ -48,7 +47,7 @@ export interface SyncConfig {
|
|
|
48
47
|
readonly onError: (error: ProtocolError | ProtocolInvalidDataError | ProtocolTimestampMismatchError | SqliteError | SymmetricCryptoDecryptError | TimestampCounterOverflowError | TimestampDriftError | TimestampTimeOutOfRangeError | TransferableError) => void;
|
|
49
48
|
readonly onReceive: () => void;
|
|
50
49
|
}
|
|
51
|
-
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>;
|
|
52
51
|
export interface ClockDep {
|
|
53
52
|
readonly clock: Clock;
|
|
54
53
|
}
|
|
@@ -63,6 +62,12 @@ export interface ClientStorageDep {
|
|
|
63
62
|
readonly storage: ClientStorage;
|
|
64
63
|
}
|
|
65
64
|
export declare const applyLocalOnlyChange: (deps: SqliteDep & TimeDep & AppOwnerDep) => (change: MutationChange) => Result<void, SqliteError>;
|
|
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>;
|
|
66
71
|
/**
|
|
67
72
|
* TODO: Rework for the new owners API.
|
|
68
73
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sync.d.ts","sourceRoot":"","sources":["../../../src/local-first/Sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,kBAAkB,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAA2B,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGzE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAW,MAAM,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAKL,SAAS,EACT,WAAW,EAEZ,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"Sync.d.ts","sourceRoot":"","sources":["../../../src/local-first/Sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ 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 { createRecord, 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
9
|
import { booleanToSqliteBoolean, sql, SqliteBoolean, sqliteBooleanToBoolean, } from "../Sqlite.js";
|
|
@@ -11,6 +11,7 @@ import { createMutex } from "../Task.js";
|
|
|
11
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,9 +268,9 @@ 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
275
|
return deps.clock.save(clockTimestamp);
|
|
275
276
|
});
|
|
@@ -277,9 +278,9 @@ const createClientStorage = (deps) => (config) => {
|
|
|
277
278
|
return transaction;
|
|
278
279
|
return ok(true);
|
|
279
280
|
})();
|
|
280
|
-
if (!
|
|
281
|
-
if (
|
|
282
|
-
config.onError(
|
|
281
|
+
if (!result.ok) {
|
|
282
|
+
if (result.error.type !== "AbortError") {
|
|
283
|
+
config.onError(result.error);
|
|
283
284
|
}
|
|
284
285
|
return err({ type: "StorageWriteError", ownerId });
|
|
285
286
|
}
|
|
@@ -294,6 +295,10 @@ const createClientStorage = (deps) => (config) => {
|
|
|
294
295
|
const result = deps.sqlite.exec(sql `
|
|
295
296
|
select "table", "id", "column", "value"
|
|
296
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
|
|
297
302
|
where "ownerId" = ${ownerId} and "timestamp" = ${timestamp};
|
|
298
303
|
`);
|
|
299
304
|
if (!result.ok) {
|
|
@@ -301,7 +306,7 @@ const createClientStorage = (deps) => (config) => {
|
|
|
301
306
|
return null;
|
|
302
307
|
}
|
|
303
308
|
const { rows } = result.value;
|
|
304
|
-
assertNonEmptyReadonlyArray(rows, "Every timestamp must have rows");
|
|
309
|
+
assertNonEmptyReadonlyArray(rows, "Every timestamp must have rows in evolu_history or quarantine");
|
|
305
310
|
const { table, id } = firstInArray(rows);
|
|
306
311
|
const values = createRecord();
|
|
307
312
|
let isInsert;
|
|
@@ -392,65 +397,133 @@ const applyMessages = (deps) => (ownerId, messages) => {
|
|
|
392
397
|
return usage;
|
|
393
398
|
let { firstTimestamp, lastTimestamp } = usage.value;
|
|
394
399
|
for (const { timestamp, change } of messages) {
|
|
395
|
-
const timestampBytes = timestampToTimestampBytes(timestamp);
|
|
396
|
-
const idBytes = idToIdBytes(change.id);
|
|
397
400
|
const columns = dbChangeToColumns(change, timestampToDateIso(timestamp));
|
|
401
|
+
const idBytes = idToIdBytes(change.id);
|
|
402
|
+
const timestampBytes = timestampToTimestampBytes(timestamp);
|
|
398
403
|
for (const [column, value] of columns) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const insertHistory = deps.sqlite.exec(sql.prepared `
|
|
423
|
-
insert into evolu_history
|
|
424
|
-
("ownerId", "table", "id", "column", "value", "timestamp")
|
|
425
|
-
values
|
|
426
|
-
(
|
|
427
|
-
${ownerIdBytes},
|
|
428
|
-
${change.table},
|
|
429
|
-
${idBytes},
|
|
430
|
-
${column},
|
|
431
|
-
${value},
|
|
432
|
-
${timestampBytes}
|
|
433
|
-
)
|
|
434
|
-
on conflict do nothing;
|
|
435
|
-
`);
|
|
436
|
-
if (!insertHistory.ok)
|
|
437
|
-
return insertHistory;
|
|
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
|
+
}
|
|
438
427
|
}
|
|
439
428
|
let strategy;
|
|
440
429
|
[strategy, firstTimestamp, lastTimestamp] = getTimestampInsertStrategy(timestampBytes, firstTimestamp, lastTimestamp);
|
|
441
|
-
const
|
|
442
|
-
if (!
|
|
443
|
-
return
|
|
430
|
+
const result = deps.storage.insertTimestamp(ownerIdBytes, timestampBytes, strategy);
|
|
431
|
+
if (!result.ok)
|
|
432
|
+
return result;
|
|
444
433
|
}
|
|
445
434
|
/**
|
|
446
|
-
* TODO: Implement proper storedBytes tracking for client using
|
|
447
|
-
* message sizes
|
|
448
|
-
* client...).
|
|
435
|
+
* TODO: Implement proper storedBytes tracking for client using received and
|
|
436
|
+
* sent encrypted message sizes.
|
|
449
437
|
*/
|
|
450
|
-
|
|
438
|
+
return updateOwnerUsage(deps)(ownerIdBytes, 1, // Placeholder until proper tracking implemented
|
|
451
439
|
firstTimestamp, lastTimestamp);
|
|
452
|
-
|
|
453
|
-
|
|
440
|
+
};
|
|
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)));
|
|
450
|
+
};
|
|
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
|
+
`);
|
|
473
|
+
if (!result.ok)
|
|
474
|
+
return result;
|
|
475
|
+
{
|
|
476
|
+
const result = deps.sqlite.exec(sql.prepared `
|
|
477
|
+
insert into evolu_history
|
|
478
|
+
("ownerId", "table", "id", "column", "value", "timestamp")
|
|
479
|
+
values
|
|
480
|
+
(
|
|
481
|
+
${ownerIdBytes},
|
|
482
|
+
${table},
|
|
483
|
+
${idBytes},
|
|
484
|
+
${column},
|
|
485
|
+
${value},
|
|
486
|
+
${timestampBytes}
|
|
487
|
+
)
|
|
488
|
+
on conflict do nothing;
|
|
489
|
+
`);
|
|
490
|
+
if (!result.ok)
|
|
491
|
+
return result;
|
|
492
|
+
}
|
|
493
|
+
return ok();
|
|
494
|
+
};
|
|
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
|
+
}
|
|
526
|
+
}
|
|
454
527
|
return ok();
|
|
455
528
|
};
|
|
456
529
|
export const initialSyncState = { type: "SyncStateInitial" };
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Internal local-first modules re-exported from "@evolu/common/local-first"
|
|
3
|
+
*
|
|
4
|
+
* These exports expose the internal building blocks of Evolu's local-first
|
|
5
|
+
* implementation (Db, Relay, Sync, Query, Storage, Timestamp, etc.). They are
|
|
6
|
+
* primarily intended for use within Evolu packages and for advanced use-cases
|
|
7
|
+
* that require direct access to local-first internals.
|
|
8
|
+
*
|
|
9
|
+
* Public consumers should us top-level exports from `@evolu/common` unless you
|
|
10
|
+
* have a specific need to import these internals.
|
|
3
11
|
*
|
|
4
12
|
* @module
|
|
5
13
|
*/
|
|
6
14
|
export * from "./Db.js";
|
|
7
|
-
export * from "./Diff.js";
|
|
8
15
|
export * from "./Evolu.js";
|
|
9
16
|
export * from "./Owner.js";
|
|
10
17
|
export * from "./Platform.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/local-first/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/local-first/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;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"}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Internal local-first modules re-exported from "@evolu/common/local-first"
|
|
3
|
+
*
|
|
4
|
+
* These exports expose the internal building blocks of Evolu's local-first
|
|
5
|
+
* implementation (Db, Relay, Sync, Query, Storage, Timestamp, etc.). They are
|
|
6
|
+
* primarily intended for use within Evolu packages and for advanced use-cases
|
|
7
|
+
* that require direct access to local-first internals.
|
|
8
|
+
*
|
|
9
|
+
* Public consumers should us top-level exports from `@evolu/common` unless you
|
|
10
|
+
* have a specific need to import these internals.
|
|
3
11
|
*
|
|
4
12
|
* @module
|
|
5
13
|
*/
|
|
6
14
|
export * from "./Db.js";
|
|
7
|
-
export * from "./Diff.js";
|
|
8
15
|
export * from "./Evolu.js";
|
|
9
16
|
export * from "./Owner.js";
|
|
10
17
|
export * from "./Platform.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/common",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.4.0",
|
|
4
4
|
"description": "TypeScript library and local-first platform",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"evolu",
|
|
@@ -56,7 +56,6 @@
|
|
|
56
56
|
"@types/ws": "^8.18.1",
|
|
57
57
|
"better-sqlite3": "^12.1.1",
|
|
58
58
|
"fast-check": "^4.2.0",
|
|
59
|
-
"shx": "^0.4.0",
|
|
60
59
|
"typescript": "^5.9.2",
|
|
61
60
|
"vitest": "^4.0.4",
|
|
62
61
|
"ws": "^8.18.2",
|
|
@@ -71,10 +70,10 @@
|
|
|
71
70
|
"sideEffects": [],
|
|
72
71
|
"scripts": {
|
|
73
72
|
"dev": "tsc --watch",
|
|
74
|
-
"build": "
|
|
73
|
+
"build": "rimraf dist && tsc",
|
|
75
74
|
"test": "vitest run --disableConsoleIntercept",
|
|
76
75
|
"test:watch": "vitest --disableConsoleIntercept",
|
|
77
|
-
"clean": "
|
|
76
|
+
"clean": "rimraf .turbo node_modules dist",
|
|
78
77
|
"format": "prettier --write \"src/*.{ts,tsx,md}\"",
|
|
79
78
|
"bench": "vitest bench"
|
|
80
79
|
}
|
package/src/Buffer.ts
CHANGED
|
@@ -63,7 +63,7 @@ export class BufferError extends Error {
|
|
|
63
63
|
* https://blog.mozilla.org/nnethercote/2014/11/04/please-grow-your-buffers-exponentially
|
|
64
64
|
*/
|
|
65
65
|
export interface Buffer {
|
|
66
|
-
/** Returns the
|
|
66
|
+
/** Returns the current capacity of the buffer. */
|
|
67
67
|
getCapacity: () => NonNegativeInt;
|
|
68
68
|
|
|
69
69
|
/** Returns the current number of bytes stored in the buffer. */
|
|
@@ -110,6 +110,7 @@ export interface Buffer {
|
|
|
110
110
|
unwrap: () => Uint8Array;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/** Creates a {@link Buffer} for efficient byte operations. */
|
|
113
114
|
export const createBuffer = (
|
|
114
115
|
arrayLike?: Uint8Array | ArrayLike<number>,
|
|
115
116
|
): Buffer => {
|
package/src/Callbacks.ts
CHANGED
|
@@ -55,7 +55,7 @@ export interface Callbacks<T = undefined> {
|
|
|
55
55
|
/** Unique identifier for a callback in {@link Callbacks}. */
|
|
56
56
|
export type CallbackId = Id & Brand<"Callback">;
|
|
57
57
|
|
|
58
|
-
/** Creates a
|
|
58
|
+
/** Creates a {@link Callbacks} registry for managing callbacks. */
|
|
59
59
|
export const createCallbacks = <T = undefined>(
|
|
60
60
|
deps: RandomBytesDep,
|
|
61
61
|
): Callbacks<T> => {
|
package/src/Console.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cross-platform console
|
|
3
|
-
*
|
|
4
2
|
* Console abstraction for Chrome 123+, Firefox 125+, Safari 18.1+, Node.js
|
|
5
3
|
* 22.x+, and React Native 0.75+. Includes methods guaranteed to be available in
|
|
6
4
|
* these environments and expected to remain compatible in future versions.
|
|
@@ -35,13 +33,6 @@
|
|
|
35
33
|
* }
|
|
36
34
|
* }
|
|
37
35
|
* ```
|
|
38
|
-
*
|
|
39
|
-
* @module
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Cross-platform Console interface for Chrome 123+, Firefox 125+, Safari 18.1+,
|
|
44
|
-
* Node.js 22.x+, and React Native 0.75+
|
|
45
36
|
*/
|
|
46
37
|
export interface Console {
|
|
47
38
|
/** Controls whether console methods produce output (default: true) */
|
|
@@ -105,7 +96,7 @@ export interface ConsoleConfig {
|
|
|
105
96
|
readonly enableLogging?: boolean;
|
|
106
97
|
}
|
|
107
98
|
|
|
108
|
-
/** Creates a
|
|
99
|
+
/** Creates a {@link Console} for logging with configurable output. */
|
|
109
100
|
export const createConsole = (config: ConsoleConfig = {}): Console => {
|
|
110
101
|
const instance: Console = {
|
|
111
102
|
enabled: config.enableLogging ?? false,
|
package/src/Function.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { NonEmptyArray, NonEmptyReadonlyArray } from "./Array.js";
|
|
2
|
+
import { ReadonlyRecord } from "./Object.js";
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Helper function to ensure exhaustive matching in a switch statement. Throws
|
|
3
6
|
* an error if an unhandled case is encountered.
|
|
@@ -31,8 +34,79 @@ export const exhaustiveCheck = (value: never): never => {
|
|
|
31
34
|
throw new Error(`exhaustiveCheck unhandled case: ${JSON.stringify(value)}`);
|
|
32
35
|
};
|
|
33
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Returns the input value unchanged.
|
|
39
|
+
*
|
|
40
|
+
* Useful as a default transformation, placeholder callback, or when a function
|
|
41
|
+
* is required but no transformation is needed.
|
|
42
|
+
*
|
|
43
|
+
* ### Example
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* const values = [1, 2, 3];
|
|
47
|
+
* const same = values.map(identity); // [1, 2, 3]
|
|
48
|
+
*
|
|
49
|
+
* const getTransform = (shouldDouble: boolean) =>
|
|
50
|
+
* shouldDouble ? (x: number) => x * 2 : identity;
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
34
53
|
export const identity = <A>(a: A): A => a;
|
|
35
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Casts an array, set, record, or map to its readonly counterpart.
|
|
57
|
+
*
|
|
58
|
+
* Zero runtime cost — returns the same value with a readonly type. Use this to
|
|
59
|
+
* enforce immutability at the type level. Preserves {@link NonEmptyArray} as
|
|
60
|
+
* {@link NonEmptyReadonlyArray}.
|
|
61
|
+
*
|
|
62
|
+
* ### Example
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* // Array literals become NonEmptyReadonlyArray
|
|
66
|
+
* const items = readonly([1, 2, 3]);
|
|
67
|
+
* // Type: NonEmptyReadonlyArray<number>
|
|
68
|
+
*
|
|
69
|
+
* // NonEmptyArray is preserved as NonEmptyReadonlyArray
|
|
70
|
+
* const nonEmpty: NonEmptyArray<number> = [1, 2, 3];
|
|
71
|
+
* const readonlyNonEmpty = readonly(nonEmpty);
|
|
72
|
+
* // Type: NonEmptyReadonlyArray<number>
|
|
73
|
+
*
|
|
74
|
+
* // Regular arrays become ReadonlyArray
|
|
75
|
+
* const arr: Array<number> = getNumbers();
|
|
76
|
+
* const readonlyArr = readonly(arr);
|
|
77
|
+
* // Type: ReadonlyArray<number>
|
|
78
|
+
*
|
|
79
|
+
* // Sets, Records, and Maps
|
|
80
|
+
* const ids = readonly(new Set(["a", "b"]));
|
|
81
|
+
* // Type: ReadonlySet<string>
|
|
82
|
+
*
|
|
83
|
+
* const users: Record<UserId, string> = { ... };
|
|
84
|
+
* const readonlyUsers = readonly(users);
|
|
85
|
+
* // Type: ReadonlyRecord<UserId, string>
|
|
86
|
+
*
|
|
87
|
+
* const lookup = readonly(new Map([["key", "value"]]));
|
|
88
|
+
* // Type: ReadonlyMap<string, string>
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @experimental
|
|
92
|
+
*/
|
|
93
|
+
export function readonly<T>(array: NonEmptyArray<T>): NonEmptyReadonlyArray<T>;
|
|
94
|
+
export function readonly<T>(array: Array<T>): ReadonlyArray<T>;
|
|
95
|
+
export function readonly<T>(set: Set<T>): ReadonlySet<T>;
|
|
96
|
+
export function readonly<K, V>(map: Map<K, V>): ReadonlyMap<K, V>;
|
|
97
|
+
export function readonly<K extends keyof any, V>(
|
|
98
|
+
record: Record<K, V>,
|
|
99
|
+
): ReadonlyRecord<K, V>;
|
|
100
|
+
export function readonly<T, K extends keyof any, V>(
|
|
101
|
+
value: Array<T> | Set<T> | Map<K, V> | Record<K, V>,
|
|
102
|
+
):
|
|
103
|
+
| ReadonlyArray<T>
|
|
104
|
+
| ReadonlySet<T>
|
|
105
|
+
| ReadonlyMap<K, V>
|
|
106
|
+
| ReadonlyRecord<K, V> {
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
|
|
36
110
|
/**
|
|
37
111
|
* A function that delays computation and returns a value of type T.
|
|
38
112
|
*
|
package/src/Object.ts
CHANGED
|
@@ -89,3 +89,23 @@ export const createRecord = <K extends string = string, V = unknown>(): Record<
|
|
|
89
89
|
K,
|
|
90
90
|
V
|
|
91
91
|
> => Object.create(null) as Record<K, V>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Safely gets a property from a record, returning `undefined` if the key
|
|
95
|
+
* doesn't exist.
|
|
96
|
+
*
|
|
97
|
+
* TypeScript's `Record<K, V>` type assumes all keys exist, but at runtime
|
|
98
|
+
* accessing a non-existent key returns `undefined`. This helper provides proper
|
|
99
|
+
* typing for that case without needing a type assertion.
|
|
100
|
+
*
|
|
101
|
+
* ### Example
|
|
102
|
+
*
|
|
103
|
+
* ```ts
|
|
104
|
+
* const users: Record<string, User> = { alice: { name: "Alice" } };
|
|
105
|
+
* const user = getProperty(users, "bob"); // User | undefined
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export const getProperty = <K extends string, V>(
|
|
109
|
+
record: ReadonlyRecord<K, V>,
|
|
110
|
+
key: string,
|
|
111
|
+
): V | undefined => (key in record ? record[key as K] : undefined);
|