@evolu/common 5.4.8 → 6.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 +30 -34
- package/dist/src/Array.d.ts +17 -0
- package/dist/src/Array.d.ts.map +1 -0
- package/dist/src/Array.js +12 -0
- package/dist/src/Assert.d.ts +68 -0
- package/dist/src/Assert.d.ts.map +1 -0
- package/dist/src/Assert.js +77 -0
- package/dist/src/BigInt.d.ts +20 -0
- package/dist/src/BigInt.d.ts.map +1 -0
- package/dist/src/BigInt.js +18 -0
- package/dist/src/Buffer.d.ts +92 -0
- package/dist/src/Buffer.d.ts.map +1 -0
- package/dist/src/Buffer.js +62 -0
- package/dist/src/Callbacks.d.ts +20 -0
- package/dist/src/Callbacks.d.ts.map +1 -0
- package/dist/src/Callbacks.js +18 -0
- package/dist/src/Console.d.ts +78 -0
- package/dist/src/Console.d.ts.map +1 -0
- package/dist/src/Console.js +103 -0
- package/dist/src/Crypto.d.ts +72 -39
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +89 -54
- package/dist/src/Eq.d.ts +97 -0
- package/dist/src/Eq.d.ts.map +1 -0
- package/dist/src/Eq.js +167 -0
- package/dist/src/Error.d.ts +14 -10
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +43 -11
- package/dist/src/Evolu/Config.d.ts +69 -0
- package/dist/src/Evolu/Config.d.ts.map +1 -0
- package/dist/src/Evolu/Config.js +9 -0
- package/dist/src/Evolu/Db.d.ts +126 -0
- package/dist/src/Evolu/Db.d.ts.map +1 -0
- package/dist/src/Evolu/Db.js +774 -0
- package/dist/src/Evolu/Diff.d.ts +43 -0
- package/dist/src/Evolu/Diff.d.ts.map +1 -0
- package/dist/src/Evolu/Diff.js +95 -0
- package/dist/src/Evolu/Evolu.d.ts +334 -0
- package/dist/src/Evolu/Evolu.d.ts.map +1 -0
- package/dist/src/Evolu/Evolu.js +434 -0
- package/dist/src/Evolu/Internal.d.ts +26 -0
- package/dist/src/Evolu/Internal.d.ts.map +1 -0
- package/dist/src/Evolu/Internal.js +25 -0
- package/dist/src/Evolu/Kysely.d.ts +6 -0
- package/dist/src/Evolu/Kysely.d.ts.map +1 -0
- package/dist/src/Evolu/Kysely.js +21 -0
- package/dist/src/Evolu/Owner.d.ts +155 -0
- package/dist/src/Evolu/Owner.d.ts.map +1 -0
- package/dist/src/Evolu/Owner.js +126 -0
- package/dist/src/Evolu/Platform.d.ts +23 -0
- package/dist/src/Evolu/Platform.d.ts.map +1 -0
- package/dist/src/Evolu/Platform.js +1 -0
- package/dist/src/Evolu/Protocol.d.ts +401 -0
- package/dist/src/Evolu/Protocol.d.ts.map +1 -0
- package/dist/src/Evolu/Protocol.js +1151 -0
- package/dist/src/Evolu/Public.d.ts +18 -0
- package/dist/src/Evolu/Public.d.ts.map +1 -0
- package/dist/src/Evolu/Public.js +11 -0
- package/dist/src/Evolu/PublicKysely.d.ts +148 -0
- package/dist/src/Evolu/PublicKysely.d.ts.map +1 -0
- package/dist/src/Evolu/PublicKysely.js +185 -0
- package/dist/src/Evolu/Query.d.ts +63 -0
- package/dist/src/Evolu/Query.d.ts.map +1 -0
- package/dist/src/Evolu/Query.js +61 -0
- package/dist/src/Evolu/Relay.d.ts +13 -0
- package/dist/src/Evolu/Relay.d.ts.map +1 -0
- package/dist/src/Evolu/Relay.js +109 -0
- package/dist/src/Evolu/Schema.d.ts +201 -0
- package/dist/src/Evolu/Schema.d.ts.map +1 -0
- package/dist/src/Evolu/Schema.js +150 -0
- package/dist/src/Evolu/Storage.d.ts +49 -0
- package/dist/src/Evolu/Storage.d.ts.map +1 -0
- package/dist/src/Evolu/Storage.js +1111 -0
- package/dist/src/Evolu/Sync.d.ts +59 -0
- package/dist/src/Evolu/Sync.d.ts.map +1 -0
- package/dist/src/Evolu/Sync.js +29 -0
- package/dist/src/Evolu/Timestamp.d.ts +106 -0
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -0
- package/dist/src/Evolu/Timestamp.js +179 -0
- package/dist/src/Function.d.ts +54 -0
- package/dist/src/Function.d.ts.map +1 -0
- package/dist/src/Function.js +38 -0
- package/dist/src/ManyToManyMap.d.ts +26 -0
- package/dist/src/ManyToManyMap.d.ts.map +1 -0
- package/dist/src/ManyToManyMap.js +92 -0
- package/dist/src/NanoId.d.ts +27 -0
- package/dist/src/NanoId.d.ts.map +1 -0
- package/dist/src/NanoId.js +6 -0
- package/dist/src/Number.d.ts +42 -0
- package/dist/src/Number.d.ts.map +1 -0
- package/dist/src/Number.js +55 -0
- package/dist/src/Object.d.ts +35 -0
- package/dist/src/Object.d.ts.map +1 -0
- package/dist/src/Object.js +36 -0
- package/dist/src/Order.d.ts +90 -0
- package/dist/src/Order.d.ts.map +1 -0
- package/dist/src/Order.js +85 -0
- package/dist/src/Promise.d.ts +180 -0
- package/dist/src/Promise.d.ts.map +1 -0
- package/dist/src/Promise.js +176 -0
- package/dist/src/Random.d.ts +52 -0
- package/dist/src/Random.d.ts.map +1 -0
- package/dist/src/Random.js +29 -0
- package/dist/src/Ref.d.ts +40 -0
- package/dist/src/Ref.d.ts.map +1 -0
- package/dist/src/Ref.js +13 -0
- package/dist/src/Result.d.ts +421 -0
- package/dist/src/Result.d.ts.map +1 -0
- package/dist/src/Result.js +357 -0
- package/dist/src/Skiplist.d.ts +23 -0
- package/dist/src/Skiplist.d.ts.map +1 -0
- package/dist/src/Skiplist.js +58 -0
- package/dist/src/Sqlite.d.ts +116 -52
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +183 -67
- package/dist/src/Store.d.ts +45 -8
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +33 -17
- package/dist/src/String.d.ts +2 -0
- package/dist/src/String.d.ts.map +1 -0
- package/dist/src/String.js +14 -0
- package/dist/src/Time.d.ts +20 -0
- package/dist/src/Time.d.ts.map +1 -0
- package/dist/src/Time.js +25 -0
- package/dist/src/Type.d.ts +1937 -0
- package/dist/src/Type.d.ts.map +1 -0
- package/dist/src/Type.js +2002 -0
- package/dist/src/Types.d.ts +188 -0
- package/dist/src/Types.d.ts.map +1 -0
- package/dist/src/Types.js +6 -0
- package/dist/src/WebSocket.d.ts +112 -0
- package/dist/src/WebSocket.d.ts.map +1 -0
- package/dist/src/WebSocket.js +139 -0
- package/dist/src/Worker.d.ts +44 -0
- package/dist/src/Worker.d.ts.map +1 -0
- package/dist/src/Worker.js +66 -0
- package/dist/src/index.d.ts +24 -11
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +24 -11
- package/package.json +29 -38
- package/src/Array.ts +39 -0
- package/src/Assert.ts +116 -0
- package/src/BigInt.ts +29 -0
- package/src/Buffer.ts +175 -0
- package/src/Callbacks.ts +43 -0
- package/src/Console.ts +159 -0
- package/src/Crypto.ts +169 -115
- package/src/Eq.ts +204 -0
- package/src/Error.ts +57 -20
- package/src/Evolu/Config.ts +83 -0
- package/src/Evolu/Db.ts +1275 -0
- package/src/Evolu/Diff.ts +142 -0
- package/src/Evolu/Evolu.ts +947 -0
- package/src/Evolu/Internal.ts +26 -0
- package/src/Evolu/Kysely.ts +38 -0
- package/src/Evolu/Owner.ts +296 -0
- package/src/Evolu/Platform.ts +27 -0
- package/src/Evolu/Protocol.ts +1857 -0
- package/src/Evolu/Public.ts +43 -0
- package/src/Evolu/PublicKysely.ts +240 -0
- package/src/Evolu/Query.ts +167 -0
- package/src/Evolu/Relay.ts +142 -0
- package/src/Evolu/Schema.ts +417 -0
- package/src/Evolu/Storage.ts +1281 -0
- package/src/Evolu/Sync.ts +105 -0
- package/src/Evolu/Timestamp.ts +311 -0
- package/src/Function.ts +58 -0
- package/src/ManyToManyMap.ts +140 -0
- package/src/NanoId.ts +39 -0
- package/src/Number.ts +90 -0
- package/src/Object.ts +64 -0
- package/src/Order.ts +113 -0
- package/src/Promise.ts +295 -0
- package/src/Random.ts +68 -0
- package/src/Ref.ts +63 -0
- package/src/Result.ts +453 -0
- package/src/Skiplist.ts +102 -0
- package/src/Sqlite.ts +366 -153
- package/src/Store.ts +79 -36
- package/src/String.ts +10 -0
- package/src/Time.ts +36 -0
- package/src/Type.ts +3978 -0
- package/src/Types.ts +209 -0
- package/src/WebSocket.ts +273 -0
- package/src/Worker.ts +129 -0
- package/src/index.ts +24 -11
- package/dist/src/Config.d.ts +0 -56
- package/dist/src/Config.d.ts.map +0 -1
- package/dist/src/Config.js +0 -39
- package/dist/src/Crdt.d.ts +0 -89
- package/dist/src/Crdt.d.ts.map +0 -1
- package/dist/src/Crdt.js +0 -181
- package/dist/src/Db.d.ts +0 -107
- package/dist/src/Db.d.ts.map +0 -1
- package/dist/src/Db.js +0 -443
- package/dist/src/Diff.d.ts +0 -27
- package/dist/src/Diff.d.ts.map +0 -1
- package/dist/src/Diff.js +0 -84
- package/dist/src/Evolu.d.ts +0 -426
- package/dist/src/Evolu.d.ts.map +0 -1
- package/dist/src/Evolu.js +0 -333
- package/dist/src/Model.d.ts +0 -141
- package/dist/src/Model.d.ts.map +0 -1
- package/dist/src/Model.js +0 -125
- package/dist/src/Murmurhash.d.ts +0 -2
- package/dist/src/Murmurhash.d.ts.map +0 -1
- package/dist/src/Murmurhash.js +0 -60
- package/dist/src/Owner.d.ts +0 -33
- package/dist/src/Owner.d.ts.map +0 -1
- package/dist/src/Owner.js +0 -26
- package/dist/src/Platform.d.ts +0 -37
- package/dist/src/Platform.d.ts.map +0 -1
- package/dist/src/Platform.js +0 -11
- package/dist/src/Protobuf.d.ts +0 -81
- package/dist/src/Protobuf.d.ts.map +0 -1
- package/dist/src/Protobuf.js +0 -92
- package/dist/src/Public.d.ts +0 -13
- package/dist/src/Public.d.ts.map +0 -1
- package/dist/src/Public.js +0 -6
- package/dist/src/Socket.d.ts +0 -8
- package/dist/src/Socket.d.ts.map +0 -1
- package/dist/src/Socket.js +0 -51
- package/dist/src/Sql.d.ts +0 -12
- package/dist/src/Sql.d.ts.map +0 -1
- package/dist/src/Sql.js +0 -30
- package/dist/src/Sync.d.ts +0 -70
- package/dist/src/Sync.d.ts.map +0 -1
- package/dist/src/Sync.js +0 -127
- package/src/Config.ts +0 -119
- package/src/Crdt.ts +0 -361
- package/src/Db.ts +0 -955
- package/src/Diff.ts +0 -114
- package/src/Evolu.ts +0 -1016
- package/src/Model.ts +0 -233
- package/src/Murmurhash.ts +0 -70
- package/src/Owner.ts +0 -69
- package/src/Platform.ts +0 -47
- package/src/Protobuf.ts +0 -155
- package/src/Public.ts +0 -12
- package/src/Socket.ts +0 -83
- package/src/Sql.ts +0 -41
- package/src/Sync.ts +0 -315
package/src/Crypto.ts
CHANGED
|
@@ -1,137 +1,191 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 🔒
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { xchacha20poly1305 } from "@noble/ciphers/chacha";
|
|
4
8
|
import { hmac } from "@noble/hashes/hmac";
|
|
5
|
-
import { sha512 } from "@noble/hashes/
|
|
9
|
+
import { sha512 } from "@noble/hashes/sha2";
|
|
6
10
|
import { randomBytes } from "@noble/hashes/utils";
|
|
7
11
|
import * as bip39 from "@scure/bip39";
|
|
8
12
|
import { wordlist } from "@scure/bip39/wordlists/english";
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
import { urlAlphabet } from "nanoid";
|
|
14
|
+
import { getOrThrow, Result, trySync } from "./Result.js";
|
|
15
|
+
import {
|
|
16
|
+
brand,
|
|
17
|
+
Id,
|
|
18
|
+
length,
|
|
19
|
+
Mnemonic,
|
|
20
|
+
NonNegativeInt,
|
|
21
|
+
Uint8Array,
|
|
22
|
+
} from "./Type.js";
|
|
23
|
+
import { Brand } from "./Types.js";
|
|
24
|
+
|
|
25
|
+
/** `Uint8Array` created by {@link createRandomBytes}. */
|
|
26
|
+
export type RandomBytes = Uint8Array & Brand<"RandomBytes">;
|
|
27
|
+
|
|
28
|
+
export type CreateRandomBytes = (bytesLength?: number) => RandomBytes;
|
|
29
|
+
|
|
30
|
+
export interface CreateRandomBytesDep {
|
|
31
|
+
readonly createRandomBytes: CreateRandomBytes;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Cryptographically secure PRNG. Uses internal OS-level crypto.getRandomValues. */
|
|
35
|
+
export const createRandomBytes: CreateRandomBytes = (bytesLength = 32) =>
|
|
36
|
+
randomBytes(bytesLength) as RandomBytes;
|
|
37
|
+
|
|
38
|
+
export type CreateMnemonic = () => Mnemonic;
|
|
39
|
+
|
|
40
|
+
export interface CreateMnemonicDep {
|
|
41
|
+
readonly createMnemonic: CreateMnemonic;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const createEnglishMnemonic: CreateMnemonic = () =>
|
|
45
|
+
bip39.generateMnemonic(wordlist, 128) as Mnemonic;
|
|
46
|
+
|
|
47
|
+
export type MnemonicSeed = Uint8Array & Brand<"MnemonicSeed">;
|
|
48
|
+
|
|
49
|
+
export const mnemonicToMnemonicSeed = (mnemonic: Mnemonic): MnemonicSeed =>
|
|
50
|
+
bip39.mnemonicToSeedSync(mnemonic) as MnemonicSeed;
|
|
14
51
|
|
|
15
52
|
/**
|
|
16
|
-
*
|
|
53
|
+
* SLIP21.
|
|
17
54
|
*
|
|
18
|
-
*
|
|
19
|
-
* order chosen from a predefined list. The purpose of the BIP39 mnemonic is to
|
|
20
|
-
* provide a human-readable way of storing a private key.
|
|
55
|
+
* https://github.com/satoshilabs/slips/blob/master/slip-0021.md
|
|
21
56
|
*/
|
|
22
|
-
export
|
|
57
|
+
export const createSlip21 = (
|
|
58
|
+
seed: MnemonicSeed,
|
|
59
|
+
path: ReadonlyArray<string>,
|
|
60
|
+
): Uint8Array => {
|
|
61
|
+
let m = hmac(sha512, "Symmetric key seed", seed);
|
|
62
|
+
for (const component of path) {
|
|
63
|
+
const p = new TextEncoder().encode(component);
|
|
64
|
+
const e = new globalThis.Uint8Array(p.byteLength + 1);
|
|
65
|
+
e[0] = 0;
|
|
66
|
+
e.set(p, 1);
|
|
67
|
+
m = hmac(sha512, m.slice(0, 32), e);
|
|
68
|
+
}
|
|
69
|
+
return m.slice(32, 64);
|
|
70
|
+
};
|
|
23
71
|
|
|
24
72
|
/**
|
|
25
|
-
*
|
|
73
|
+
* Creates a 21-character Base64URL ID (also known as nanoid) from a SLIP-21
|
|
74
|
+
* derived key.
|
|
75
|
+
*
|
|
76
|
+
* Reduces the 256-bit SLIP-21 output to 126 bits (21 chars × 6 bits) for a
|
|
77
|
+
* compact, human-readable, and shareable identifier suitable for UI display or
|
|
78
|
+
* URL use. While this lowers entropy, 126 bits remains cryptographically secure
|
|
79
|
+
* for uniqueness and unpredictability in most applications (comparable to
|
|
80
|
+
* UUIDv4's 122 bits).
|
|
26
81
|
*
|
|
27
|
-
*
|
|
28
|
-
* order chosen from a predefined list. The purpose of the BIP39 mnemonic is to
|
|
29
|
-
* provide a human-readable way of storing a private key.
|
|
82
|
+
* See https://github.com/satoshilabs/slips/blob/master/slip-0021.md
|
|
30
83
|
*/
|
|
31
|
-
export const
|
|
32
|
-
|
|
84
|
+
export const createSlip21Id = (
|
|
85
|
+
seed: MnemonicSeed,
|
|
86
|
+
path: ReadonlyArray<string>,
|
|
87
|
+
): Id => {
|
|
88
|
+
const slip21 = createSlip21(seed, path);
|
|
89
|
+
let id = "" as Id;
|
|
90
|
+
|
|
91
|
+
// Convert the key to the Id/NanoId/Base64Url format.
|
|
92
|
+
for (let i = 0; i < 21; i++) {
|
|
93
|
+
id = (id + urlAlphabet[slip21[i] & 63]) as Id;
|
|
94
|
+
}
|
|
33
95
|
|
|
34
|
-
|
|
35
|
-
export const parseMnemonic = (
|
|
36
|
-
mnemonic: string,
|
|
37
|
-
): Effect.Effect<Mnemonic, InvalidMnemonicError> => {
|
|
38
|
-
const mnemonicTrimmed = mnemonic.trim();
|
|
39
|
-
return bip39.validateMnemonic(mnemonicTrimmed, wordlist)
|
|
40
|
-
? Effect.succeed(mnemonicTrimmed as Mnemonic)
|
|
41
|
-
: Effect.fail<InvalidMnemonicError>({
|
|
42
|
-
_tag: "InvalidMnemonicError",
|
|
43
|
-
});
|
|
96
|
+
return id;
|
|
44
97
|
};
|
|
45
98
|
|
|
46
|
-
|
|
47
|
-
|
|
99
|
+
/** The encryption key for {@link SymmetricCrypto}. */
|
|
100
|
+
export const EncryptionKey = brand("EncryptionKey", length(32)(Uint8Array));
|
|
101
|
+
export type EncryptionKey = typeof EncryptionKey.Type;
|
|
102
|
+
|
|
103
|
+
export const createEncryptionKey = (seed: MnemonicSeed): EncryptionKey =>
|
|
104
|
+
createSlip21(seed, ["Evolu", "Encryption Key"]) as EncryptionKey;
|
|
105
|
+
|
|
106
|
+
/** Symmetric cryptography. */
|
|
107
|
+
export interface SymmetricCrypto {
|
|
108
|
+
readonly nonceLength: NonNegativeInt;
|
|
109
|
+
|
|
110
|
+
readonly encrypt: (
|
|
111
|
+
plaintext: Uint8Array,
|
|
112
|
+
encryptionKey: EncryptionKey,
|
|
113
|
+
) => {
|
|
114
|
+
readonly nonce: Uint8Array;
|
|
115
|
+
readonly ciphertext: Uint8Array;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
readonly decrypt: (
|
|
119
|
+
ciphertext: Uint8Array,
|
|
120
|
+
encryptionKey: EncryptionKey,
|
|
121
|
+
nonce: Uint8Array,
|
|
122
|
+
) => Result<Uint8Array, SymmetricCryptoDecryptError>;
|
|
48
123
|
}
|
|
49
124
|
|
|
50
|
-
export
|
|
51
|
-
|
|
125
|
+
export interface SymmetricCryptoDep {
|
|
126
|
+
readonly symmetricCrypto: SymmetricCrypto;
|
|
127
|
+
}
|
|
52
128
|
|
|
53
|
-
export
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
readonly nodeId: Effect.Effect<NodeId>;
|
|
58
|
-
readonly rowId: Effect.Effect<Id>;
|
|
59
|
-
}
|
|
60
|
-
>() {}
|
|
61
|
-
|
|
62
|
-
export const createNanoIdGeneratorLive = (
|
|
63
|
-
customAlphabet: (
|
|
64
|
-
alphabet: string,
|
|
65
|
-
defaultSize?: number,
|
|
66
|
-
) => (size?: number) => string,
|
|
67
|
-
nanoid: (size?: number) => string,
|
|
68
|
-
): Layer.Layer<NanoIdGenerator, never, never> => {
|
|
69
|
-
const nanoidForNodeId = customAlphabet("0123456789abcdef", 16);
|
|
70
|
-
return Layer.succeed(
|
|
71
|
-
NanoIdGenerator,
|
|
72
|
-
NanoIdGenerator.of({
|
|
73
|
-
nanoid: Effect.sync(() => nanoid() as NanoId),
|
|
74
|
-
nodeId: Effect.sync(() => nanoidForNodeId() as NodeId),
|
|
75
|
-
rowId: Effect.sync(() => nanoid() as Id),
|
|
76
|
-
}),
|
|
77
|
-
);
|
|
78
|
-
};
|
|
129
|
+
export interface SymmetricCryptoDecryptError {
|
|
130
|
+
readonly type: "SymmetricCryptoDecryptError";
|
|
131
|
+
readonly error: unknown;
|
|
132
|
+
}
|
|
79
133
|
|
|
80
|
-
|
|
134
|
+
/**
|
|
135
|
+
* XChaCha20-Poly1305 encryption
|
|
136
|
+
*
|
|
137
|
+
* https://github.com/paulmillr/noble-ciphers?tab=readme-ov-file#which-cipher-should-i-pick
|
|
138
|
+
*/
|
|
139
|
+
export const createSymmetricCrypto = (
|
|
140
|
+
deps: CreateRandomBytesDep,
|
|
141
|
+
): SymmetricCrypto => {
|
|
142
|
+
const nonceLength = getOrThrow(NonNegativeInt.from(24));
|
|
81
143
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
S.brand("NodeId"),
|
|
85
|
-
);
|
|
86
|
-
export type NodeId = typeof NodeId.Type;
|
|
144
|
+
const symmetricCrypto: SymmetricCrypto = {
|
|
145
|
+
nonceLength,
|
|
87
146
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
return m.slice(32, 64);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
export class SecretBox extends Context.Tag("SecretBox")<
|
|
107
|
-
SecretBox,
|
|
108
|
-
{
|
|
109
|
-
readonly seal: (
|
|
110
|
-
key: Uint8Array,
|
|
111
|
-
plaintext: Uint8Array,
|
|
112
|
-
) => Effect.Effect<Uint8Array>;
|
|
113
|
-
|
|
114
|
-
readonly open: (
|
|
115
|
-
key: Uint8Array,
|
|
116
|
-
ciphertext: Uint8Array,
|
|
117
|
-
) => Effect.Effect<Uint8Array>;
|
|
118
|
-
}
|
|
119
|
-
>() {
|
|
120
|
-
static Live = Layer.succeed(
|
|
121
|
-
SecretBox,
|
|
122
|
-
SecretBox.of({
|
|
123
|
-
seal: (key, plaintext) =>
|
|
124
|
-
Effect.sync(() => {
|
|
125
|
-
const nonce = randomBytes(24);
|
|
126
|
-
const ciphertext = secretbox(key, nonce).seal(plaintext);
|
|
127
|
-
return concatBytes(nonce, ciphertext);
|
|
128
|
-
}),
|
|
129
|
-
open: (key, ciphertext) =>
|
|
130
|
-
Effect.sync(() => {
|
|
131
|
-
const nonce = ciphertext.subarray(0, 24);
|
|
132
|
-
const ciphertextWithoutNonce = ciphertext.subarray(24);
|
|
133
|
-
return secretbox(key, nonce).open(ciphertextWithoutNonce);
|
|
147
|
+
encrypt: (plaintext, encryptionKey) => {
|
|
148
|
+
const nonce = deps.createRandomBytes(nonceLength);
|
|
149
|
+
const ciphertext = xchacha20poly1305(encryptionKey, nonce).encrypt(
|
|
150
|
+
plaintext,
|
|
151
|
+
);
|
|
152
|
+
return { nonce, ciphertext };
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
decrypt: (ciphertext, encryptionKey, nonce) =>
|
|
156
|
+
trySync(
|
|
157
|
+
() => xchacha20poly1305(encryptionKey, nonce).decrypt(ciphertext),
|
|
158
|
+
(error): SymmetricCryptoDecryptError => ({
|
|
159
|
+
type: "SymmetricCryptoDecryptError",
|
|
160
|
+
error,
|
|
134
161
|
}),
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
162
|
+
),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return symmetricCrypto;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Returns the PADMÉ padded length for a given input length.
|
|
170
|
+
*
|
|
171
|
+
* PADMÉ limits information leakage about the length of the plain-text for a
|
|
172
|
+
* wide range of encrypted data sizes. See the PURBs paper for details:
|
|
173
|
+
* https://bford.info/pub/sec/purb.pdf
|
|
174
|
+
*/
|
|
175
|
+
export const padmePaddedLength = (length: NonNegativeInt): NonNegativeInt => {
|
|
176
|
+
if (length <= 0) return 0 as NonNegativeInt;
|
|
177
|
+
const e = 31 - Math.clz32(length >>> 0);
|
|
178
|
+
const s = 32 - Math.clz32(e >>> 0);
|
|
179
|
+
const z = Math.max(0, e - s);
|
|
180
|
+
const mask = (1 << z) - 1;
|
|
181
|
+
return ((length + mask) & ~mask) as NonNegativeInt;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Returns the PADMÉ padding length for a given input length. Uses
|
|
186
|
+
* {@link padmePaddedLength}.
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
export const padmePaddingLength = (length: NonNegativeInt): NonNegativeInt => {
|
|
190
|
+
return (padmePaddedLength(length) - length) as NonNegativeInt;
|
|
191
|
+
};
|
package/src/Eq.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { JsonArray, JsonObject, JsonValue, JsonValueInput } from "./Type.js";
|
|
2
|
+
import { Order } from "./Order.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compares two values of the same type `A` for equality.
|
|
6
|
+
*
|
|
7
|
+
* Equality functions start with an 'eq' prefix, e.g., `eqString`.
|
|
8
|
+
*
|
|
9
|
+
* TODO: Explain, examples (composition etc.)
|
|
10
|
+
*/
|
|
11
|
+
export type Eq<in A> = (x: A, y: A) => boolean;
|
|
12
|
+
|
|
13
|
+
export const eqStrict = <A>(x: A, y: A): boolean => x === y;
|
|
14
|
+
|
|
15
|
+
export const eqString: Eq<string> = eqStrict;
|
|
16
|
+
export const eqNumber: Eq<number> = eqStrict;
|
|
17
|
+
export const eqBigInt: Eq<bigint> = eqStrict;
|
|
18
|
+
export const eqBoolean: Eq<boolean> = eqStrict;
|
|
19
|
+
export const eqUndefined: Eq<undefined> = eqStrict;
|
|
20
|
+
export const eqNull: Eq<null> = eqStrict;
|
|
21
|
+
|
|
22
|
+
/** Derives an {@link Eq} from an {@link Order}. */
|
|
23
|
+
export const eqFromOrder =
|
|
24
|
+
<A>(order: Order<A>): Eq<A> =>
|
|
25
|
+
(x, y) =>
|
|
26
|
+
order(x, y) === 0;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates an equivalence function for array-like structures based on an
|
|
30
|
+
* equivalence for their elements.
|
|
31
|
+
*
|
|
32
|
+
* ### Example
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const eqArrayNumber = createEqArrayLike(eqNumber);
|
|
36
|
+
* eqArrayNumber([1, 2, 3], [1, 2, 3]); // true (works with regular arrays)
|
|
37
|
+
* eqArrayNumber(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3])); // true (works with Uint8Array)
|
|
38
|
+
* eqArrayNumber([1, 2, 3], [1, 2, 4]); // false
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export const createEqArrayLike =
|
|
42
|
+
<A>(item: Eq<A>): Eq<ArrayLike<A>> =>
|
|
43
|
+
(x, y) => {
|
|
44
|
+
if (x === y) return true;
|
|
45
|
+
if (x.length !== y.length) return false;
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < x.length; i++) {
|
|
48
|
+
if (!item(x[i], y[i])) return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Compares two array-like structures of numbers for equality.
|
|
56
|
+
*
|
|
57
|
+
* ### Example
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* eqArrayNumber([1, 2, 3], [1, 2, 3]); // true (works with regular arrays)
|
|
61
|
+
* eqArrayNumber(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3])); // true (works with Uint8Array)
|
|
62
|
+
* eqArrayNumber([1, 2, 3], [1, 2, 4]); // false
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export const eqArrayNumber = createEqArrayLike(eqNumber);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Creates an equivalence function for objects based on an equivalence for their
|
|
69
|
+
* fields.
|
|
70
|
+
*
|
|
71
|
+
* ### Example
|
|
72
|
+
*
|
|
73
|
+
* ```ts
|
|
74
|
+
* const eqObjectNumber = createEqObject({ a: eqNumber });
|
|
75
|
+
* eqObjectNumber({ a: 1 }, { a: 1 }); // true
|
|
76
|
+
* eqObjectNumber({ a: 1 }, { a: 2 }); // false
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export const createEqObject =
|
|
80
|
+
<A>(eqs: { [K in keyof A]: Eq<A[K]> }): Eq<{
|
|
81
|
+
readonly [K in keyof A]: A[K];
|
|
82
|
+
}> =>
|
|
83
|
+
(x, y) => {
|
|
84
|
+
if (x === y) return true;
|
|
85
|
+
for (const key in eqs) {
|
|
86
|
+
if (!eqs[key](x[key], y[key])) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Deeply compares two {@link JsonValue} values for equality.
|
|
95
|
+
*
|
|
96
|
+
* - Uses an iterative approach with a stack to handle large or deeply nested
|
|
97
|
+
* objects without risking stack overflow.
|
|
98
|
+
* - Handles circular references with a WeakMap to prevent infinite loops.
|
|
99
|
+
* - Unlike JSON.stringify, this function directly compares values, avoiding
|
|
100
|
+
* serialization overhead and leveraging short-circuit evaluation for faster
|
|
101
|
+
* failure on mismatched structures.
|
|
102
|
+
*
|
|
103
|
+
* ### Example
|
|
104
|
+
*
|
|
105
|
+
* ```ts
|
|
106
|
+
* const obj1: Json = { name: "Alice", hobbies: ["reading", "hiking"] };
|
|
107
|
+
* const obj2: Json = { name: "Alice", hobbies: ["reading", "hiking"] };
|
|
108
|
+
* console.log(eqJson(obj1, obj2)); // true
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export const eqJsonValue = (a: JsonValue, b: JsonValue): boolean => {
|
|
112
|
+
const stack: Array<[JsonValue, JsonValue]> = [[a, b]];
|
|
113
|
+
|
|
114
|
+
const seen = new WeakMap<object, WeakSet<object>>();
|
|
115
|
+
|
|
116
|
+
while (stack.length > 0) {
|
|
117
|
+
const [x, y] = stack.pop()!;
|
|
118
|
+
|
|
119
|
+
if (x === y) continue;
|
|
120
|
+
|
|
121
|
+
const typeX = typeof x;
|
|
122
|
+
const typeY = typeof y;
|
|
123
|
+
|
|
124
|
+
if (typeX !== typeY || x === null || y === null) return false;
|
|
125
|
+
|
|
126
|
+
if (typeX === "number" && isNaN(x as number) && isNaN(y as number)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (typeX === "object") {
|
|
131
|
+
const isArrayX = Array.isArray(x);
|
|
132
|
+
const isArrayY = Array.isArray(y);
|
|
133
|
+
|
|
134
|
+
if (isArrayX !== isArrayY) return false;
|
|
135
|
+
|
|
136
|
+
const xObj = x as object;
|
|
137
|
+
const yObj = y as object;
|
|
138
|
+
|
|
139
|
+
if (seen.has(xObj)) {
|
|
140
|
+
const ySet = seen.get(xObj)!;
|
|
141
|
+
if (ySet.has(yObj)) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
ySet.add(yObj);
|
|
145
|
+
} else {
|
|
146
|
+
const ySet = new WeakSet<object>();
|
|
147
|
+
ySet.add(yObj);
|
|
148
|
+
seen.set(xObj, ySet);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (isArrayX && isArrayY) {
|
|
152
|
+
const xArr = x as JsonArray;
|
|
153
|
+
const yArr = y as JsonArray;
|
|
154
|
+
|
|
155
|
+
if (xArr.length !== yArr.length) return false;
|
|
156
|
+
for (let i = 0; i < xArr.length; i++) {
|
|
157
|
+
stack.push([xArr[i], yArr[i]]);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
const xObjTyped = x as JsonObject;
|
|
161
|
+
const yObjTyped = y as JsonObject;
|
|
162
|
+
|
|
163
|
+
const xKeys = Object.keys(xObjTyped);
|
|
164
|
+
const yKeys = Object.keys(yObjTyped);
|
|
165
|
+
|
|
166
|
+
if (xKeys.length !== yKeys.length) return false;
|
|
167
|
+
|
|
168
|
+
const yKeySet = new Set(yKeys);
|
|
169
|
+
|
|
170
|
+
for (const key of xKeys) {
|
|
171
|
+
if (!yKeySet.has(key)) return false;
|
|
172
|
+
stack.push([xObjTyped[key], yObjTyped[key]]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return true;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Deeply compares two {@link JsonValueInput} values for equality.
|
|
185
|
+
*
|
|
186
|
+
* - Uses an iterative approach with a stack to handle large or deeply nested
|
|
187
|
+
* objects without risking stack overflow.
|
|
188
|
+
* - Handles circular references with a WeakMap to prevent infinite loops.
|
|
189
|
+
* - Unlike JSON.stringify, this function directly compares values, avoiding
|
|
190
|
+
* serialization overhead and leveraging short-circuit evaluation for faster
|
|
191
|
+
* failure on mismatched structures.
|
|
192
|
+
*
|
|
193
|
+
* ### Example
|
|
194
|
+
*
|
|
195
|
+
* ```ts
|
|
196
|
+
* const obj1: Json = { name: "Alice", hobbies: ["reading", "hiking"] };
|
|
197
|
+
* const obj2: Json = { name: "Alice", hobbies: ["reading", "hiking"] };
|
|
198
|
+
* console.log(eqJson(obj1, obj2)); // true
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
export const eqJsonValueInput = (
|
|
202
|
+
a: JsonValueInput,
|
|
203
|
+
b: JsonValueInput,
|
|
204
|
+
): boolean => eqJsonValue(a as JsonValue, b as JsonValue);
|
package/src/Error.ts
CHANGED
|
@@ -1,29 +1,66 @@
|
|
|
1
|
-
import { TimestampError } from "./Crdt.js";
|
|
2
|
-
|
|
3
|
-
/** The EvoluError type is used to represent errors that can occur in Evolu. */
|
|
4
|
-
export type EvoluError = TimestampError | UnexpectedError;
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* A serializable representation of an error for safe transfer between execution
|
|
3
|
+
* contexts, such as Web Workers and the main thread.
|
|
4
|
+
*
|
|
5
|
+
* Use this type for unknown (unexpected) errors that need to be transferred
|
|
6
|
+
* across boundaries where native Error objects cannot be sent directly. Not
|
|
7
|
+
* intended for regular (expected) errors.
|
|
8
|
+
*
|
|
9
|
+
* The `error` property contains a plain object with error details, a string, or
|
|
10
|
+
* a fallback value if serialization fails to preserve as much debugging
|
|
11
|
+
* information as possible.
|
|
9
12
|
*/
|
|
10
|
-
export interface
|
|
11
|
-
readonly
|
|
13
|
+
export interface TransferableError {
|
|
14
|
+
readonly type: "TransferableError";
|
|
12
15
|
readonly error: unknown;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
/** Creates a {@link TransferableError} from an unknown error. */
|
|
19
|
+
export const createTransferableError = (error: unknown): TransferableError => {
|
|
20
|
+
const convertError = (err: Error): Record<string, unknown> => {
|
|
21
|
+
const transferableError: Record<string, unknown> =
|
|
22
|
+
Object.getOwnPropertyNames(err).reduce<Record<string, unknown>>(
|
|
23
|
+
(acc, key) => {
|
|
24
|
+
const value = (err as never)[key] as unknown;
|
|
25
|
+
if (key === "cause" && value instanceof Error) {
|
|
26
|
+
// Recursively process the `cause` property
|
|
27
|
+
acc[key] = convertError(value);
|
|
28
|
+
} else if (typeof value !== "function") {
|
|
29
|
+
acc[key] = value;
|
|
30
|
+
}
|
|
31
|
+
return acc;
|
|
32
|
+
},
|
|
33
|
+
{},
|
|
34
|
+
);
|
|
35
|
+
return transferableError;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (error instanceof Error) {
|
|
39
|
+
return {
|
|
40
|
+
type: "TransferableError",
|
|
41
|
+
error: convertError(error),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
19
44
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (error instanceof Error)
|
|
45
|
+
try {
|
|
46
|
+
// Clone other values that are transferable
|
|
23
47
|
return {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
stack: error.stack,
|
|
48
|
+
type: "TransferableError",
|
|
49
|
+
error: structuredClone(error),
|
|
27
50
|
};
|
|
28
|
-
|
|
51
|
+
} catch {
|
|
52
|
+
// Fallback for non-transferable or problematic values
|
|
53
|
+
try {
|
|
54
|
+
return {
|
|
55
|
+
type: "TransferableError",
|
|
56
|
+
error: String(error), // Attempt to convert to a string
|
|
57
|
+
};
|
|
58
|
+
} catch {
|
|
59
|
+
// Final fallback if even `String(error)` fails
|
|
60
|
+
return {
|
|
61
|
+
type: "TransferableError",
|
|
62
|
+
error: "[Unserializable Object]",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
29
66
|
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ConsoleConfig } from "../Console.js";
|
|
2
|
+
import { getOrThrow } from "../Result.js";
|
|
3
|
+
import { Mnemonic, SimpleName } from "../Type.js";
|
|
4
|
+
import type { DbIndexesBuilder } from "./Kysely.js";
|
|
5
|
+
|
|
6
|
+
export interface Config extends ConsoleConfig {
|
|
7
|
+
/**
|
|
8
|
+
* The name of Evolu instances. Evolu is multitenant - it can run more
|
|
9
|
+
* instances concurrently. Every Evolu instance has to have its own unique
|
|
10
|
+
* name. Database files are separated and invisible to each other.
|
|
11
|
+
*
|
|
12
|
+
* The default value is: `Evolu`.
|
|
13
|
+
*
|
|
14
|
+
* ### Example
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* // name: getOrThrow(SimpleName.from("MyApp"))
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
readonly name: SimpleName;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* URL for Evolu sync and backup server.
|
|
24
|
+
*
|
|
25
|
+
* The default value is `https://evolu.world`.
|
|
26
|
+
*/
|
|
27
|
+
readonly syncUrl: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* URL to reload browser tabs after reset or restore.
|
|
31
|
+
*
|
|
32
|
+
* The default value is `/`.
|
|
33
|
+
*/
|
|
34
|
+
readonly reloadUrl: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Maximum physical clock drift allowed in ms.
|
|
38
|
+
*
|
|
39
|
+
* The default value is 5 * 60 * 1000 (5 minutes).
|
|
40
|
+
*/
|
|
41
|
+
readonly maxDrift: number;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Use the `indexes` option to define SQLite indexes.
|
|
45
|
+
*
|
|
46
|
+
* Table and column names are not typed because Kysely doesn't support it.
|
|
47
|
+
*
|
|
48
|
+
* https://medium.com/@JasonWyatt/squeezing-performance-from-sqlite-indexes-indexes-c4e175f3c346
|
|
49
|
+
*
|
|
50
|
+
* ### Example
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* const evolu = createEvolu(evoluReactDeps)(Schema, {
|
|
54
|
+
* indexes: (create) => [
|
|
55
|
+
* create("todoCreatedAt").on("todo").column("createdAt"),
|
|
56
|
+
* create("todoCategoryCreatedAt")
|
|
57
|
+
* .on("todoCategory")
|
|
58
|
+
* .column("createdAt"),
|
|
59
|
+
* ],
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
readonly indexes?: DbIndexesBuilder;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Use this option to create Evolu with the specified mnemonic. If omitted,
|
|
67
|
+
* the mnemonic will be autogenerated. That should be the default behavior
|
|
68
|
+
* until special UX requirements are needed (e.g., multitenancy).
|
|
69
|
+
*/
|
|
70
|
+
readonly mnemonic?: Mnemonic;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ConfigDep {
|
|
74
|
+
readonly config: Config;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const defaultConfig: Config = {
|
|
78
|
+
name: getOrThrow(SimpleName.fromParent("Evolu")),
|
|
79
|
+
syncUrl: "https://evolu.world",
|
|
80
|
+
reloadUrl: "/",
|
|
81
|
+
maxDrift: 5 * 60 * 1000,
|
|
82
|
+
enableLogging: false,
|
|
83
|
+
};
|