@evolu/common 6.0.1-preview.9 → 7.1.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 +27 -29
- package/dist/src/Array.d.ts +317 -12
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +219 -9
- package/dist/src/Assert.d.ts +0 -13
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +0 -15
- package/dist/src/Brand.d.ts +75 -0
- package/dist/src/Brand.d.ts.map +1 -0
- package/dist/src/Brand.js +1 -0
- package/dist/src/Buffer.d.ts +1 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +8 -7
- package/dist/src/Cache.d.ts +39 -0
- package/dist/src/Cache.d.ts.map +1 -0
- package/dist/src/Cache.js +47 -0
- package/dist/src/Callbacks.d.ts +45 -12
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +14 -7
- package/dist/src/Console.d.ts +32 -7
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +73 -10
- package/dist/src/Crypto.d.ts +56 -42
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +45 -54
- package/dist/src/Evolu/Db.d.ts +162 -74
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +284 -702
- package/dist/src/Evolu/Diff.d.ts +3 -3
- package/dist/src/Evolu/Diff.d.ts.map +1 -1
- package/dist/src/Evolu/Diff.js +7 -5
- package/dist/src/Evolu/Evolu.d.ts +213 -133
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +189 -180
- package/dist/src/Evolu/Internal.d.ts +2 -4
- package/dist/src/Evolu/Internal.d.ts.map +1 -1
- package/dist/src/Evolu/Internal.js +2 -4
- package/dist/src/Evolu/LocalAuth.d.ts +150 -0
- package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
- package/dist/src/Evolu/LocalAuth.js +174 -0
- package/dist/src/Evolu/Owner.d.ts +273 -120
- package/dist/src/Evolu/Owner.d.ts.map +1 -1
- package/dist/src/Evolu/Owner.js +130 -104
- package/dist/src/Evolu/Platform.d.ts +9 -7
- package/dist/src/Evolu/Platform.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.d.ts +268 -240
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +595 -447
- package/dist/src/Evolu/Public.d.ts +7 -9
- package/dist/src/Evolu/Public.d.ts.map +1 -1
- package/dist/src/Evolu/Public.js +3 -4
- package/dist/src/Evolu/PublicKysely.d.ts.map +1 -1
- package/dist/src/Evolu/PublicKysely.js +3 -4
- package/dist/src/Evolu/Query.d.ts +2 -1
- package/dist/src/Evolu/Query.d.ts.map +1 -1
- package/dist/src/Evolu/Query.js +1 -1
- package/dist/src/Evolu/Relay.d.ts +91 -8
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +214 -88
- package/dist/src/Evolu/Schema.d.ts +125 -47
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +175 -31
- package/dist/src/Evolu/Storage.d.ts +249 -27
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +198 -92
- package/dist/src/Evolu/Sync.d.ts +68 -13
- package/dist/src/Evolu/Sync.d.ts.map +1 -1
- package/dist/src/Evolu/Sync.js +469 -20
- package/dist/src/Evolu/Timestamp.d.ts +83 -30
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +79 -34
- package/dist/src/Identicon.d.ts +35 -0
- package/dist/src/Identicon.d.ts.map +1 -0
- package/dist/src/Identicon.js +143 -0
- package/dist/src/Instances.d.ts +34 -0
- package/dist/src/Instances.d.ts.map +1 -0
- package/dist/src/Instances.js +44 -0
- package/dist/src/Number.d.ts +4 -3
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +5 -4
- package/dist/src/Object.d.ts +10 -4
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +9 -3
- package/dist/src/Order.d.ts +0 -5
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +0 -5
- package/dist/src/Platform.d.ts +15 -0
- package/dist/src/Platform.d.ts.map +1 -0
- package/dist/src/Platform.js +17 -0
- package/dist/src/Random.d.ts +3 -7
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +0 -5
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Relation.d.ts +79 -0
- package/dist/src/Relation.d.ts.map +1 -0
- package/dist/src/Relation.js +127 -0
- package/dist/src/Resources.d.ts +118 -0
- package/dist/src/Resources.d.ts.map +1 -0
- package/dist/src/Resources.js +197 -0
- package/dist/src/Result.d.ts +189 -59
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +30 -241
- package/dist/src/Skiplist.js +2 -1
- package/dist/src/Sqlite.d.ts +89 -5
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +136 -9
- package/dist/src/Store.d.ts +0 -5
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +0 -5
- package/dist/src/Task.d.ts +580 -0
- package/dist/src/Task.d.ts.map +1 -0
- package/dist/src/Task.js +464 -0
- package/dist/src/Time.d.ts +66 -6
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +96 -7
- package/dist/src/Type.d.ts +677 -344
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +718 -467
- package/dist/src/Types.d.ts +38 -75
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/WebSocket.d.ts +5 -7
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +12 -23
- package/dist/src/Worker.d.ts +39 -16
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +22 -9
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -3
- package/package.json +14 -13
- package/src/Array.ts +396 -19
- package/src/Assert.ts +0 -35
- package/src/Brand.ts +75 -0
- package/src/Buffer.ts +7 -7
- package/src/Cache.ts +79 -0
- package/src/Callbacks.ts +62 -22
- package/src/Console.ts +92 -12
- package/src/Crypto.ts +99 -91
- package/src/Evolu/Db.ts +519 -1026
- package/src/Evolu/Diff.ts +7 -5
- package/src/Evolu/Evolu.ts +476 -360
- package/src/Evolu/Internal.ts +2 -4
- package/src/Evolu/LocalAuth.ts +463 -0
- package/src/Evolu/Owner.ts +355 -228
- package/src/Evolu/Platform.ts +9 -9
- package/src/Evolu/Protocol.ts +846 -742
- package/src/Evolu/Public.ts +8 -15
- package/src/Evolu/PublicKysely.ts +4 -5
- package/src/Evolu/Query.ts +3 -2
- package/src/Evolu/Relay.ts +406 -103
- package/src/Evolu/Schema.ts +323 -91
- package/src/Evolu/Storage.ts +559 -137
- package/src/Evolu/Sync.ts +819 -36
- package/src/Evolu/Timestamp.ts +90 -58
- package/src/Identicon.ts +197 -0
- package/src/Instances.ts +90 -0
- package/src/Number.ts +6 -10
- package/src/Object.ts +13 -5
- package/src/Order.ts +0 -6
- package/src/Platform.ts +20 -0
- package/src/Random.ts +3 -8
- package/src/Ref.ts +0 -5
- package/src/Relation.ts +234 -0
- package/src/Resources.ts +367 -0
- package/src/Result.ts +196 -61
- package/src/Skiplist.ts +1 -1
- package/src/Sqlite.ts +152 -17
- package/src/Store.ts +0 -5
- package/src/Task.ts +894 -0
- package/src/Time.ts +179 -10
- package/src/Type.ts +1136 -731
- package/src/Types.ts +43 -77
- package/src/WebSocket.ts +27 -31
- package/src/Worker.ts +72 -29
- package/src/index.ts +8 -3
- package/dist/src/Evolu/Config.d.ts +0 -69
- package/dist/src/Evolu/Config.d.ts.map +0 -1
- package/dist/src/Evolu/Config.js +0 -9
- package/dist/src/Evolu/Kysely.d.ts +0 -6
- package/dist/src/Evolu/Kysely.d.ts.map +0 -1
- package/dist/src/Evolu/Kysely.js +0 -21
- package/dist/src/ManyToManyMap.d.ts +0 -26
- package/dist/src/ManyToManyMap.d.ts.map +0 -1
- package/dist/src/ManyToManyMap.js +0 -92
- package/dist/src/NanoId.d.ts +0 -27
- package/dist/src/NanoId.d.ts.map +0 -1
- package/dist/src/NanoId.js +0 -6
- package/dist/src/Promise.d.ts +0 -180
- package/dist/src/Promise.d.ts.map +0 -1
- package/dist/src/Promise.js +0 -176
- package/src/Evolu/Config.ts +0 -83
- package/src/Evolu/Kysely.ts +0 -38
- package/src/ManyToManyMap.ts +0 -140
- package/src/NanoId.ts +0 -39
- package/src/Promise.ts +0 -295
package/src/Callbacks.ts
CHANGED
|
@@ -1,43 +1,83 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Brand } from "./Brand.js";
|
|
2
|
+
import { RandomBytesDep } from "./Crypto.js";
|
|
3
|
+
import { Result } from "./Result.js";
|
|
4
|
+
import { createId, Id } from "./Type.js";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
7
|
+
* Request-response correlation for callbacks across boundaries.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* Stores callbacks with unique IDs and executes them once with an optional
|
|
10
|
+
* argument. Executed callbacks are automatically removed.
|
|
9
11
|
*
|
|
10
|
-
* This is useful for
|
|
11
|
-
*
|
|
12
|
+
* This is useful for correlating asynchronous request-response operations
|
|
13
|
+
* across boundaries where callback functions cannot be passed directly (e.g.,
|
|
14
|
+
* web workers, message queues).
|
|
15
|
+
*
|
|
16
|
+
* The `execute` method intentionally does not use try-catch or {@link Result}
|
|
17
|
+
* because it's the callback's responsibility to handle its own errors.
|
|
18
|
+
*
|
|
19
|
+
* ### Example
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* // No-argument callbacks
|
|
23
|
+
* const callbacks = createCallbacks(deps);
|
|
24
|
+
* const id = callbacks.register(() => console.log("called"));
|
|
25
|
+
* callbacks.execute(id);
|
|
26
|
+
*
|
|
27
|
+
* // With argument callbacks
|
|
28
|
+
* const stringCallbacks = createCallbacks<string>(deps);
|
|
29
|
+
* const id = stringCallbacks.register((value) => {
|
|
30
|
+
* console.log(value);
|
|
31
|
+
* });
|
|
32
|
+
* stringCallbacks.execute(id, "hello");
|
|
33
|
+
*
|
|
34
|
+
* // Promise.withResolvers pattern
|
|
35
|
+
* const promiseCallbacks = createCallbacks<string>(deps);
|
|
36
|
+
* const { promise, resolve } = Promise.withResolvers<string>();
|
|
37
|
+
* const id = promiseCallbacks.register(resolve);
|
|
38
|
+
* promiseCallbacks.execute(id, "resolved value");
|
|
39
|
+
* await promise; // "resolved value"
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @template T - The type of argument passed to callbacks (defaults to undefined
|
|
43
|
+
* for no-argument callbacks)
|
|
12
44
|
*/
|
|
13
|
-
export interface Callbacks {
|
|
45
|
+
export interface Callbacks<T = undefined> {
|
|
14
46
|
/** Registers a callback function and returns a unique ID. */
|
|
15
|
-
readonly register: (callback: (arg
|
|
47
|
+
readonly register: (callback: (arg: T) => void) => CallbackId;
|
|
16
48
|
|
|
17
49
|
/** Executes and removes a callback associated with the given ID. */
|
|
18
|
-
readonly execute:
|
|
50
|
+
readonly execute: T extends undefined
|
|
51
|
+
? (id: CallbackId) => undefined
|
|
52
|
+
: (id: CallbackId, arg: T) => undefined;
|
|
19
53
|
}
|
|
20
54
|
|
|
21
|
-
|
|
55
|
+
/** Unique identifier for a callback in {@link Callbacks}. */
|
|
56
|
+
export type CallbackId = Id & Brand<"Callback">;
|
|
22
57
|
|
|
23
|
-
|
|
24
|
-
|
|
58
|
+
/** Creates a new {@link Callbacks}. */
|
|
59
|
+
export const createCallbacks = <T = undefined>(
|
|
60
|
+
deps: RandomBytesDep,
|
|
61
|
+
): Callbacks<T> => {
|
|
62
|
+
const callbackMap = new Map<CallbackId, (arg: T) => void>();
|
|
25
63
|
|
|
26
|
-
|
|
64
|
+
return {
|
|
27
65
|
register: (callback) => {
|
|
28
|
-
const id = deps
|
|
66
|
+
const id = createId<"Callback">(deps);
|
|
29
67
|
callbackMap.set(id, callback);
|
|
30
68
|
return id;
|
|
31
69
|
},
|
|
32
70
|
|
|
33
|
-
execute: (id,
|
|
71
|
+
execute: (id: CallbackId, ...args: T extends undefined ? [] : [T]) => {
|
|
34
72
|
const callback = callbackMap.get(id);
|
|
35
|
-
if (callback)
|
|
36
|
-
|
|
37
|
-
|
|
73
|
+
if (!callback) return;
|
|
74
|
+
callbackMap.delete(id);
|
|
75
|
+
if (args.length === 0) {
|
|
76
|
+
// Called without argument (undefined case)
|
|
77
|
+
(callback as () => void)();
|
|
78
|
+
} else {
|
|
79
|
+
callback(args[0]);
|
|
38
80
|
}
|
|
39
81
|
},
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return callbacks;
|
|
82
|
+
} as Callbacks<T>;
|
|
43
83
|
};
|
package/src/Console.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Cross-platform console
|
|
3
3
|
*
|
|
4
4
|
* Console abstraction for Chrome 123+, Firefox 125+, Safari 18.1+, Node.js
|
|
5
5
|
* 22.x+, and React Native 0.75+. Includes methods guaranteed to be available in
|
|
@@ -13,14 +13,28 @@
|
|
|
13
13
|
* ### Example
|
|
14
14
|
*
|
|
15
15
|
* ```ts
|
|
16
|
-
* deps.console.log("[
|
|
16
|
+
* deps.console.log("[evolu]", "createEvoluInstance", { name });
|
|
17
17
|
* ```
|
|
18
18
|
*
|
|
19
19
|
* **Tip**: In browser dev tools, you can filter logs by tag (e.g., `[db]`) to
|
|
20
|
-
* quickly find relevant messages.
|
|
20
|
+
* quickly find relevant messages. In Node.js, use `grep` to filter output:
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* ```bash
|
|
23
|
+
* node app.js | grep "\[relay\]" # Show only relay logs
|
|
24
|
+
* node app.js | grep -E "\[db\]|\[sql\]" # Show db and sql logs
|
|
25
|
+
* node app.js | grep -v "\[debug\]" # Hide debug logs
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Or add to package.json scripts:
|
|
29
|
+
*
|
|
30
|
+
* ```json
|
|
31
|
+
* {
|
|
32
|
+
* "scripts": {
|
|
33
|
+
* "dev:relay": "node app.js | grep \"\\[relay\\]\"",
|
|
34
|
+
* "dev:db": "node app.js | grep -E \"\\[db\\]|\\[sql\\]\""
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
24
38
|
*
|
|
25
39
|
* @module
|
|
26
40
|
*/
|
|
@@ -51,6 +65,9 @@ export interface Console {
|
|
|
51
65
|
/** Starts a timer with an optional label */
|
|
52
66
|
time: (label?: string) => void;
|
|
53
67
|
|
|
68
|
+
/** Logs the elapsed time for a timer without ending it */
|
|
69
|
+
timeLog: (label?: string, ...data: Array<any>) => void;
|
|
70
|
+
|
|
54
71
|
/** Ends a timer and logs the elapsed time */
|
|
55
72
|
timeEnd: (label?: string) => void;
|
|
56
73
|
|
|
@@ -69,9 +86,6 @@ export interface Console {
|
|
|
69
86
|
/** Writes a message if the value is falsy, otherwise does nothing */
|
|
70
87
|
assert: (value: any, message?: string, ...optionalParams: Array<any>) => void;
|
|
71
88
|
|
|
72
|
-
/** Logs the elapsed time for a timer without ending it */
|
|
73
|
-
timeLog: (label?: string, ...data: Array<any>) => void;
|
|
74
|
-
|
|
75
89
|
/** Prints a stack trace with an optional message */
|
|
76
90
|
trace: (message?: any, ...optionalParams: Array<any>) => void;
|
|
77
91
|
}
|
|
@@ -121,6 +135,10 @@ export const createConsole = (config: ConsoleConfig = {}): Console => {
|
|
|
121
135
|
// eslint-disable-next-line no-console
|
|
122
136
|
if (instance.enabled) console.time(label);
|
|
123
137
|
},
|
|
138
|
+
timeLog: (label, ...data) => {
|
|
139
|
+
// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-argument
|
|
140
|
+
if (instance.enabled) console.timeLog(label, ...data);
|
|
141
|
+
},
|
|
124
142
|
timeEnd: (label) => {
|
|
125
143
|
// eslint-disable-next-line no-console
|
|
126
144
|
if (instance.enabled) console.timeEnd(label);
|
|
@@ -145,10 +163,6 @@ export const createConsole = (config: ConsoleConfig = {}): Console => {
|
|
|
145
163
|
// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-argument
|
|
146
164
|
if (instance.enabled) console.assert(value, message, ...optionalParams);
|
|
147
165
|
},
|
|
148
|
-
timeLog: (label, ...data) => {
|
|
149
|
-
// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-argument
|
|
150
|
-
if (instance.enabled) console.timeLog(label, ...data);
|
|
151
|
-
},
|
|
152
166
|
trace: (message, ...optionalParams) => {
|
|
153
167
|
// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-argument
|
|
154
168
|
if (instance.enabled) console.trace(message, ...optionalParams);
|
|
@@ -157,3 +171,69 @@ export const createConsole = (config: ConsoleConfig = {}): Console => {
|
|
|
157
171
|
|
|
158
172
|
return instance;
|
|
159
173
|
};
|
|
174
|
+
|
|
175
|
+
export interface ConsoleWithTimeConfig extends ConsoleConfig {
|
|
176
|
+
/**
|
|
177
|
+
* Type of timestamp to prepend to log messages.
|
|
178
|
+
*
|
|
179
|
+
* - 'absolute': Shows actual time (e.g., "14:32:15.234")
|
|
180
|
+
* - 'relative': Shows time since console creation (e.g., "+1.234s")
|
|
181
|
+
*/
|
|
182
|
+
readonly timestampType: "absolute" | "relative";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Creates a console instance with timestamp prefixes. */
|
|
186
|
+
export const createConsoleWithTime = (
|
|
187
|
+
config: ConsoleWithTimeConfig = { timestampType: "relative" },
|
|
188
|
+
): Console => {
|
|
189
|
+
const console = createConsole(config);
|
|
190
|
+
const startTime = performance.now();
|
|
191
|
+
|
|
192
|
+
const getTimestamp = (): string => {
|
|
193
|
+
if (config.timestampType === "relative") {
|
|
194
|
+
const elapsed = (performance.now() - startTime) / 1000;
|
|
195
|
+
|
|
196
|
+
// Format for better readability at different time scales
|
|
197
|
+
if (elapsed < 60) {
|
|
198
|
+
// Under 1 minute: show seconds with millisecond precision
|
|
199
|
+
return `+${elapsed.toFixed(3)}s`;
|
|
200
|
+
} else if (elapsed < 3600) {
|
|
201
|
+
// 1 minute to 1 hour: show minutes and seconds with millisecond precision
|
|
202
|
+
const minutes = Math.floor(elapsed / 60);
|
|
203
|
+
const seconds = (elapsed % 60).toFixed(3);
|
|
204
|
+
return `+${minutes}m${seconds}s`;
|
|
205
|
+
} else {
|
|
206
|
+
// Over 1 hour: show hours, minutes, and seconds with millisecond precision
|
|
207
|
+
const hours = Math.floor(elapsed / 3600);
|
|
208
|
+
const minutes = Math.floor((elapsed % 3600) / 60);
|
|
209
|
+
const seconds = ((elapsed % 3600) % 60).toFixed(3);
|
|
210
|
+
return `+${hours}h${minutes}m${seconds}s`;
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
// Absolute time - format as HH:MM:SS.mmm
|
|
214
|
+
const now = new Date();
|
|
215
|
+
const hours = now.getHours().toString().padStart(2, "0");
|
|
216
|
+
const minutes = now.getMinutes().toString().padStart(2, "0");
|
|
217
|
+
const seconds = now.getSeconds().toString().padStart(2, "0");
|
|
218
|
+
const milliseconds = now.getMilliseconds().toString().padStart(3, "0");
|
|
219
|
+
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const withTimestamp =
|
|
224
|
+
(fn: (...args: Array<any>) => void) =>
|
|
225
|
+
(...args: Array<any>) => {
|
|
226
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
227
|
+
fn(`[${getTimestamp()}]`, ...args);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Override methods that should have timestamps
|
|
231
|
+
console.log = withTimestamp(console.log);
|
|
232
|
+
console.info = withTimestamp(console.info);
|
|
233
|
+
console.warn = withTimestamp(console.warn);
|
|
234
|
+
console.error = withTimestamp(console.error);
|
|
235
|
+
console.debug = withTimestamp(console.debug);
|
|
236
|
+
console.trace = withTimestamp(console.trace);
|
|
237
|
+
|
|
238
|
+
return console;
|
|
239
|
+
};
|
package/src/Crypto.ts
CHANGED
|
@@ -1,53 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Cryptographic utilities
|
|
3
|
+
*
|
|
4
|
+
* Type-safe cryptographic operations including random number generation, SLIP21
|
|
5
|
+
* key derivation, XChaCha20-Poly1305 symmetric encryption, PADMÉ padding, and
|
|
6
|
+
* timing-safe comparisons.
|
|
3
7
|
*
|
|
4
8
|
* @module
|
|
5
9
|
*/
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
10
|
+
import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
|
|
11
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
12
|
+
import { sha512 } from "@noble/hashes/sha2.js";
|
|
13
|
+
import { randomBytes, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
14
|
+
import { Result, trySync } from "./Result.js";
|
|
15
|
+
import { brand, length, NonNegativeInt, Uint8Array } from "./Type.js";
|
|
16
|
+
|
|
17
|
+
export interface RandomBytes {
|
|
18
|
+
/**
|
|
19
|
+
* Creates cryptographically secure random bytes with type-safe length
|
|
20
|
+
* branding.
|
|
21
|
+
*
|
|
22
|
+
* Uses the operating system's cryptographically secure random number
|
|
23
|
+
* generator (crypto.getRandomValues) to generate high-quality entropy
|
|
24
|
+
* suitable for cryptographic operations.
|
|
25
|
+
*
|
|
26
|
+
* ### Type Safety
|
|
27
|
+
*
|
|
28
|
+
* Returns specific branded types for common sizes:
|
|
29
|
+
*
|
|
30
|
+
* - `Random16` for 16-byte values (128 bits)
|
|
31
|
+
* - `Random32` for 32-byte values (256 bits)
|
|
32
|
+
* - `Random64` for 64-byte values (512 bits)
|
|
33
|
+
* - `Random` for any other size
|
|
34
|
+
*
|
|
35
|
+
* ### Example
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* const nonce = randomBytes.create(16); // Type: Random16
|
|
39
|
+
* const key = randomBytes.create(32); // Type: Random32
|
|
40
|
+
* const seed = randomBytes.create(64); // Type: Random64
|
|
41
|
+
* const custom = randomBytes.create(48); // Type: Random
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
create(bytesLength: 16): Entropy16;
|
|
45
|
+
create(bytesLength: 32): Entropy32;
|
|
46
|
+
create(bytesLength: 64): Entropy64;
|
|
47
|
+
create(bytesLength: number): Entropy;
|
|
32
48
|
}
|
|
33
49
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
export interface RandomBytesDep {
|
|
51
|
+
readonly randomBytes: RandomBytes;
|
|
52
|
+
}
|
|
37
53
|
|
|
38
|
-
|
|
54
|
+
const Entropy = brand("Entropy", Uint8Array);
|
|
55
|
+
type Entropy = typeof Entropy.Type;
|
|
39
56
|
|
|
40
|
-
export
|
|
41
|
-
|
|
42
|
-
}
|
|
57
|
+
export const Entropy16 = length(16)(Entropy);
|
|
58
|
+
export type Entropy16 = typeof Entropy16.Type;
|
|
43
59
|
|
|
44
|
-
export const
|
|
45
|
-
|
|
60
|
+
export const Entropy32 = length(32)(Entropy);
|
|
61
|
+
export type Entropy32 = typeof Entropy32.Type;
|
|
46
62
|
|
|
47
|
-
export
|
|
63
|
+
export const Entropy64 = length(64)(Entropy);
|
|
64
|
+
export type Entropy64 = typeof Entropy64.Type;
|
|
48
65
|
|
|
49
|
-
export const
|
|
50
|
-
|
|
66
|
+
export const createRandomBytes = (): RandomBytes => ({
|
|
67
|
+
create: randomBytes as RandomBytes["create"],
|
|
68
|
+
});
|
|
51
69
|
|
|
52
70
|
/**
|
|
53
71
|
* SLIP21.
|
|
@@ -55,54 +73,43 @@ export const mnemonicToMnemonicSeed = (mnemonic: Mnemonic): MnemonicSeed =>
|
|
|
55
73
|
* https://github.com/satoshilabs/slips/blob/master/slip-0021.md
|
|
56
74
|
*/
|
|
57
75
|
export const createSlip21 = (
|
|
58
|
-
seed:
|
|
59
|
-
path: ReadonlyArray<string>,
|
|
60
|
-
):
|
|
61
|
-
let
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
seed: Entropy16 | Entropy32 | Entropy64,
|
|
77
|
+
path: ReadonlyArray<string | number>,
|
|
78
|
+
): Entropy32 => {
|
|
79
|
+
let currentNode = hmac(
|
|
80
|
+
sha512,
|
|
81
|
+
utf8ToBytes("Symmetric key seed"),
|
|
82
|
+
seed,
|
|
83
|
+
) as Entropy64;
|
|
84
|
+
|
|
85
|
+
for (const element of path) {
|
|
86
|
+
const label = typeof element === "number" ? element.toString() : element;
|
|
87
|
+
currentNode = deriveSlip21Node(label, currentNode);
|
|
68
88
|
}
|
|
69
|
-
|
|
89
|
+
|
|
90
|
+
return currentNode.slice(32, 64) as Entropy32;
|
|
70
91
|
};
|
|
71
92
|
|
|
72
93
|
/**
|
|
73
|
-
*
|
|
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).
|
|
94
|
+
* Derives a single node in the SLIP-21 hierarchical key derivation.
|
|
81
95
|
*
|
|
82
|
-
*
|
|
96
|
+
* @see {@link createSlip21}
|
|
83
97
|
*/
|
|
84
|
-
export const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
):
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
id = (id + urlAlphabet[slip21[i] & 63]) as Id;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return id;
|
|
98
|
+
export const deriveSlip21Node = (
|
|
99
|
+
label: string,
|
|
100
|
+
parentNode: Entropy64,
|
|
101
|
+
): Entropy64 => {
|
|
102
|
+
const labelBytes = utf8ToBytes(label);
|
|
103
|
+
const message = new globalThis.Uint8Array(labelBytes.byteLength + 1);
|
|
104
|
+
message[0] = 0;
|
|
105
|
+
message.set(labelBytes, 1);
|
|
106
|
+
return hmac(sha512, parentNode.slice(0, 32), message) as Entropy64;
|
|
97
107
|
};
|
|
98
108
|
|
|
99
109
|
/** The encryption key for {@link SymmetricCrypto}. */
|
|
100
|
-
export const EncryptionKey = brand("EncryptionKey",
|
|
110
|
+
export const EncryptionKey = brand("EncryptionKey", Entropy32);
|
|
101
111
|
export type EncryptionKey = typeof EncryptionKey.Type;
|
|
102
112
|
|
|
103
|
-
export const createEncryptionKey = (seed: MnemonicSeed): EncryptionKey =>
|
|
104
|
-
createSlip21(seed, ["Evolu", "Encryption Key"]) as EncryptionKey;
|
|
105
|
-
|
|
106
113
|
/** Symmetric cryptography. */
|
|
107
114
|
export interface SymmetricCrypto {
|
|
108
115
|
readonly nonceLength: NonNegativeInt;
|
|
@@ -137,15 +144,15 @@ export interface SymmetricCryptoDecryptError {
|
|
|
137
144
|
* https://github.com/paulmillr/noble-ciphers?tab=readme-ov-file#which-cipher-should-i-pick
|
|
138
145
|
*/
|
|
139
146
|
export const createSymmetricCrypto = (
|
|
140
|
-
deps:
|
|
147
|
+
deps: RandomBytesDep,
|
|
141
148
|
): SymmetricCrypto => {
|
|
142
|
-
const nonceLength =
|
|
149
|
+
const nonceLength = NonNegativeInt.orThrow(24);
|
|
143
150
|
|
|
144
151
|
const symmetricCrypto: SymmetricCrypto = {
|
|
145
152
|
nonceLength,
|
|
146
153
|
|
|
147
154
|
encrypt: (plaintext, encryptionKey) => {
|
|
148
|
-
const nonce = deps.
|
|
155
|
+
const nonce = deps.randomBytes.create(nonceLength);
|
|
149
156
|
const ciphertext = xchacha20poly1305(encryptionKey, nonce).encrypt(
|
|
150
157
|
plaintext,
|
|
151
158
|
);
|
|
@@ -169,25 +176,26 @@ export const createSymmetricCrypto = (
|
|
|
169
176
|
* Returns the PADMÉ padded length for a given input length.
|
|
170
177
|
*
|
|
171
178
|
* PADMÉ limits information leakage about the length of the plain-text for a
|
|
172
|
-
* wide range of encrypted data sizes.
|
|
173
|
-
*
|
|
179
|
+
* wide range of encrypted data sizes.
|
|
180
|
+
*
|
|
181
|
+
* See the PURBs paper for details: https://bford.info/pub/sec/purb.pdf
|
|
174
182
|
*/
|
|
175
|
-
export const
|
|
176
|
-
|
|
183
|
+
export const createPadmePaddedLength = (
|
|
184
|
+
length: NonNegativeInt,
|
|
185
|
+
): NonNegativeInt => {
|
|
186
|
+
if (length <= 0) return NonNegativeInt.orThrow(0);
|
|
177
187
|
const e = 31 - Math.clz32(length >>> 0);
|
|
178
188
|
const s = 32 - Math.clz32(e >>> 0);
|
|
179
189
|
const z = Math.max(0, e - s);
|
|
180
190
|
const mask = (1 << z) - 1;
|
|
181
|
-
return ((length + mask) & ~mask)
|
|
191
|
+
return NonNegativeInt.orThrow((length + mask) & ~mask);
|
|
182
192
|
};
|
|
183
193
|
|
|
184
|
-
/**
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
export const padmePaddingLength = (length: NonNegativeInt): NonNegativeInt => {
|
|
190
|
-
return (padmePaddedLength(length) - length) as NonNegativeInt;
|
|
194
|
+
/** Creates a PADMÉ padding array of zeros for the given input length. */
|
|
195
|
+
export const createPadmePadding = (length: NonNegativeInt): Uint8Array => {
|
|
196
|
+
const paddedLength = createPadmePaddedLength(length);
|
|
197
|
+
const paddingLength = NonNegativeInt.orThrow(paddedLength - length);
|
|
198
|
+
return new globalThis.Uint8Array(paddingLength);
|
|
191
199
|
};
|
|
192
200
|
|
|
193
201
|
/**
|