@evolu/common 6.0.1-preview.32 → 6.0.1-preview.34
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 +2 -2
- package/dist/src/Array.d.ts +193 -41
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +155 -48
- package/dist/src/Crypto.d.ts +6 -8
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +9 -9
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +4 -4
- package/dist/src/Evolu/Evolu.d.ts +9 -4
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +11 -11
- package/dist/src/Evolu/Protocol.d.ts +24 -1
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +59 -30
- package/dist/src/Evolu/Query.d.ts +1 -1
- package/dist/src/Evolu/Query.d.ts.map +1 -1
- package/dist/src/Evolu/Query.js +1 -1
- package/dist/src/Evolu/Schema.d.ts +24 -20
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +36 -27
- package/dist/src/Evolu/Storage.d.ts +10 -2
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +18 -4
- package/dist/src/Evolu/Sync.d.ts +2 -1
- package/dist/src/Evolu/Sync.d.ts.map +1 -1
- package/dist/src/Evolu/Sync.js +45 -33
- package/dist/src/Evolu/Timestamp.d.ts +12 -17
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +5 -19
- 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/Type.d.ts +57 -11
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +58 -28
- package/package.json +2 -2
- package/src/Array.ts +213 -55
- package/src/Crypto.ts +11 -9
- package/src/Evolu/Db.ts +7 -7
- package/src/Evolu/Evolu.ts +21 -20
- package/src/Evolu/Protocol.ts +70 -35
- package/src/Evolu/Query.ts +2 -2
- package/src/Evolu/Schema.ts +44 -32
- package/src/Evolu/Storage.ts +40 -2
- package/src/Evolu/Sync.ts +54 -32
- package/src/Evolu/Timestamp.ts +7 -28
- package/src/Object.ts +13 -5
- package/src/Type.ts +58 -32
package/dist/src/Evolu/Schema.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as Kysely from "kysely";
|
|
2
|
+
import { assert } from "../Assert.js";
|
|
3
|
+
import { createEqArrayLike, eqString } from "../Eq.js";
|
|
2
4
|
import { mapObject, objectToEntries } from "../Object.js";
|
|
3
5
|
import { ok } from "../Result.js";
|
|
4
6
|
import { sql, SqliteBoolean, } from "../Sqlite.js";
|
|
@@ -19,23 +21,25 @@ export const evoluSchemaToDbSchema = (schema, indexesConfig) => {
|
|
|
19
21
|
return { tables, indexes };
|
|
20
22
|
};
|
|
21
23
|
/**
|
|
22
|
-
*
|
|
24
|
+
* System columns that are implicitly defined by Evolu.
|
|
23
25
|
*
|
|
24
|
-
* - `createdAt`: Set by Evolu
|
|
25
|
-
*
|
|
26
|
-
* - `
|
|
27
|
-
*
|
|
28
|
-
* preserve real update time.
|
|
29
|
-
* - `isDeleted`: Soft delete flag.
|
|
26
|
+
* - `createdAt`: Set by Evolu on row creation, derived from {@link Timestamp}.
|
|
27
|
+
* - `updatedAt`: Set by Evolu on every row change, derived from {@link Timestamp}.
|
|
28
|
+
* - `isDeleted`: Soft delete flag created by Evolu and used by the developer to
|
|
29
|
+
* mark rows as deleted.
|
|
30
30
|
*/
|
|
31
|
-
export const
|
|
31
|
+
export const SystemColumns = object({
|
|
32
32
|
createdAt: DateIso,
|
|
33
33
|
updatedAt: DateIso,
|
|
34
34
|
isDeleted: nullOr(SqliteBoolean),
|
|
35
|
+
// TODO: ownerId
|
|
35
36
|
});
|
|
37
|
+
export const systemColumns = ["createdAt", "updatedAt", "isDeleted"];
|
|
38
|
+
assert(createEqArrayLike(eqString)(objectToEntries(SystemColumns.props).map(([key]) => key), systemColumns), "SystemColumns keys must match systemColumnsKeys");
|
|
36
39
|
/**
|
|
37
40
|
* Type Factory to create insertable {@link Type}. It makes nullable Types
|
|
38
|
-
* optional, omits Id, and ensures the
|
|
41
|
+
* optional (so they are not required), omits Id, and ensures the
|
|
42
|
+
* {@link maxMutationSize}.
|
|
39
43
|
*
|
|
40
44
|
* ### Example
|
|
41
45
|
*
|
|
@@ -53,7 +57,7 @@ export const insertable = (props) => {
|
|
|
53
57
|
};
|
|
54
58
|
/**
|
|
55
59
|
* Type Factory to create updateable {@link Type}. It makes everything except for
|
|
56
|
-
* the `id` column
|
|
60
|
+
* the `id` column optional (so they are not required) and ensures the
|
|
57
61
|
* {@link maxMutationSize}.
|
|
58
62
|
*
|
|
59
63
|
* ### Example
|
|
@@ -76,9 +80,15 @@ export const updateable = (props) => {
|
|
|
76
80
|
return validMutationSize(object(updateableProps));
|
|
77
81
|
};
|
|
78
82
|
/**
|
|
79
|
-
* Type Factory to create upsertable Type. It makes nullable Types optional
|
|
80
|
-
*
|
|
81
|
-
*
|
|
83
|
+
* Type Factory to create an upsertable Type. It makes nullable Types optional
|
|
84
|
+
* (so they are not required) and ensures the {@link maxMutationSize}.
|
|
85
|
+
*
|
|
86
|
+
* Upsert is like insert, except it requires an ID. It's useful for inserting
|
|
87
|
+
* rows with external ID via {@link createIdFromString}.
|
|
88
|
+
*
|
|
89
|
+
* Note that it's not possible to upsert a row with `createdAt` nor `updatedAt`,
|
|
90
|
+
* because they are derived from {@link CrdtMessage} timestamp. For external
|
|
91
|
+
* createdAt, use a different column.
|
|
82
92
|
*
|
|
83
93
|
* ### Example
|
|
84
94
|
*
|
|
@@ -88,7 +98,6 @@ export const updateable = (props) => {
|
|
|
88
98
|
* const todo = UpsertableTodo.from({
|
|
89
99
|
* id,
|
|
90
100
|
* title,
|
|
91
|
-
* createdAt: "2023-01-01T00:00:00.000Z",
|
|
92
101
|
* });
|
|
93
102
|
* if (!todo.ok) return; // handle errors
|
|
94
103
|
* ```
|
|
@@ -96,7 +105,6 @@ export const updateable = (props) => {
|
|
|
96
105
|
export const upsertable = (props) => {
|
|
97
106
|
const propsWithDefaults = {
|
|
98
107
|
...props,
|
|
99
|
-
createdAt: optional(DateIso),
|
|
100
108
|
isDeleted: optional(SqliteBoolean),
|
|
101
109
|
};
|
|
102
110
|
return validMutationSize(nullableToOptional(propsWithDefaults));
|
|
@@ -166,7 +174,7 @@ export const ensureDbSchema = (deps) => (newSchema, currentSchema, options) => {
|
|
|
166
174
|
const currentTable = currentSchema.tables.find((t) => t.name === newTable.name);
|
|
167
175
|
if (!currentTable) {
|
|
168
176
|
queries.push({
|
|
169
|
-
sql:
|
|
177
|
+
sql: createAppTableWithDefaultColumns(newTable),
|
|
170
178
|
parameters: [],
|
|
171
179
|
});
|
|
172
180
|
}
|
|
@@ -202,20 +210,21 @@ export const ensureDbSchema = (deps) => (newSchema, currentSchema, options) => {
|
|
|
202
210
|
}
|
|
203
211
|
return ok();
|
|
204
212
|
};
|
|
205
|
-
const
|
|
206
|
-
|
|
213
|
+
const createAppTableWithDefaultColumns = (table) => {
|
|
214
|
+
return `
|
|
215
|
+
create table ${sql.identifier(table.name).sql} (
|
|
207
216
|
"id" text primary key,
|
|
208
|
-
${columns
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
.join(", ")}
|
|
217
|
+
${table.columns
|
|
218
|
+
.concat(systemColumns)
|
|
219
|
+
.filter((c) => c !== "id")
|
|
220
|
+
// "A column with affinity BLOB does not prefer one storage class over another
|
|
221
|
+
// and no attempt is made to coerce data from one storage class into another."
|
|
222
|
+
// https://www.sqlite.org/datatype3.html
|
|
223
|
+
.map((name) => `${sql.identifier(name).sql} blob`)
|
|
224
|
+
.join(", ")}
|
|
217
225
|
);
|
|
218
226
|
`;
|
|
227
|
+
};
|
|
219
228
|
// https://kysely.dev/docs/recipes/splitting-query-building-and-execution
|
|
220
229
|
export const kysely = new Kysely.Kysely({
|
|
221
230
|
dialect: {
|
|
@@ -4,7 +4,7 @@ import { RandomDep } from "../Random.js";
|
|
|
4
4
|
import { Result } from "../Result.js";
|
|
5
5
|
import { SqliteDep, SqliteError } from "../Sqlite.js";
|
|
6
6
|
import { MaybeAsync } from "../Task.js";
|
|
7
|
-
import { NonNegativeInt, PositiveInt } from "../Type.js";
|
|
7
|
+
import { NonNegativeInt, PositiveInt, TypeError } from "../Type.js";
|
|
8
8
|
import { BaseOwnerError, OwnerId, OwnerIdBytes, OwnerWriteKey } from "./Owner.js";
|
|
9
9
|
import { Timestamp, TimestampBytes } from "./Timestamp.js";
|
|
10
10
|
export interface StorageConfig {
|
|
@@ -163,6 +163,13 @@ export interface CrdtMessage {
|
|
|
163
163
|
readonly timestamp: Timestamp;
|
|
164
164
|
readonly change: DbChange;
|
|
165
165
|
}
|
|
166
|
+
export declare const DbChangeValues: import("../Type.js").RecordType<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError, import("../Type.js").UnionType<[import("../Type.js").Type<"Null", null, null, import("../Type.js").NullError, null, import("../Type.js").NullError>, import("../Type.js").Type<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError>, import("../Type.js").Type<"Number", number, number, import("../Type.js").NumberError, number, import("../Type.js").NumberError>, import("../Type.js").Type<"Uint8Array", Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError>]>>;
|
|
167
|
+
export type DbChangeValues = typeof DbChangeValues.Type;
|
|
168
|
+
export declare const ValidDbChangeValues: import("../Type.js").BrandType<import("../Type.js").RecordType<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError, import("../Type.js").UnionType<[import("../Type.js").Type<"Null", null, null, import("../Type.js").NullError, null, import("../Type.js").NullError>, import("../Type.js").Type<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError>, import("../Type.js").Type<"Number", number, number, import("../Type.js").NumberError, number, import("../Type.js").NumberError>, import("../Type.js").Type<"Uint8Array", Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError>]>>, "ValidDbChangeValues", ValidDbChangeValuesError, import("../Type.js").RecordError<import("../Type.js").StringError, never> | import("../Type.js").RecordError<import("../Type.js").StringError, import("../Type.js").UnionError<import("../Type.js").Uint8ArrayError | import("../Type.js").NumberError | import("../Type.js").StringError | import("../Type.js").NullError>>>;
|
|
169
|
+
export type ValidDbChangeValues = typeof ValidDbChangeValues.Type;
|
|
170
|
+
export interface ValidDbChangeValuesError extends TypeError<"ValidDbChangeValues"> {
|
|
171
|
+
readonly invalidColumns: ReadonlyArray<string>;
|
|
172
|
+
}
|
|
166
173
|
/**
|
|
167
174
|
* A DbChange is a change to a table row. Together with a unique
|
|
168
175
|
* {@link Timestamp}, it forms a {@link CrdtMessage}.
|
|
@@ -170,7 +177,8 @@ export interface CrdtMessage {
|
|
|
170
177
|
export declare const DbChange: import("../Type.js").ObjectType<{
|
|
171
178
|
table: import("../Type.js").Type<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError>;
|
|
172
179
|
id: import("../Type.js").BrandType<import("../Type.js").Type<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError>, "Id", import("../Type.js").IdError, import("../Type.js").StringError>;
|
|
173
|
-
values: import("../Type.js").RecordType<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError, import("../Type.js").UnionType<[import("../Type.js").Type<"Null", null, null, import("../Type.js").NullError, null, import("../Type.js").NullError>, import("../Type.js").Type<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError>, import("../Type.js").Type<"Number", number, number, import("../Type.js").NumberError, number, import("../Type.js").NumberError>, import("../Type.js").Type<"Uint8Array", Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError>]
|
|
180
|
+
values: import("../Type.js").BrandType<import("../Type.js").RecordType<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError, import("../Type.js").UnionType<[import("../Type.js").Type<"Null", null, null, import("../Type.js").NullError, null, import("../Type.js").NullError>, import("../Type.js").Type<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError>, import("../Type.js").Type<"Number", number, number, import("../Type.js").NumberError, number, import("../Type.js").NumberError>, import("../Type.js").Type<"Uint8Array", Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError>]>>, "ValidDbChangeValues", ValidDbChangeValuesError, import("../Type.js").RecordError<import("../Type.js").StringError, never> | import("../Type.js").RecordError<import("../Type.js").StringError, import("../Type.js").UnionError<import("../Type.js").Uint8ArrayError | import("../Type.js").NumberError | import("../Type.js").StringError | import("../Type.js").NullError>>>;
|
|
181
|
+
isInsert: import("../Type.js").Type<"Boolean", boolean, boolean, import("../Type.js").BooleanError, boolean, import("../Type.js").BooleanError>;
|
|
174
182
|
}>;
|
|
175
183
|
export type DbChange = typeof DbChange.Type;
|
|
176
184
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Storage.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Storage.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,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,EAEd,WAAW,EAGX,SAAS,EACV,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,cAAc,EAEd,OAAO,EACP,YAAY,EACZ,aAAa,EACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAuB,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGhF,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,QAAQ,CAAC,kBAAkB,EAAE,CAC3B,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,WAAW,KACvB,UAAU,CAAC,OAAO,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,cAAc,GAAG,IAAI,CAAC;IAEnE,QAAQ,CAAC,WAAW,EAAE,CACpB,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,cAAc,KAChB,WAAW,GAAG,IAAI,CAAC;IAExB;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,EAAE,CAC1B,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,aAAa,CAAC,cAAc,CAAC,EACtC,UAAU,CAAC,EAAE,eAAe,KACzB,aAAa,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC;IAE5C,QAAQ,CAAC,cAAc,EAAE,CACvB,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,cAAc,EACnB,UAAU,EAAE,eAAe,KACxB,cAAc,GAAG,IAAI,CAAC;IAE3B,QAAQ,CAAC,OAAO,EAAE,CAChB,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,CAAC,SAAS,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,KAAK,OAAO,KACpE,IAAI,CAAC;IAEV;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,CACzB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,aAAa,KACpB,OAAO,CAAC;IAEb,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,CACpB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,aAAa,KACpB,OAAO,CAAC;IAEb;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,EAAE,CACtB,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,qBAAqB,CAAC,oBAAoB,CAAC,KAClD,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,CAAC,CAAC,CAAC;IAErE,qDAAqD;IACrD,QAAQ,CAAC,YAAY,EAAE,CACrB,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,cAAc,KACtB,iBAAiB,GAAG,IAAI,CAAC;IAE9B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC;CAC1D;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,gDAAgD;AAChD,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACvD,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;CACpC;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACvD,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;CACpC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC;AAE5D,eAAO,MAAM,eAAe,8CAA6B,CAAC;AAE1D,uCAAuC;AACvC,eAAO,MAAM,eAAe,EAAsC,WAAW,CAAC;AAE9E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;CACtC;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,kBAAkB,CAAC;AAElE,eAAO,MAAM,kBAAkB,eAA+B,CAAC;AAC/D,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAC;AAE3D,eAAO,MAAM,SAAS;;;;CAIZ,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEnE,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,QAAQ,CAAC,IAAI,EAAE,OAAO,SAAS,CAAC,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,gBAAiB,SAAQ,SAAS;IACjD,QAAQ,CAAC,IAAI,EAAE,OAAO,SAAS,CAAC,WAAW,CAAC;IAC5C,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,QAAQ,CAAC,IAAI,EAAE,OAAO,SAAS,CAAC,UAAU,CAAC;IAC3C,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;CACpD;AAED,MAAM,MAAM,KAAK,GAAG,SAAS,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAEnE,wCAAwC;AACxC,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;CACpC;AAED,yBAAyB;AACzB,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,KAAK,CAAC,mBAAmB,CAAC,CAAC;AAExE;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;CAC3B;AAED,eAAO,MAAM,cAAc,4uBAA8B,CAAC;AAC1D,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;AAOxD,eAAO,MAAM,mBAAmB,2nCAgB/B,CAAC;AACF,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAC;AAElE,MAAM,WAAW,wBACf,SAAQ,SAAS,CAAC,qBAAqB,CAAC;IACxC,QAAQ,CAAC,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChD;AAED;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;EAKnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,WAAW,iBACf,SAAQ,IAAI,CACV,OAAO,EACL,SAAS,GACT,aAAa,GACb,mBAAmB,GACnB,gBAAgB,GAChB,SAAS,GACT,aAAa,CAChB;IACD,wEAAwE;IACxE,QAAQ,CAAC,eAAe,EAAE,CACxB,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,8BAA8B,KACrC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAE/B;;;OAGG;IACH,QAAQ,CAAC,qBAAqB,EAAE,CAC9B,YAAY,EAAE,YAAY,EAC1B,eAAe,EAAE,qBAAqB,CAAC,cAAc,CAAC,KACnD,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;CACrC;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtD,MAAM,WAAW,6BAA8B,SAAQ,aAAa;IAClE,cAAc,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CAC9C;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,GACjC,MAAM,iBAAiB,MACvB,QAAQ,6BAA6B,KAAG,iBAyIxC,CAAC;AAMJ,eAAO,MAAM,6BAA6B,GACxC,MAAM,SAAS,KACd,MAAM,CAAC,IAAI,EAAE,WAAW,CAkE1B,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7E;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,GACrC,WAAW,cAAc,EACzB,gBAAgB,cAAc,EAC9B,eAAe,cAAc,KAC5B,CACD,QAAQ,EAAE,8BAA8B,EACxC,cAAc,EAAE,cAAc,EAC9B,aAAa,EAAE,cAAc,CAS9B,CAAC;AA+hBF,eAAO,MAAM,2BAA2B,GACtC,WAAW,cAAc,KACxB,WAGF,CAAC;AAuYF,eAAO,MAAM,mBAAmB,GAC7B,MAAM,SAAS,MAEd,SAAS,YAAY,EACrB,OAAO,cAAc,KACpB,MAAM,CAAC,cAAc,EAAE,WAAW,CA6EpC,CAAC;AAEJ,2EAA2E;AAC3E,eAAO,MAAM,aAAa,GACvB,MAAM,SAAS,MAEd,cAAc,YAAY,EAC1B,kBAAkB,cAAc,KAC/B,MAAM,CACP;IACE,WAAW,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,cAAc,EAAE,cAAc,CAAC;IAC/B,aAAa,EAAE,cAAc,CAAC;CAC/B,EACD,WAAW,CA8BZ,CAAC;AAEJ;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC1B,MAAM,SAAS,MAEd,cAAc,YAAY,EAC1B,aAAa,WAAW,EACxB,gBAAgB,cAAc,EAC9B,eAAe,cAAc,KAC5B,MAAM,CAAC,IAAI,EAAE,WAAW,CAc1B,CAAC"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
2
|
-
import { firstInArray, isNonEmptyReadonlyArray, } from "../Array.js";
|
|
2
|
+
import { filterArray, firstInArray, isNonEmptyReadonlyArray, } from "../Array.js";
|
|
3
3
|
import { assert } from "../Assert.js";
|
|
4
4
|
import { concatBytes } from "../Buffer.js";
|
|
5
5
|
import { decrement } from "../Number.js";
|
|
6
|
-
import { ok } from "../Result.js";
|
|
6
|
+
import { err, ok } from "../Result.js";
|
|
7
7
|
import { sql, SqliteValue } from "../Sqlite.js";
|
|
8
|
-
import { Id, NonNegativeInt, object, PositiveInt, record, String, } from "../Type.js";
|
|
8
|
+
import { Boolean, brand, Id, NonNegativeInt, object, PositiveInt, record, String, } from "../Type.js";
|
|
9
9
|
import { orderTimestampBytes } from "./Timestamp.js";
|
|
10
|
+
import { systemColumns } from "./Schema.js";
|
|
10
11
|
export const fingerprintSize = NonNegativeInt.orThrow(12);
|
|
11
12
|
/** A fingerprint of an empty range. */
|
|
12
13
|
export const zeroFingerprint = new Uint8Array(fingerprintSize);
|
|
@@ -16,6 +17,18 @@ export const RangeType = {
|
|
|
16
17
|
Skip: 0,
|
|
17
18
|
Timestamps: 2,
|
|
18
19
|
};
|
|
20
|
+
export const DbChangeValues = record(String, SqliteValue);
|
|
21
|
+
const systemColumnsWithIdAndWithoutIsDeleted = filterArray(systemColumns.concat("id"), (column) => column != "isDeleted");
|
|
22
|
+
export const ValidDbChangeValues = brand("ValidDbChangeValues", DbChangeValues, (value) => {
|
|
23
|
+
const invalidColumns = systemColumnsWithIdAndWithoutIsDeleted.filter((key) => key in value);
|
|
24
|
+
if (invalidColumns.length > 0)
|
|
25
|
+
return err({
|
|
26
|
+
type: "ValidDbChangeValues",
|
|
27
|
+
value,
|
|
28
|
+
invalidColumns,
|
|
29
|
+
});
|
|
30
|
+
return ok(value);
|
|
31
|
+
});
|
|
19
32
|
/**
|
|
20
33
|
* A DbChange is a change to a table row. Together with a unique
|
|
21
34
|
* {@link Timestamp}, it forms a {@link CrdtMessage}.
|
|
@@ -23,7 +36,8 @@ export const RangeType = {
|
|
|
23
36
|
export const DbChange = object({
|
|
24
37
|
table: String,
|
|
25
38
|
id: Id,
|
|
26
|
-
values:
|
|
39
|
+
values: ValidDbChangeValues,
|
|
40
|
+
isInsert: Boolean,
|
|
27
41
|
});
|
|
28
42
|
/**
|
|
29
43
|
* Creates a {@link BaseSqliteStorage} implementation.
|
package/dist/src/Evolu/Sync.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { RandomDep } from "../Random.js";
|
|
|
6
6
|
import { Result } from "../Result.js";
|
|
7
7
|
import { SqliteDep, SqliteError } from "../Sqlite.js";
|
|
8
8
|
import { TimeDep } from "../Time.js";
|
|
9
|
+
import { DateIso } from "../Type.js";
|
|
9
10
|
import { CreateWebSocketDep } from "../WebSocket.js";
|
|
10
11
|
import type { PostMessageDep } from "./Db.js";
|
|
11
12
|
import { AppOwner, OwnerEncryptionKey, OwnerId, OwnerIdBytes, OwnerTransport, OwnerWriteKey } from "./Owner.js";
|
|
@@ -68,7 +69,7 @@ export interface ClientStorageDep {
|
|
|
68
69
|
readonly storage: ClientStorage;
|
|
69
70
|
}
|
|
70
71
|
export declare const applyLocalOnlyChange: (deps: SqliteDep & TimeDep) => (change: MutationChange) => Result<void, SqliteError>;
|
|
71
|
-
export declare const applyMessageToTimestampAndHistoryTables: (deps: ClientStorageDep & SqliteDep) => (ownerId: OwnerIdBytes, message: CrdtMessage, strategy: StorageInsertTimestampStrategy) => Result<void, SqliteError>;
|
|
72
|
+
export declare const applyMessageToTimestampAndHistoryTables: (deps: ClientStorageDep & SqliteDep) => (ownerId: OwnerIdBytes, message: CrdtMessage, strategy: StorageInsertTimestampStrategy, date: DateIso) => Result<void, SqliteError>;
|
|
72
73
|
/**
|
|
73
74
|
* TODO: Rework for the new owners API.
|
|
74
75
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sync.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,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,EAAO,SAAS,EAAE,WAAW,EAAe,MAAM,cAAc,CAAC;AAExE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"Sync.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,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,EAAO,SAAS,EAAE,WAAW,EAAe,MAAM,cAAc,CAAC;AAExE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EACL,OAAO,EAKR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAa,MAAM,iBAAiB,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EACL,QAAQ,EACR,kBAAkB,EAClB,OAAO,EACP,YAAY,EAGZ,cAAc,EACd,aAAa,EAId,MAAM,YAAY,CAAC;AACpB,OAAO,EAOL,aAAa,EACb,wBAAwB,EACxB,8BAA8B,EAE/B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,iBAAiB,EACjB,WAAW,EAKX,OAAO,EACP,8BAA8B,EAG/B,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,MAAM,EAGN,SAAS,EAET,kBAAkB,EAClB,6BAA6B,EAC7B,mBAAmB,EACnB,4BAA4B,EAG7B,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,IAAK,SAAQ,UAAU;IACtC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAE5D,QAAQ,CAAC,YAAY,EAAE,CACrB,OAAO,EAAE,qBAAqB,CAAC,cAAc,CAAC,KAC3C,MAAM,CACT,IAAI,EACF,WAAW,GACX,6BAA6B,GAC7B,mBAAmB,GACnB,4BAA4B,CAC/B,CAAC;CACH;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,aAAa,EAAE,kBAAkB,CAAC;IAC3C,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAE5B,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IAEnD;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAElC,QAAQ,CAAC,OAAO,EAAE,CAChB,KAAK,EACD,aAAa,GACb,wBAAwB,GACxB,8BAA8B,GAC9B,WAAW,GACX,2BAA2B,GAC3B,6BAA6B,GAC7B,mBAAmB,GACnB,4BAA4B,GAC5B,iBAAiB,KAClB,IAAI,CAAC;IAEV,QAAQ,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,eAAO,MAAM,UAAU,GAEnB,MAAM,QAAQ,GACZ,UAAU,GACV,kBAAkB,GAClB,cAAc,GACd,cAAc,GACd,SAAS,GACT,SAAS,GACT,kBAAkB,GAClB,OAAO,GACP,kBAAkB,MAErB,QAAQ,UAAU,KAAG,MAAM,CAAC,IAAI,EAAE,WAAW,CA2O7C,CAAC;AAEJ,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,SAAS,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;CACpE;AAED,eAAO,MAAM,WAAW,GACrB,MAAM,cAAc,GAAG,SAAS,MAChC,4BAA+C,KAAG,KAiBlD,CAAC;AAMJ,MAAM,WAAW,aAAc,SAAQ,OAAO,EAAE,iBAAiB;CAAG;AAEpE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;CACjC;AA+LD,eAAO,MAAM,oBAAoB,GAC9B,MAAM,SAAS,GAAG,OAAO,MACzB,QAAQ,cAAc,KAAG,MAAM,CAAC,IAAI,EAAE,WAAW,CA4BjD,CAAC;AAoGJ,eAAO,MAAM,uCAAuC,GACjD,MAAM,gBAAgB,GAAG,SAAS,MAEjC,SAAS,YAAY,EACrB,SAAS,WAAW,EACpB,UAAU,8BAA8B,EACxC,MAAM,OAAO,KACZ,MAAM,CAAC,IAAI,EAAE,WAAW,CA+B1B,CAAC;AAEJ;;;;;;;;;;GAUG;AACH,MAAM,MAAM,SAAS,GACjB,gBAAgB,GAChB,kBAAkB,GAClB,iBAAiB,GACjB,oBAAoB,CAAC;AAEzB;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;CACnC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,WAAW,GAAG,oBAAoB,CAAC;CACnE;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;CACvC;AAED,eAAO,MAAM,gBAAgB,EAAE,gBAA+C,CAAC"}
|
package/dist/src/Evolu/Sync.js
CHANGED
|
@@ -8,11 +8,11 @@ import { createResources } from "../Resources.js";
|
|
|
8
8
|
import { err, ok } from "../Result.js";
|
|
9
9
|
import { sql } from "../Sqlite.js";
|
|
10
10
|
import { createMutex } from "../Task.js";
|
|
11
|
-
import { idBytesToId, idToIdBytes } from "../Type.js";
|
|
11
|
+
import { 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 { createBaseSqliteStorage, getOwnerUsage, getTimestampInsertStrategy, updateOwnerUsage, } from "./Storage.js";
|
|
15
|
-
import { createInitialTimestamp, receiveTimestamp, sendTimestamp, timestampBytesToTimestamp,
|
|
14
|
+
import { createBaseSqliteStorage, DbChange, getOwnerUsage, getTimestampInsertStrategy, updateOwnerUsage, } from "./Storage.js";
|
|
15
|
+
import { createInitialTimestamp, receiveTimestamp, sendTimestamp, timestampBytesToTimestamp, timestampToDateIso, timestampToTimestampBytes, } from "./Timestamp.js";
|
|
16
16
|
export const createSync = (deps) => (config) => {
|
|
17
17
|
let isDisposed = false;
|
|
18
18
|
/** Returns owner data only if actively assigned to at least one transport. */
|
|
@@ -160,7 +160,10 @@ export const createSync = (deps) => (config) => {
|
|
|
160
160
|
return nextTimestamp;
|
|
161
161
|
clockTimestamp = nextTimestamp.value;
|
|
162
162
|
const { ownerId = config.appOwner.id, ...dbChange } = change;
|
|
163
|
-
const message = {
|
|
163
|
+
const message = {
|
|
164
|
+
timestamp: clockTimestamp,
|
|
165
|
+
change: dbChange,
|
|
166
|
+
};
|
|
164
167
|
const messages = ownerMessages.get(ownerId);
|
|
165
168
|
if (messages)
|
|
166
169
|
messages.push(message);
|
|
@@ -209,9 +212,9 @@ export const createClock = (deps) => (initialTimestamp = createInitialTimestamp(
|
|
|
209
212
|
get: () => currentTimestamp,
|
|
210
213
|
save: (timestamp) => {
|
|
211
214
|
currentTimestamp = timestamp;
|
|
212
|
-
const timestampString = timestampToTimestampString(timestamp);
|
|
213
215
|
const result = deps.sqlite.exec(sql.prepared `
|
|
214
|
-
update evolu_config
|
|
216
|
+
update evolu_config
|
|
217
|
+
set "clock" = ${timestampToTimestampBytes(timestamp)};
|
|
215
218
|
`);
|
|
216
219
|
if (!result.ok)
|
|
217
220
|
return result;
|
|
@@ -306,18 +309,25 @@ const createClientStorage = (deps) => (config) => {
|
|
|
306
309
|
assert(rows.length > 0, "Rows must not be empty");
|
|
307
310
|
const { table, id } = rows[0];
|
|
308
311
|
const values = {};
|
|
312
|
+
let isInsert = false;
|
|
309
313
|
for (const r of rows) {
|
|
310
314
|
assert(r.table === table, "All rows must have the same table");
|
|
311
315
|
assert(eqArrayNumber(r.id, id), "All rows must have the same Id");
|
|
312
|
-
|
|
316
|
+
if (r.column === "createdAt") {
|
|
317
|
+
isInsert = true;
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
values[r.column] = r.value;
|
|
321
|
+
}
|
|
313
322
|
}
|
|
314
323
|
const message = {
|
|
315
324
|
timestamp: timestampBytesToTimestamp(timestamp),
|
|
316
|
-
change: {
|
|
325
|
+
change: DbChange.orThrow({
|
|
317
326
|
table: rows[0].table,
|
|
318
327
|
id: idBytesToId(rows[0].id),
|
|
319
328
|
values,
|
|
320
|
-
|
|
329
|
+
isInsert,
|
|
330
|
+
}),
|
|
321
331
|
};
|
|
322
332
|
return encodeAndEncryptDbChange(deps)(message, owner.encryptionKey);
|
|
323
333
|
},
|
|
@@ -329,31 +339,26 @@ const createTransportKey = (transportConfig) => {
|
|
|
329
339
|
return `${transportConfig.type}:${transportConfig.url}`;
|
|
330
340
|
};
|
|
331
341
|
export const applyLocalOnlyChange = (deps) => (change) => {
|
|
332
|
-
const
|
|
333
|
-
table: change.table,
|
|
334
|
-
id: change.id,
|
|
335
|
-
values: change.values,
|
|
336
|
-
};
|
|
337
|
-
const isDeletion = "isDeleted" in dbChange.values && dbChange.values.isDeleted === 1;
|
|
342
|
+
const isDeletion = "isDeleted" in change.values && change.values.isDeleted === 1;
|
|
338
343
|
if (isDeletion) {
|
|
339
344
|
const result = deps.sqlite.exec(sql `
|
|
340
|
-
delete from ${sql.identifier(
|
|
341
|
-
where id = ${
|
|
345
|
+
delete from ${sql.identifier(change.table)}
|
|
346
|
+
where id = ${change.id};
|
|
342
347
|
`);
|
|
343
348
|
if (!result.ok)
|
|
344
349
|
return result;
|
|
345
350
|
}
|
|
346
351
|
else {
|
|
347
|
-
const
|
|
348
|
-
for (const [column, value] of objectToEntries(
|
|
352
|
+
const now = deps.time.nowIso();
|
|
353
|
+
for (const [column, value] of objectToEntries(change.values)) {
|
|
349
354
|
const result = deps.sqlite.exec(sql.prepared `
|
|
350
|
-
insert into ${sql.identifier(
|
|
355
|
+
insert into ${sql.identifier(change.table)}
|
|
351
356
|
("id", ${sql.identifier(column)}, createdAt, updatedAt)
|
|
352
|
-
values (${
|
|
357
|
+
values (${change.id}, ${value}, ${now}, ${now})
|
|
353
358
|
on conflict ("id") do update
|
|
354
359
|
set
|
|
355
360
|
${sql.identifier(column)} = ${value},
|
|
356
|
-
updatedAt = ${
|
|
361
|
+
updatedAt = ${now};
|
|
357
362
|
`);
|
|
358
363
|
if (!result.ok)
|
|
359
364
|
return result;
|
|
@@ -368,13 +373,14 @@ const applyMessages = (deps) => (ownerId, messages) => {
|
|
|
368
373
|
return usageResult;
|
|
369
374
|
let { firstTimestamp, lastTimestamp } = usageResult.value;
|
|
370
375
|
for (const message of messages) {
|
|
371
|
-
const
|
|
376
|
+
const date = timestampToDateIso(message.timestamp);
|
|
377
|
+
const result1 = applyMessageToAppTable(deps)(ownerIdBytes, message, date);
|
|
372
378
|
if (!result1.ok)
|
|
373
379
|
return result1;
|
|
374
380
|
const timestamp = timestampToTimestampBytes(message.timestamp);
|
|
375
381
|
let strategy;
|
|
376
382
|
[strategy, firstTimestamp, lastTimestamp] = getTimestampInsertStrategy(timestamp, firstTimestamp, lastTimestamp);
|
|
377
|
-
const result2 = applyMessageToTimestampAndHistoryTables(deps)(ownerIdBytes, message, strategy);
|
|
383
|
+
const result2 = applyMessageToTimestampAndHistoryTables(deps)(ownerIdBytes, message, strategy, date);
|
|
378
384
|
if (!result2.ok)
|
|
379
385
|
return result2;
|
|
380
386
|
}
|
|
@@ -389,10 +395,12 @@ const applyMessages = (deps) => (ownerId, messages) => {
|
|
|
389
395
|
return updateUsage;
|
|
390
396
|
return ok();
|
|
391
397
|
};
|
|
392
|
-
const applyMessageToAppTable = (deps) => (ownerId, message) => {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
398
|
+
const applyMessageToAppTable = (deps) => (ownerId, message, date) => {
|
|
399
|
+
let entries = objectToEntries(message.change.values);
|
|
400
|
+
if (message.change.isInsert) {
|
|
401
|
+
entries = [...entries, ["createdAt", date]];
|
|
402
|
+
}
|
|
403
|
+
for (const [column, value] of entries) {
|
|
396
404
|
const result = deps.sqlite.exec(sql.prepared `
|
|
397
405
|
with
|
|
398
406
|
existingTimestamp as (
|
|
@@ -403,17 +411,17 @@ const applyMessageToAppTable = (deps) => (ownerId, message) => {
|
|
|
403
411
|
and "table" = ${message.change.table}
|
|
404
412
|
and "id" = ${idToIdBytes(message.change.id)}
|
|
405
413
|
and "column" = ${column}
|
|
406
|
-
and "timestamp" >= ${timestamp}
|
|
414
|
+
and "timestamp" >= ${timestampToTimestampBytes(message.timestamp)}
|
|
407
415
|
limit 1
|
|
408
416
|
)
|
|
409
417
|
insert into ${sql.identifier(message.change.table)}
|
|
410
418
|
("id", ${sql.identifier(column)}, updatedAt)
|
|
411
|
-
select ${message.change.id}, ${value}, ${
|
|
419
|
+
select ${message.change.id}, ${value}, ${date}
|
|
412
420
|
where not exists (select 1 from existingTimestamp)
|
|
413
421
|
on conflict ("id") do update
|
|
414
422
|
set
|
|
415
423
|
${sql.identifier(column)} = ${value},
|
|
416
|
-
updatedAt = ${
|
|
424
|
+
updatedAt = ${date}
|
|
417
425
|
where not exists (select 1 from existingTimestamp);
|
|
418
426
|
`);
|
|
419
427
|
if (!result.ok)
|
|
@@ -421,13 +429,17 @@ const applyMessageToAppTable = (deps) => (ownerId, message) => {
|
|
|
421
429
|
}
|
|
422
430
|
return ok();
|
|
423
431
|
};
|
|
424
|
-
export const applyMessageToTimestampAndHistoryTables = (deps) => (ownerId, message, strategy) => {
|
|
432
|
+
export const applyMessageToTimestampAndHistoryTables = (deps) => (ownerId, message, strategy, date) => {
|
|
425
433
|
const timestamp = timestampToTimestampBytes(message.timestamp);
|
|
426
434
|
const id = idToIdBytes(message.change.id);
|
|
427
435
|
const result = deps.storage.insertTimestamp(ownerId, timestamp, strategy);
|
|
428
436
|
if (!result.ok)
|
|
429
437
|
return result;
|
|
430
|
-
|
|
438
|
+
let entries = objectToEntries(message.change.values);
|
|
439
|
+
if (message.change.isInsert) {
|
|
440
|
+
entries = [...entries, ["createdAt", date]];
|
|
441
|
+
}
|
|
442
|
+
for (const [column, value] of entries) {
|
|
431
443
|
const result = deps.sqlite.exec(sql.prepared `
|
|
432
444
|
insert into evolu_history
|
|
433
445
|
("ownerId", "table", "id", "column", "value", "timestamp")
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { Brand } from "../Brand.js";
|
|
2
1
|
import { RandomBytesDep } from "../Crypto.js";
|
|
3
2
|
import { Order } from "../Order.js";
|
|
4
3
|
import { Result } from "../Result.js";
|
|
5
4
|
import { TimeDep } from "../Time.js";
|
|
6
|
-
import { InferType } from "../Type.js";
|
|
5
|
+
import { DateIso, InferType } from "../Type.js";
|
|
7
6
|
export interface TimestampConfig {
|
|
8
7
|
/**
|
|
9
8
|
* Maximum physical clock drift allowed in ms.
|
|
@@ -39,11 +38,11 @@ export interface TimestampTimeOutOfRangeError {
|
|
|
39
38
|
*
|
|
40
39
|
* `new Date(281474976710654).toString()` = Tue Aug 02 10889 07:31:49
|
|
41
40
|
*/
|
|
42
|
-
export declare const Millis: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, `LessThanOrEqualTo${number}`, import("../Type.js").LessThanOrEqualToError<number>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Millis", import("../Type.js").BrandWithoutRefineError<"Millis", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<number>>, never>;
|
|
41
|
+
export declare const Millis: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & import("../Brand.js").Brand<"Int"> & import("../Brand.js").Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & import("../Brand.js").Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, `LessThanOrEqualTo${number}`, import("../Type.js").LessThanOrEqualToError<number>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Millis", import("../Type.js").BrandWithoutRefineError<"Millis", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<number>>, never>;
|
|
43
42
|
export type Millis = typeof Millis.Type;
|
|
44
43
|
export declare const minMillis: Millis;
|
|
45
44
|
export declare const maxMillis: Millis;
|
|
46
|
-
export declare const Counter: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, "LessThanOrEqualTo65535", import("../Type.js").LessThanOrEqualToError<65535>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Counter", import("../Type.js").BrandWithoutRefineError<"Counter", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<65535>>, never>;
|
|
45
|
+
export declare const Counter: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & import("../Brand.js").Brand<"Int"> & import("../Brand.js").Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & import("../Brand.js").Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, "LessThanOrEqualTo65535", import("../Type.js").LessThanOrEqualToError<65535>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Counter", import("../Type.js").BrandWithoutRefineError<"Counter", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<65535>>, never>;
|
|
47
46
|
export type Counter = typeof Counter.Type;
|
|
48
47
|
export declare const minCounter: Counter;
|
|
49
48
|
export declare const maxCounter: Counter;
|
|
@@ -128,37 +127,33 @@ export declare const maxNodeId: NodeId;
|
|
|
128
127
|
* queue:
|
|
129
128
|
*
|
|
130
129
|
* 1. Write changes immediately to a local-only table
|
|
131
|
-
* 2. Periodically
|
|
132
|
-
* 3. This decouples user activity from sync timing
|
|
130
|
+
* 2. Periodically and randomly flush messages to sync tables
|
|
133
131
|
*
|
|
134
|
-
*
|
|
132
|
+
* **Trade-off:** It breaks real-time collaboration.
|
|
135
133
|
*/
|
|
136
134
|
export declare const Timestamp: import("../Type.js").ObjectType<{
|
|
137
|
-
millis: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, `LessThanOrEqualTo${number}`, import("../Type.js").LessThanOrEqualToError<number>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Millis", import("../Type.js").BrandWithoutRefineError<"Millis", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<number>>, never>;
|
|
138
|
-
counter: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & Brand<"Int"> & Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, "LessThanOrEqualTo65535", import("../Type.js").LessThanOrEqualToError<65535>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Counter", import("../Type.js").BrandWithoutRefineError<"Counter", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<65535>>, never>;
|
|
135
|
+
millis: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & import("../Brand.js").Brand<"Int"> & import("../Brand.js").Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & import("../Brand.js").Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, `LessThanOrEqualTo${number}`, import("../Type.js").LessThanOrEqualToError<number>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Millis", import("../Type.js").BrandWithoutRefineError<"Millis", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<number>>, never>;
|
|
136
|
+
counter: import("../Type.js").BrandType<import("../Type.js").BrandType<import("../Type.js").Type<"Brand", number & import("../Brand.js").Brand<"Int"> & import("../Brand.js").Brand<"NonNegative">, number, import("../Type.js").NonNegativeError, number & import("../Brand.js").Brand<"Int">, import("../Type.js").IntError | import("../Type.js").NumberError>, "LessThanOrEqualTo65535", import("../Type.js").LessThanOrEqualToError<65535>, import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError>, "Counter", import("../Type.js").BrandWithoutRefineError<"Counter", import("../Type.js").NonNegativeError | import("../Type.js").IntError | import("../Type.js").NumberError | import("../Type.js").LessThanOrEqualToError<65535>>, never>;
|
|
139
137
|
nodeId: import("../Type.js").BrandType<import("../Type.js").Type<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError>, "NodeId", import("../Type.js").RegexError<"NodeId">, import("../Type.js").StringError>;
|
|
140
138
|
}>;
|
|
141
139
|
export interface Timestamp extends InferType<typeof Timestamp> {
|
|
142
140
|
}
|
|
143
141
|
/** Equality function for comparing {@link Timestamp}. */
|
|
144
142
|
export declare const eqTimestamp: import("../Eq.js").Eq<{
|
|
145
|
-
readonly millis: number & Brand<"Int"> & Brand<"NonNegative"> & Brand<`LessThanOrEqualTo${number}`> & Brand<"Millis">;
|
|
146
|
-
readonly counter: number & Brand<"Int"> & Brand<"NonNegative"> & Brand<"LessThanOrEqualTo65535"> & Brand<"Counter">;
|
|
147
|
-
readonly nodeId: string & Brand<"NodeId">;
|
|
143
|
+
readonly millis: number & import("../Brand.js").Brand<"Int"> & import("../Brand.js").Brand<"NonNegative"> & import("../Brand.js").Brand<`LessThanOrEqualTo${number}`> & import("../Brand.js").Brand<"Millis">;
|
|
144
|
+
readonly counter: number & import("../Brand.js").Brand<"Int"> & import("../Brand.js").Brand<"NonNegative"> & import("../Brand.js").Brand<"LessThanOrEqualTo65535"> & import("../Brand.js").Brand<"Counter">;
|
|
145
|
+
readonly nodeId: string & import("../Brand.js").Brand<"NodeId">;
|
|
148
146
|
}>;
|
|
149
147
|
export declare const createTimestamp: ({ millis, counter, nodeId, }?: Partial<Timestamp>) => Timestamp;
|
|
150
148
|
export declare const createInitialTimestamp: (deps: RandomBytesDep) => Timestamp;
|
|
151
|
-
/** Sortable string representation of {@link Timestamp}. */
|
|
152
|
-
export type TimestampString = string & Brand<"TimestampString">;
|
|
153
|
-
export declare const timestampToTimestampString: (t: Timestamp) => TimestampString;
|
|
154
|
-
export declare const timestampStringToTimestamp: (timestampString: TimestampString) => Timestamp;
|
|
155
149
|
export declare const sendTimestamp: (deps: TimeDep & TimestampConfigDep) => (timestamp: Timestamp) => Result<Timestamp, TimestampDriftError | TimestampCounterOverflowError | TimestampTimeOutOfRangeError>;
|
|
156
150
|
export declare const receiveTimestamp: (deps: TimeDep & TimestampConfigDep) => (local: Timestamp, remote: Timestamp) => Result<Timestamp, TimestampDriftError | TimestampCounterOverflowError | TimestampTimeOutOfRangeError>;
|
|
157
151
|
/** Sortable bytes representation of {@link Timestamp}. */
|
|
158
152
|
export declare const TimestampBytes: import("../Type.js").BrandType<import("../Type.js").Type<"Uint8Array", Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError, Uint8Array<ArrayBufferLike>, import("../Type.js").Uint8ArrayError>, "TimestampBytes", import("../Type.js").BrandWithoutRefineError<"TimestampBytes", import("../Type.js").Uint8ArrayError>, never>;
|
|
159
153
|
export type TimestampBytes = typeof TimestampBytes.Type;
|
|
160
|
-
export declare const timestampBytesLength: number & Brand<"Int"> & Brand<"NonNegative">;
|
|
154
|
+
export declare const timestampBytesLength: number & import("../Brand.js").Brand<"Int"> & import("../Brand.js").Brand<"NonNegative">;
|
|
161
155
|
export declare const timestampToTimestampBytes: (timestamp: Timestamp) => TimestampBytes;
|
|
162
156
|
export declare const timestampBytesToTimestamp: (timestamp: TimestampBytes) => Timestamp;
|
|
163
157
|
export declare const orderTimestampBytes: Order<TimestampBytes>;
|
|
158
|
+
export declare const timestampToDateIso: (timestamp: Timestamp) => DateIso;
|
|
164
159
|
//# sourceMappingURL=Timestamp.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Timestamp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Timestamp.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,EAAE,KAAK,EAAmB,MAAM,aAAa,CAAC;AACrD,OAAO,EAAW,MAAM,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAEL,OAAO,EACP,SAAS,EAOV,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;CAC3C;AAED,MAAM,MAAM,cAAc,GACtB,mBAAmB,GACnB,6BAA6B,GAC7B,4BAA4B,CAAC;AAEjC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,IAAI,EAAE,+BAA+B,CAAC;CAChD;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,IAAI,EAAE,8BAA8B,CAAC;CAC/C;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM,kwBAGlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAC;AAExC,eAAO,MAAM,SAAS,EAAQ,MAAM,CAAC;AACrC,eAAO,MAAM,SAAS,EAA4B,MAAM,CAAC;AAEzD,eAAO,MAAM,OAAO,8vBAGnB,CAAC;AACF,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAC;AAE1C,eAAO,MAAM,UAAU,EAAQ,OAAO,CAAC;AACvC,eAAO,MAAM,UAAU,EAAY,OAAO,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,MAAM,wPAA4C,CAAC;AAChE,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAC;AAExC,eAAO,MAAM,SAAS,EAAyB,MAAM,CAAC;AACtD,eAAO,MAAM,SAAS,EAAyB,MAAM,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,eAAO,MAAM,SAAS;;;;EAIpB,CAAC;AACH,MAAM,WAAW,SAAU,SAAQ,SAAS,CAAC,OAAO,SAAS,CAAC;CAAG;AAEjE,yDAAyD;AACzD,eAAO,MAAM,WAAW;;;;EAItB,CAAC;AAEH,eAAO,MAAM,eAAe,GAAI,+BAI7B,OAAO,CAAC,SAAS,CAAM,KAAG,SAA0C,CAAC;AAExE,eAAO,MAAM,sBAAsB,GAAI,MAAM,cAAc,KAAG,SAG7D,CAAC;AA6BF,eAAO,MAAM,aAAa,GACvB,MAAM,OAAO,GAAG,kBAAkB,MAEjC,WAAW,SAAS,KACnB,MAAM,CACP,SAAS,EACP,mBAAmB,GACnB,6BAA6B,GAC7B,4BAA4B,CAgB/B,CAAC;AAEJ,eAAO,MAAM,gBAAgB,GAC1B,MAAM,OAAO,GAAG,kBAAkB,MAEjC,OAAO,SAAS,EAChB,QAAQ,SAAS,KAChB,MAAM,CACP,SAAS,EACP,mBAAmB,GACnB,6BAA6B,GAC7B,4BAA4B,CAqB/B,CAAC;AAEJ,0DAA0D;AAC1D,eAAO,MAAM,cAAc,2WAAsC,CAAC;AAClE,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;AAExD,eAAO,MAAM,oBAAoB,0FAA6B,CAAC;AAE/D,eAAO,MAAM,yBAAyB,GACpC,WAAW,SAAS,KACnB,cA0BF,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,WAAW,cAAc,KACxB,SAoBF,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,cAAc,CAAmB,CAAC;AAE1E,eAAO,MAAM,kBAAkB,GAAI,WAAW,SAAS,KAAG,OAEL,CAAC"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { assert } from "../Assert.js";
|
|
2
1
|
import { bytesToHex } from "../Buffer.js";
|
|
3
2
|
import { createEqObject, eqNumber, eqString } from "../Eq.js";
|
|
4
3
|
import { increment } from "../Number.js";
|
|
@@ -103,10 +102,9 @@ export const maxNodeId = "ffffffffffffffff";
|
|
|
103
102
|
* queue:
|
|
104
103
|
*
|
|
105
104
|
* 1. Write changes immediately to a local-only table
|
|
106
|
-
* 2. Periodically
|
|
107
|
-
* 3. This decouples user activity from sync timing
|
|
105
|
+
* 2. Periodically and randomly flush messages to sync tables
|
|
108
106
|
*
|
|
109
|
-
*
|
|
107
|
+
* **Trade-off:** It breaks real-time collaboration.
|
|
110
108
|
*/
|
|
111
109
|
export const Timestamp = object({
|
|
112
110
|
millis: Millis,
|
|
@@ -124,21 +122,6 @@ export const createInitialTimestamp = (deps) => {
|
|
|
124
122
|
const nodeId = bytesToHex(deps.randomBytes.create(8));
|
|
125
123
|
return createTimestamp({ nodeId });
|
|
126
124
|
};
|
|
127
|
-
export const timestampToTimestampString = (t) => [
|
|
128
|
-
new Date(t.millis).toISOString(),
|
|
129
|
-
t.counter.toString(16).toUpperCase().padStart(4, "0"),
|
|
130
|
-
t.nodeId,
|
|
131
|
-
].join("-");
|
|
132
|
-
export const timestampStringToTimestamp = (timestampString) => {
|
|
133
|
-
const array = timestampString.split("-");
|
|
134
|
-
const timestamp = {
|
|
135
|
-
millis: Date.parse(array.slice(0, 3).join("-")).valueOf(),
|
|
136
|
-
counter: parseInt(array[3], 16),
|
|
137
|
-
nodeId: array[4],
|
|
138
|
-
};
|
|
139
|
-
assert(Timestamp.is(timestamp), "timestampString is malformed");
|
|
140
|
-
return timestamp;
|
|
141
|
-
};
|
|
142
125
|
const getNextMillis = (deps) => (millis) => {
|
|
143
126
|
const now = Millis.from(deps.time.now());
|
|
144
127
|
if (!now.ok) {
|
|
@@ -236,3 +219,6 @@ export const timestampBytesToTimestamp = (timestamp) => {
|
|
|
236
219
|
return { millis: Number(millis), counter, nodeId };
|
|
237
220
|
};
|
|
238
221
|
export const orderTimestampBytes = orderUint8Array;
|
|
222
|
+
export const timestampToDateIso = (timestamp) =>
|
|
223
|
+
// `as DateIso` is safe because the timestamp is always valid
|
|
224
|
+
new Date(timestamp.millis).toISOString();
|