@evolu/common 1.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/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/src/Config.d.ts +20 -0
- package/dist/src/Config.d.ts.map +1 -0
- package/dist/src/Config.js +8 -0
- package/dist/src/Crypto.d.ts +42 -0
- package/dist/src/Crypto.d.ts.map +1 -0
- package/dist/src/Crypto.js +44 -0
- package/dist/src/Db.d.ts +60 -0
- package/dist/src/Db.d.ts.map +1 -0
- package/dist/src/Db.js +118 -0
- package/dist/src/DbWorker.d.ts +78 -0
- package/dist/src/DbWorker.d.ts.map +1 -0
- package/dist/src/DbWorker.js +282 -0
- package/dist/src/Diff.d.ts +19 -0
- package/dist/src/Diff.d.ts.map +1 -0
- package/dist/src/Diff.js +86 -0
- package/dist/src/Errors.d.ts +19 -0
- package/dist/src/Errors.d.ts.map +1 -0
- package/dist/src/Errors.js +11 -0
- package/dist/src/Evolu.d.ts +71 -0
- package/dist/src/Evolu.d.ts.map +1 -0
- package/dist/src/Evolu.js +235 -0
- package/dist/src/MerkleTree.d.ts +15 -0
- package/dist/src/MerkleTree.d.ts.map +1 -0
- package/dist/src/MerkleTree.js +49 -0
- package/dist/src/Model.d.ts +143 -0
- package/dist/src/Model.d.ts.map +1 -0
- package/dist/src/Model.js +103 -0
- package/dist/src/Murmurhash.d.ts +2 -0
- package/dist/src/Murmurhash.d.ts.map +1 -0
- package/dist/src/Murmurhash.js +60 -0
- package/dist/src/Platform.d.ts +29 -0
- package/dist/src/Platform.d.ts.map +1 -0
- package/dist/src/Platform.js +17 -0
- package/dist/src/Protobuf.d.ts +130 -0
- package/dist/src/Protobuf.d.ts.map +1 -0
- package/dist/src/Protobuf.js +100 -0
- package/dist/src/Public.d.ts +7 -0
- package/dist/src/Public.d.ts.map +1 -0
- package/dist/src/Public.js +2 -0
- package/dist/src/Sql.d.ts +12 -0
- package/dist/src/Sql.d.ts.map +1 -0
- package/dist/src/Sql.js +100 -0
- package/dist/src/Sqlite.d.ts +30 -0
- package/dist/src/Sqlite.d.ts.map +1 -0
- package/dist/src/Sqlite.js +41 -0
- package/dist/src/Store.d.ts +9 -0
- package/dist/src/Store.d.ts.map +1 -0
- package/dist/src/Store.js +18 -0
- package/dist/src/SyncWorker.d.ts +73 -0
- package/dist/src/SyncWorker.d.ts.map +1 -0
- package/dist/src/SyncWorker.js +136 -0
- package/dist/src/Timestamp.d.ts +44 -0
- package/dist/src/Timestamp.d.ts.map +1 -0
- package/dist/src/Timestamp.js +76 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +18 -0
- package/package.json +76 -0
- package/src/Config.ts +33 -0
- package/src/Crypto.ts +113 -0
- package/src/Db.ts +323 -0
- package/src/DbWorker.ts +683 -0
- package/src/Diff.ts +113 -0
- package/src/Errors.ts +33 -0
- package/src/Evolu.ts +527 -0
- package/src/MerkleTree.ts +83 -0
- package/src/Model.ts +212 -0
- package/src/Murmurhash.ts +70 -0
- package/src/Platform.ts +63 -0
- package/src/Protobuf.ts +204 -0
- package/src/Public.ts +6 -0
- package/src/Sql.ts +114 -0
- package/src/Sqlite.ts +92 -0
- package/src/Store.ts +32 -0
- package/src/SyncWorker.ts +344 -0
- package/src/Timestamp.ts +192 -0
- package/src/index.ts +18 -0
package/src/Timestamp.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import * as Schema from "@effect/schema/Schema";
|
|
2
|
+
import { Brand, Context, Effect, Either, Layer, Number, pipe } from "effect";
|
|
3
|
+
import { Config } from "./Config.js";
|
|
4
|
+
import { NanoId, NodeId } from "./Crypto.js";
|
|
5
|
+
import { murmurhash } from "./Murmurhash.js";
|
|
6
|
+
|
|
7
|
+
// https://muratbuffalo.blogspot.com/2014/07/hybrid-logical-clocks.html
|
|
8
|
+
// https://jaredforsyth.com/posts/hybrid-logical-clocks/
|
|
9
|
+
// https://github.com/clintharris/crdt-example-app_annotated/blob/master/shared/timestamp.js
|
|
10
|
+
export interface Timestamp {
|
|
11
|
+
readonly node: NodeId;
|
|
12
|
+
readonly millis: Millis;
|
|
13
|
+
readonly counter: Counter;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Millis = Schema.number.pipe(
|
|
17
|
+
Schema.greaterThanOrEqualTo(0),
|
|
18
|
+
Schema.brand("Millis"),
|
|
19
|
+
);
|
|
20
|
+
export type Millis = Schema.Schema.To<typeof Millis>;
|
|
21
|
+
|
|
22
|
+
const initialMillis = Schema.parseSync(Millis)(0);
|
|
23
|
+
|
|
24
|
+
export const Counter = Schema.number.pipe(
|
|
25
|
+
Schema.between(0, 65535),
|
|
26
|
+
Schema.brand("Counter"),
|
|
27
|
+
);
|
|
28
|
+
export type Counter = Schema.Schema.To<typeof Counter>;
|
|
29
|
+
|
|
30
|
+
const initialCounter = Schema.parseSync(Counter)(0);
|
|
31
|
+
|
|
32
|
+
export type TimestampHash = number & Brand.Brand<"TimestampHash">;
|
|
33
|
+
|
|
34
|
+
export type TimestampString = string & Brand.Brand<"TimestampString">;
|
|
35
|
+
|
|
36
|
+
export const timestampToString = (t: Timestamp): TimestampString =>
|
|
37
|
+
[
|
|
38
|
+
new Date(t.millis).toISOString(),
|
|
39
|
+
t.counter.toString(16).toUpperCase().padStart(4, "0"),
|
|
40
|
+
t.node,
|
|
41
|
+
].join("-") as TimestampString;
|
|
42
|
+
|
|
43
|
+
export const unsafeTimestampFromString = (s: TimestampString): Timestamp => {
|
|
44
|
+
const a = s.split("-");
|
|
45
|
+
return {
|
|
46
|
+
millis: Date.parse(a.slice(0, 3).join("-")).valueOf() as Millis,
|
|
47
|
+
counter: parseInt(a[3], 16) as Counter,
|
|
48
|
+
node: a[4] as NodeId,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const timestampToHash = (t: Timestamp): TimestampHash =>
|
|
53
|
+
murmurhash(timestampToString(t)) as TimestampHash;
|
|
54
|
+
|
|
55
|
+
const syncNodeId = Schema.parseSync(NodeId)("0000000000000000");
|
|
56
|
+
|
|
57
|
+
export const makeSyncTimestamp = (
|
|
58
|
+
millis: Millis = initialMillis,
|
|
59
|
+
): Timestamp => ({
|
|
60
|
+
millis,
|
|
61
|
+
counter: initialCounter,
|
|
62
|
+
node: syncNodeId,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export const makeInitialTimestamp = NanoId.pipe(
|
|
66
|
+
Effect.flatMap(({ nanoidAsNodeId }) => nanoidAsNodeId),
|
|
67
|
+
Effect.map(
|
|
68
|
+
(node): Timestamp => ({
|
|
69
|
+
millis: initialMillis,
|
|
70
|
+
counter: initialCounter,
|
|
71
|
+
node,
|
|
72
|
+
}),
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
export interface Time {
|
|
77
|
+
readonly now: Effect.Effect<never, never, Millis>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const Time = Context.Tag<Time>("evolu/Time");
|
|
81
|
+
|
|
82
|
+
export const TimeLive = Layer.succeed(
|
|
83
|
+
Time,
|
|
84
|
+
Time.of({
|
|
85
|
+
now: Effect.sync(() => Date.now() as Millis),
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
export type TimestampError =
|
|
90
|
+
| TimestampDriftError
|
|
91
|
+
| TimestampCounterOverflowError
|
|
92
|
+
| TimestampDuplicateNodeError;
|
|
93
|
+
|
|
94
|
+
export interface TimestampDriftError {
|
|
95
|
+
readonly _tag: "TimestampDriftError";
|
|
96
|
+
readonly next: Millis;
|
|
97
|
+
readonly now: Millis;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface TimestampCounterOverflowError {
|
|
101
|
+
readonly _tag: "TimestampCounterOverflowError";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const getNextMillis = (
|
|
105
|
+
millis: Millis[],
|
|
106
|
+
): Effect.Effect<Time | Config, TimestampDriftError, Millis> =>
|
|
107
|
+
Effect.gen(function* (_) {
|
|
108
|
+
const time = yield* _(Time);
|
|
109
|
+
const config = yield* _(Config);
|
|
110
|
+
|
|
111
|
+
const now = yield* _(time.now);
|
|
112
|
+
const next = Math.max(now, ...millis) as Millis;
|
|
113
|
+
|
|
114
|
+
if (next - now > config.maxDrift)
|
|
115
|
+
yield* _(
|
|
116
|
+
Effect.fail<TimestampDriftError>({
|
|
117
|
+
_tag: "TimestampDriftError",
|
|
118
|
+
now,
|
|
119
|
+
next,
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return next;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const incrementCounter = (
|
|
127
|
+
counter: Counter,
|
|
128
|
+
): Either.Either<TimestampCounterOverflowError, Counter> =>
|
|
129
|
+
pipe(
|
|
130
|
+
Number.increment(counter),
|
|
131
|
+
Schema.parseEither(Counter),
|
|
132
|
+
Either.mapLeft(() => ({ _tag: "TimestampCounterOverflowError" })),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const counterMin = Schema.parseSync(Counter)(0);
|
|
136
|
+
|
|
137
|
+
export const sendTimestamp = (
|
|
138
|
+
timestamp: Timestamp,
|
|
139
|
+
): Effect.Effect<
|
|
140
|
+
Time | Config,
|
|
141
|
+
TimestampDriftError | TimestampCounterOverflowError,
|
|
142
|
+
Timestamp
|
|
143
|
+
> =>
|
|
144
|
+
Effect.gen(function* (_) {
|
|
145
|
+
const millis = yield* _(getNextMillis([timestamp.millis]));
|
|
146
|
+
const counter =
|
|
147
|
+
millis === timestamp.millis
|
|
148
|
+
? yield* _(incrementCounter(timestamp.counter))
|
|
149
|
+
: counterMin;
|
|
150
|
+
return { ...timestamp, millis, counter };
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
export interface TimestampDuplicateNodeError {
|
|
154
|
+
readonly _tag: "TimestampDuplicateNodeError";
|
|
155
|
+
readonly node: NodeId;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const receiveTimestamp = ({
|
|
159
|
+
local,
|
|
160
|
+
remote,
|
|
161
|
+
}: {
|
|
162
|
+
readonly local: Timestamp;
|
|
163
|
+
readonly remote: Timestamp;
|
|
164
|
+
}): Effect.Effect<
|
|
165
|
+
Time | Config,
|
|
166
|
+
| TimestampDriftError
|
|
167
|
+
| TimestampCounterOverflowError
|
|
168
|
+
| TimestampDuplicateNodeError,
|
|
169
|
+
Timestamp
|
|
170
|
+
> =>
|
|
171
|
+
Effect.gen(function* (_) {
|
|
172
|
+
if (local.node === remote.node)
|
|
173
|
+
yield* _(
|
|
174
|
+
Effect.fail<TimestampDuplicateNodeError>({
|
|
175
|
+
_tag: "TimestampDuplicateNodeError",
|
|
176
|
+
node: local.node,
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const millis = yield* _(getNextMillis([local.millis, remote.millis]));
|
|
181
|
+
const counter = yield* _(
|
|
182
|
+
millis === local.millis && millis === remote.millis
|
|
183
|
+
? incrementCounter(Math.max(local.counter, remote.counter) as Counter)
|
|
184
|
+
: millis === local.millis
|
|
185
|
+
? incrementCounter(local.counter)
|
|
186
|
+
: millis === remote.millis
|
|
187
|
+
? incrementCounter(remote.counter)
|
|
188
|
+
: Either.right(counterMin),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
return { ...local, millis, counter };
|
|
192
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
|
|
2
|
+
export * from "./Config.js";
|
|
3
|
+
export * from "./Crypto.js";
|
|
4
|
+
export * from "./Db.js";
|
|
5
|
+
export * from "./DbWorker.js";
|
|
6
|
+
export * from "./Diff.js";
|
|
7
|
+
export * from "./Errors.js";
|
|
8
|
+
export * from "./Evolu.js";
|
|
9
|
+
export * from "./MerkleTree.js";
|
|
10
|
+
export * from "./Model.js";
|
|
11
|
+
export * from "./Murmurhash.js";
|
|
12
|
+
export * from "./Platform.js";
|
|
13
|
+
export * from "./Protobuf.js";
|
|
14
|
+
export * from "./Sql.js";
|
|
15
|
+
export * from "./Sqlite.js";
|
|
16
|
+
export * from "./Store.js";
|
|
17
|
+
export * from "./SyncWorker.js";
|
|
18
|
+
export * from "./Timestamp.js";
|