core-3nweb-client-lib 0.49.6 → 0.49.8
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/build/api-defs/asmail.d.ts +1 -0
- package/build/api-defs/files.d.ts +11 -5
- package/build/core/asmail/delivery/common.d.ts +8 -28
- package/build/core/asmail/delivery/common.js +1 -47
- package/build/core/asmail/delivery/index.js +3 -3
- package/build/core/asmail/delivery/per-recipient-wip.js +3 -2
- package/build/core/asmail/inbox/index.d.ts +8 -32
- package/build/core/asmail/inbox/index.js +120 -170
- package/build/core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.d.ts +11 -0
- package/build/{lib-client/xsp-fs/util/for-arrays.js → core/asmail/inbox/msg-indexing/dataset/v0/sql-db-v-none.js} +7 -13
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.d.ts +33 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/logs.js +72 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.d.ts +28 -0
- package/build/core/asmail/inbox/msg-indexing/dataset/v1/sql-db.js +477 -0
- package/build/core/asmail/inbox/msg-indexing/index.d.ts +12 -26
- package/build/core/asmail/inbox/msg-indexing/index.js +47 -68
- package/build/core/asmail/inbox/msg-on-disk.d.ts +0 -5
- package/build/core/asmail/inbox/msg-on-disk.js +2 -24
- package/build/core/asmail/index.js +8 -7
- package/build/core/asmail/msg/common.d.ts +0 -1
- package/build/core/asmail/msg/common.js +1 -1
- package/build/core/index.js +7 -4
- package/build/core/keyring/common.d.ts +1 -0
- package/build/core/keyring/common.js +2 -1
- package/build/core/keyring/index.d.ts +7 -44
- package/build/core/keyring/index.js +88 -222
- package/build/core/keyring/introductory-keys/published-intro-key.d.ts +17 -0
- package/build/core/keyring/introductory-keys/published-intro-key.js +190 -0
- package/build/core/keyring/peer-keys/dataset/v1/json-files.d.ts +105 -0
- package/build/core/keyring/peer-keys/dataset/v1/json-files.js +89 -0
- package/build/core/keyring/peer-keys/dataset/v2/logs.d.ts +43 -0
- package/build/core/keyring/peer-keys/dataset/v2/logs.js +116 -0
- package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.d.ts +73 -0
- package/build/core/keyring/peer-keys/dataset/v2/peer-keys-db.js +543 -0
- package/build/core/keyring/peer-keys/index.d.ts +24 -0
- package/build/core/keyring/peer-keys/index.js +208 -0
- package/build/core/storage/synced/obj-files.js +10 -3
- package/build/core/storage/synced/obj-status.d.ts +1 -1
- package/build/core/storage/synced/obj-status.js +8 -2
- package/build/core/storage/synced/storage.d.ts +4 -4
- package/build/core/storage/system-folders/apps-data.d.ts +0 -1
- package/build/core/storage/system-folders/apps-data.js +2 -6
- package/build/lib-client/asmail/recipient.d.ts +1 -0
- package/build/lib-client/asmail/recipient.js +11 -0
- package/build/lib-client/fs-utils/fs-sync-utils.d.ts +1 -1
- package/build/lib-client/fs-utils/fs-sync-utils.js +70 -13
- package/build/lib-client/xsp-fs/folder-node.js +13 -3
- package/build/lib-client/xsp-fs/fs.js +3 -1
- package/build/lib-client/xsp-fs/node-in-fs.d.ts +1 -0
- package/build/lib-client/xsp-fs/node-in-fs.js +7 -2
- package/build/lib-client/xsp-fs/node-persistence.js +1 -3
- package/build/lib-client/xsp-fs/xsp-payload-v2.js +1 -2
- package/build/lib-common/dataset-sync/per-device-json-array-logs.d.ts +58 -0
- package/build/lib-common/dataset-sync/per-device-json-array-logs.js +432 -0
- package/build/lib-common/dataset-sync/single-file.d.ts +10 -0
- package/build/lib-common/dataset-sync/single-file.js +77 -0
- package/build/lib-common/number-line.d.ts +10 -0
- package/build/lib-common/number-line.js +66 -0
- package/build/lib-common/objs-on-disk/obj-version-file.d.ts +1 -1
- package/build/lib-common/objs-on-disk/obj-version-file.js +3 -3
- package/build/lib-common/rng-def.d.ts +1 -0
- package/build/lib-common/rng-def.js +8 -0
- package/build/lib-sqlite-on-3nstorage/for-sqlite.d.ts +38 -0
- package/build/lib-sqlite-on-3nstorage/for-sqlite.js +162 -0
- package/build/lib-sqlite-on-3nstorage/index.d.ts +18 -5
- package/build/lib-sqlite-on-3nstorage/index.js +49 -26
- package/build/tests/caps-api/file-sink-checks/splice.js +72 -1
- package/build/tests/caps-api/fs-checks/sync-on-one-dev/upload.js +27 -1
- package/build/tests/caps-api/fs-checks/sync-with-two-devs/update-propagation.js +12 -3
- package/build/tests/computer.3nweb.core/inbox/msg-indexing.js +16 -12
- package/build/tests/computer.3nweb.core/keyrings.js +113 -28
- package/build/tests/jasmine.js +2 -2
- package/build/tests/units/number-line.js +9 -9
- package/package.json +2 -1
- package/build/core/asmail/inbox/msg-indexing/logs-n-entries.d.ts +0 -52
- package/build/core/asmail/inbox/msg-indexing/logs-n-entries.js +0 -333
- package/build/core/asmail/inbox/msg-indexing/sql-indexing.d.ts +0 -4
- package/build/core/asmail/inbox/msg-indexing/sql-indexing.js +0 -319
- package/build/core/keyring/correspondent-keys.d.ts +0 -149
- package/build/core/keyring/correspondent-keys.js +0 -403
- package/build/core/keyring/id-to-email-map.d.ts +0 -38
- package/build/core/keyring/id-to-email-map.js +0 -105
- package/build/core/keyring/keyring-storage.d.ts +0 -9
- package/build/core/keyring/keyring-storage.js +0 -54
- package/build/core/keyring/published-intro-key.d.ts +0 -45
- package/build/core/keyring/published-intro-key.js +0 -200
- package/build/lib-client/xsp-fs/util/for-arrays.d.ts +0 -1
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
19
|
exports.stringOfB64UrlSafeChars = stringOfB64UrlSafeChars;
|
|
20
20
|
exports.stringOfB64Chars = stringOfB64Chars;
|
|
21
|
+
exports.pseudoRNG = pseudoRNG;
|
|
21
22
|
const buffer_utils_1 = require("./buffer-utils");
|
|
22
23
|
async function stringOfB64UrlSafeChars(numOfChars, random) {
|
|
23
24
|
const numOfbytes = 3 * (1 + Math.floor(numOfChars / 4));
|
|
@@ -29,4 +30,11 @@ async function stringOfB64Chars(numOfChars, random) {
|
|
|
29
30
|
const byteArr = await random(numOfbytes);
|
|
30
31
|
return byteArr.toString('base64').substring(0, numOfChars);
|
|
31
32
|
}
|
|
33
|
+
async function pseudoRNG(n) {
|
|
34
|
+
const bytes = new Uint8Array(n);
|
|
35
|
+
for (let i = 0; i < n; i += 1) {
|
|
36
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
37
|
+
}
|
|
38
|
+
return bytes;
|
|
39
|
+
}
|
|
32
40
|
Object.freeze(exports);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { QueryExecResult } from './index.js';
|
|
2
|
+
import { ParamsObject, SqlValue, Database } from './sqljs.js';
|
|
3
|
+
export type TransformDefinition<T> = {
|
|
4
|
+
[field in keyof T]: {
|
|
5
|
+
toSQLValue: (v: any) => SqlValue;
|
|
6
|
+
fromSQLValue: (sv: SqlValue) => any;
|
|
7
|
+
} | 'as-is';
|
|
8
|
+
};
|
|
9
|
+
export declare function queryParamsFrom<T>(record: Partial<T>, transforms: TransformDefinition<T>, omit?: (keyof T)[]): ParamsObject;
|
|
10
|
+
export declare function fromQueryResult<T extends object>(queryResult: QueryExecResult, transforms: TransformDefinition<T>): T[];
|
|
11
|
+
export declare function queryParamsFromComplete<T>(record: T, transforms: TransformDefinition<T>): ParamsObject;
|
|
12
|
+
export declare function forTableInsert<T extends object>(record: T, transforms: TransformDefinition<T>): {
|
|
13
|
+
orderedColumns: string;
|
|
14
|
+
orderedValues: string;
|
|
15
|
+
insertParams: ParamsObject;
|
|
16
|
+
};
|
|
17
|
+
export declare function performTableInsert<T extends object>(db: Database, tabName: string, record: T, transforms: TransformDefinition<T>): void;
|
|
18
|
+
export declare function selectFromTable<T extends object>(db: Database, tabName: string, selectColumns: (keyof T)[] | '*', selectWith: Partial<T>, transforms: TransformDefinition<T>): Partial<T>[] | undefined;
|
|
19
|
+
export declare function updateInTable<T extends object>(db: Database, tabName: string, valuesToSet: Partial<T>, updateWith: Partial<T>, transforms: TransformDefinition<T>): void;
|
|
20
|
+
export declare function setExprFor<T extends object>(params: ParamsObject, omit?: (keyof T)[]): string;
|
|
21
|
+
export declare function andEqualExprFor<T extends object>(params: ParamsObject, omit?: (keyof T)[]): string;
|
|
22
|
+
export declare const optJsonTransform: {
|
|
23
|
+
toSQLValue: (v: object | null) => string | null;
|
|
24
|
+
fromSQLValue: (sv: SqlValue) => object | null;
|
|
25
|
+
};
|
|
26
|
+
export declare const jsonTransform: {
|
|
27
|
+
toSQLValue: (v: object) => string;
|
|
28
|
+
fromSQLValue: (sv: SqlValue) => object;
|
|
29
|
+
};
|
|
30
|
+
export declare const booleanTransform: {
|
|
31
|
+
toSQLValue: (b: boolean) => 1 | 0;
|
|
32
|
+
fromSQLValue: (flag: SqlValue) => boolean;
|
|
33
|
+
};
|
|
34
|
+
export declare const optStringAsEmptyTransform: {
|
|
35
|
+
toSQLValue: (s: string | null) => string;
|
|
36
|
+
fromSQLValue: (s: SqlValue) => string | null;
|
|
37
|
+
};
|
|
38
|
+
export declare function passOmitting<T extends object>(o: T, ...omitted: (keyof T)[]): Partial<T>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (C) 2025 3NSoft Inc.
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
6
|
+
the terms of the GNU General Public License as published by the Free Software
|
|
7
|
+
Foundation, either version 3 of the License, or (at your option) any later
|
|
8
|
+
version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful, but
|
|
11
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
13
|
+
See the GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License along with
|
|
16
|
+
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.optStringAsEmptyTransform = exports.booleanTransform = exports.jsonTransform = exports.optJsonTransform = void 0;
|
|
20
|
+
exports.queryParamsFrom = queryParamsFrom;
|
|
21
|
+
exports.fromQueryResult = fromQueryResult;
|
|
22
|
+
exports.queryParamsFromComplete = queryParamsFromComplete;
|
|
23
|
+
exports.forTableInsert = forTableInsert;
|
|
24
|
+
exports.performTableInsert = performTableInsert;
|
|
25
|
+
exports.selectFromTable = selectFromTable;
|
|
26
|
+
exports.updateInTable = updateInTable;
|
|
27
|
+
exports.setExprFor = setExprFor;
|
|
28
|
+
exports.andEqualExprFor = andEqualExprFor;
|
|
29
|
+
exports.passOmitting = passOmitting;
|
|
30
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
31
|
+
// @deno-types="../../shared-libs/sqlite-on-3nstorage/index.d.ts"
|
|
32
|
+
// @deno-types="../../shared-libs/sqlite-on-3nstorage/sqljs.d.ts"
|
|
33
|
+
const index_js_1 = require("./index.js");
|
|
34
|
+
function queryParamsFrom(record, transforms, omit) {
|
|
35
|
+
const params = {};
|
|
36
|
+
for (const field of Object.keys(record)) {
|
|
37
|
+
if (omit === null || omit === void 0 ? void 0 : omit.includes(field)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const value = record[field];
|
|
41
|
+
const transform = transforms[field];
|
|
42
|
+
if (!transform) {
|
|
43
|
+
throw new Error(`Field ${field} is not present in TransformDefinition object`);
|
|
44
|
+
}
|
|
45
|
+
else if (transform === 'as-is') {
|
|
46
|
+
params[`$${field}`] = value;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const { toSQLValue } = transform;
|
|
50
|
+
params[`$${field}`] = toSQLValue(value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return params;
|
|
54
|
+
}
|
|
55
|
+
function fromQueryResult(queryResult, transforms) {
|
|
56
|
+
const records = (0, index_js_1.objectFromQueryExecResult)(queryResult);
|
|
57
|
+
const objs = [];
|
|
58
|
+
for (const record of records) {
|
|
59
|
+
const obj = {};
|
|
60
|
+
for (const field of Object.keys(record)) {
|
|
61
|
+
const transform = transforms[field];
|
|
62
|
+
if (!transform || (transform === 'as-is')) {
|
|
63
|
+
obj[field] = record[field];
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
const { fromSQLValue } = transform;
|
|
67
|
+
obj[field] = fromSQLValue(record[field]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
objs.push(obj);
|
|
71
|
+
}
|
|
72
|
+
return objs;
|
|
73
|
+
}
|
|
74
|
+
function queryParamsFromComplete(record, transforms) {
|
|
75
|
+
const params = queryParamsFrom(record, transforms);
|
|
76
|
+
if (Object.keys(params).length < Object.keys(transforms).length) {
|
|
77
|
+
throw new Error(`Not all fields are present in the record`);
|
|
78
|
+
}
|
|
79
|
+
return params;
|
|
80
|
+
}
|
|
81
|
+
function forTableInsert(record, transforms) {
|
|
82
|
+
const insertParams = queryParamsFromComplete(record, transforms);
|
|
83
|
+
const columns = [];
|
|
84
|
+
const values = [];
|
|
85
|
+
for (const field of Object.keys(record)) {
|
|
86
|
+
const paramField = `$${field}`;
|
|
87
|
+
if (insertParams[paramField] === undefined) {
|
|
88
|
+
throw new Error(`Mismatch in expected parameters: ${paramField} is missing`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
columns.push(field);
|
|
92
|
+
values.push(paramField);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
insertParams,
|
|
97
|
+
orderedColumns: columns.join(', '),
|
|
98
|
+
orderedValues: values.join(', ')
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function performTableInsert(db, tabName, record, transforms) {
|
|
102
|
+
const { insertParams, orderedColumns, orderedValues } = forTableInsert(record, transforms);
|
|
103
|
+
db.exec(`INSERT INTO ${tabName} (${orderedColumns}) VALUES (${orderedValues})`, insertParams);
|
|
104
|
+
}
|
|
105
|
+
function selectFromTable(db, tabName, selectColumns, selectWith, transforms) {
|
|
106
|
+
const whereParams = queryParamsFrom(selectWith, transforms);
|
|
107
|
+
const whereClause = andEqualExprFor(whereParams);
|
|
108
|
+
const [sqlValue] = db.exec(`SELECT ${Array.isArray(selectColumns) ? selectColumns.join(', ') : '*'} FROM ${tabName} WHERE ${whereClause}`, whereParams);
|
|
109
|
+
return sqlValue ? fromQueryResult(sqlValue, transforms) : undefined;
|
|
110
|
+
}
|
|
111
|
+
function updateInTable(db, tabName, valuesToSet, updateWith, transforms) {
|
|
112
|
+
const whereParams = queryParamsFrom(updateWith, transforms);
|
|
113
|
+
const whereClause = andEqualExprFor(whereParams);
|
|
114
|
+
const setParams = queryParamsFrom(valuesToSet, transforms);
|
|
115
|
+
const setExpr = setExprFor(setParams);
|
|
116
|
+
db.exec(`UPDATE ${tabName} SET ${setExpr} WHERE ${whereClause}`, { ...whereParams, ...setParams });
|
|
117
|
+
}
|
|
118
|
+
function setExprFor(params, omit) {
|
|
119
|
+
const setPairs = [];
|
|
120
|
+
for (const paramField of Object.keys(params)) {
|
|
121
|
+
const field = paramField.substring(1);
|
|
122
|
+
if (!(omit === null || omit === void 0 ? void 0 : omit.includes(field))) {
|
|
123
|
+
setPairs.push(`${field}=${paramField}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return setPairs.join(', ');
|
|
127
|
+
}
|
|
128
|
+
function andEqualExprFor(params, omit) {
|
|
129
|
+
const pairs = [];
|
|
130
|
+
for (const paramField of Object.keys(params)) {
|
|
131
|
+
const field = paramField.substring(1);
|
|
132
|
+
if (!(omit === null || omit === void 0 ? void 0 : omit.includes(field))) {
|
|
133
|
+
pairs.push(`${field}=${paramField}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return pairs.join(' AND ');
|
|
137
|
+
}
|
|
138
|
+
exports.optJsonTransform = {
|
|
139
|
+
toSQLValue: (v) => (v ? JSON.stringify(v) : null),
|
|
140
|
+
fromSQLValue: (sv) => (sv ? JSON.parse(sv) : null)
|
|
141
|
+
};
|
|
142
|
+
exports.jsonTransform = {
|
|
143
|
+
toSQLValue: (v) => JSON.stringify(v),
|
|
144
|
+
fromSQLValue: (sv) => JSON.parse(sv)
|
|
145
|
+
};
|
|
146
|
+
exports.booleanTransform = {
|
|
147
|
+
toSQLValue: (b) => (b ? 1 : 0),
|
|
148
|
+
fromSQLValue: (flag) => ((flag === 1) ? true : false)
|
|
149
|
+
};
|
|
150
|
+
exports.optStringAsEmptyTransform = {
|
|
151
|
+
toSQLValue: (s) => (s ? s : ''),
|
|
152
|
+
fromSQLValue: (s) => (s ? s : '')
|
|
153
|
+
};
|
|
154
|
+
function passOmitting(o, ...omitted) {
|
|
155
|
+
const copy = {};
|
|
156
|
+
for (const [field, value] of Object.entries(o)) {
|
|
157
|
+
if (!omitted.includes(field)) {
|
|
158
|
+
copy[field] = value;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return copy;
|
|
162
|
+
}
|
|
@@ -4,20 +4,24 @@ export type Database = DBClass;
|
|
|
4
4
|
export type BindParams = QueryParams;
|
|
5
5
|
export type QueryExecResult = QueryResult;
|
|
6
6
|
type WritableFile = web3n.files.WritableFile;
|
|
7
|
-
|
|
8
|
-
skipUpload?: boolean;
|
|
9
|
-
}
|
|
7
|
+
type WritableFileSyncAPI = web3n.files.WritableFileSyncAPI;
|
|
10
8
|
export declare abstract class SQLiteOn3NStorage {
|
|
11
9
|
protected readonly database: Database;
|
|
12
10
|
protected readonly file: WritableFile;
|
|
13
11
|
protected readonly syncProc: SingleProc;
|
|
14
12
|
protected constructor(database: Database, file: WritableFile);
|
|
15
|
-
static
|
|
13
|
+
static makeSynced(file: WritableFile, startWithFileContent?: boolean): Promise<SQLiteOnSyncedFS>;
|
|
16
14
|
private start;
|
|
17
|
-
saveToFile(
|
|
15
|
+
saveToFile(): Promise<void>;
|
|
18
16
|
get db(): Database;
|
|
19
17
|
sync<T>(action: Action<T>): Promise<T>;
|
|
20
18
|
listTables(): string[];
|
|
19
|
+
makeCopyIn(file: WritableFile): Promise<SQLiteOn3NStorage>;
|
|
20
|
+
}
|
|
21
|
+
export declare class SQLiteOnSyncedFS extends SQLiteOn3NStorage {
|
|
22
|
+
constructor(db: Database, file: WritableFile);
|
|
23
|
+
get dbFile(): WritableFile;
|
|
24
|
+
get dbFileSync(): WritableFileSyncAPI;
|
|
21
25
|
}
|
|
22
26
|
export declare function objectFromQueryExecResult<T>(sqlResult: QueryExecResult): Array<T>;
|
|
23
27
|
type KeyedInfo<T> = {
|
|
@@ -26,11 +30,20 @@ type KeyedInfo<T> = {
|
|
|
26
30
|
export declare class TableColumnsAndParams<EntryType extends object> {
|
|
27
31
|
readonly name: string;
|
|
28
32
|
private readonly columnDefs;
|
|
33
|
+
/**
|
|
34
|
+
* c is column name, just column name
|
|
35
|
+
*/
|
|
29
36
|
readonly c: KeyedInfo<EntryType>;
|
|
30
37
|
readonly cReversed: {
|
|
31
38
|
[colName: string]: keyof EntryType;
|
|
32
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* p is parameter for column
|
|
42
|
+
*/
|
|
33
43
|
readonly p: KeyedInfo<EntryType>;
|
|
44
|
+
/**
|
|
45
|
+
* q is a qualified column name with table name
|
|
46
|
+
*/
|
|
34
47
|
readonly q: KeyedInfo<EntryType>;
|
|
35
48
|
readonly columnsCreateSection: string;
|
|
36
49
|
constructor(name: string, columnDefs: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
Copyright (C) 2022 - 2023 3NSoft Inc.
|
|
3
|
+
Copyright (C) 2022 - 2023, 2026 3NSoft Inc.
|
|
4
4
|
|
|
5
5
|
This program is free software: you can redistribute it and/or modify it under
|
|
6
6
|
the terms of the GNU General Public License as published by the Free Software
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.TableColumnsAndParams = exports.SQLiteOn3NStorage = void 0;
|
|
19
|
+
exports.TableColumnsAndParams = exports.SQLiteOnSyncedFS = exports.SQLiteOn3NStorage = void 0;
|
|
20
20
|
exports.objectFromQueryExecResult = objectFromQueryExecResult;
|
|
21
21
|
const sqljs_js_1 = require("./sqljs.js");
|
|
22
22
|
const synced_js_1 = require("./synced.js");
|
|
@@ -26,28 +26,35 @@ class SQLiteOn3NStorage {
|
|
|
26
26
|
this.file = file;
|
|
27
27
|
this.syncProc = new synced_js_1.SingleProc();
|
|
28
28
|
}
|
|
29
|
-
static async makeAndStart(file) {
|
|
29
|
+
// static async makeAndStart(file: WritableFile): Promise<SQLiteOn3NStorage> {
|
|
30
|
+
// const SQL = await initSqlJs(true);
|
|
31
|
+
// const fileContent = await readFileContent(file);
|
|
32
|
+
// const db = new SQL.Database(fileContent);
|
|
33
|
+
// let sqlite: SQLiteOn3NStorage;
|
|
34
|
+
// if (file.v?.sync) {
|
|
35
|
+
// sqlite = new SQLiteOnSyncedFS(db, file);
|
|
36
|
+
// } else if (file.v) {
|
|
37
|
+
// sqlite = new SQLiteOnLocalFS(db, file);
|
|
38
|
+
// } else {
|
|
39
|
+
// sqlite = new SQLiteOnDeviceFS(db, file);
|
|
40
|
+
// }
|
|
41
|
+
// await sqlite.start();
|
|
42
|
+
// return sqlite;
|
|
43
|
+
// }
|
|
44
|
+
static async makeSynced(file, startWithFileContent = true) {
|
|
30
45
|
var _a;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const db = new SQL.Database(fileContent);
|
|
34
|
-
let sqlite;
|
|
35
|
-
if ((_a = file.v) === null || _a === void 0 ? void 0 : _a.sync) {
|
|
36
|
-
sqlite = new SQLiteOnSyncedFS(db, file);
|
|
46
|
+
if (!((_a = file.v) === null || _a === void 0 ? void 0 : _a.sync)) {
|
|
47
|
+
throw new TypeError(`Expected synced file to make synced db`);
|
|
37
48
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
sqlite = new SQLiteOnDeviceFS(db, file);
|
|
43
|
-
}
|
|
44
|
-
await sqlite.start();
|
|
45
|
-
return sqlite;
|
|
49
|
+
const SQL = await (0, sqljs_js_1.default)(true);
|
|
50
|
+
const initialContent = startWithFileContent ? (await readFileContent(file)) : undefined;
|
|
51
|
+
const db = new SQL.Database(initialContent);
|
|
52
|
+
return new SQLiteOnSyncedFS(db, file);
|
|
46
53
|
}
|
|
47
54
|
async start() {
|
|
48
55
|
// XXX add listening process(es)
|
|
49
56
|
}
|
|
50
|
-
async saveToFile(
|
|
57
|
+
async saveToFile() {
|
|
51
58
|
await this.syncProc.startOrChain(async () => {
|
|
52
59
|
const dbFileContent = this.database.export();
|
|
53
60
|
await this.file.writeBytes(dbFileContent);
|
|
@@ -65,6 +72,24 @@ class SQLiteOn3NStorage {
|
|
|
65
72
|
result[0].values.map(row => row[0]) :
|
|
66
73
|
[]);
|
|
67
74
|
}
|
|
75
|
+
async makeCopyIn(file) {
|
|
76
|
+
var _a;
|
|
77
|
+
const SQL = await (0, sqljs_js_1.default)(true);
|
|
78
|
+
const fileContent = this.database.export();
|
|
79
|
+
const db = new SQL.Database(fileContent);
|
|
80
|
+
let sqlite;
|
|
81
|
+
if ((_a = file.v) === null || _a === void 0 ? void 0 : _a.sync) {
|
|
82
|
+
sqlite = new SQLiteOnSyncedFS(db, file);
|
|
83
|
+
}
|
|
84
|
+
else if (file.v) {
|
|
85
|
+
sqlite = new SQLiteOnLocalFS(db, file);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
sqlite = new SQLiteOnDeviceFS(db, file);
|
|
89
|
+
}
|
|
90
|
+
await sqlite.start();
|
|
91
|
+
return sqlite;
|
|
92
|
+
}
|
|
68
93
|
}
|
|
69
94
|
exports.SQLiteOn3NStorage = SQLiteOn3NStorage;
|
|
70
95
|
Object.freeze(SQLiteOn3NStorage.prototype);
|
|
@@ -74,16 +99,14 @@ class SQLiteOnSyncedFS extends SQLiteOn3NStorage {
|
|
|
74
99
|
super(db, file);
|
|
75
100
|
Object.seal(this);
|
|
76
101
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
else {
|
|
83
|
-
await this.file.v.sync.upload();
|
|
84
|
-
}
|
|
102
|
+
get dbFile() {
|
|
103
|
+
return this.file;
|
|
104
|
+
}
|
|
105
|
+
get dbFileSync() {
|
|
106
|
+
return this.file.v.sync;
|
|
85
107
|
}
|
|
86
108
|
}
|
|
109
|
+
exports.SQLiteOnSyncedFS = SQLiteOnSyncedFS;
|
|
87
110
|
Object.freeze(SQLiteOnSyncedFS.prototype);
|
|
88
111
|
Object.freeze(SQLiteOnSyncedFS);
|
|
89
112
|
class SQLiteOnLocalFS extends SQLiteOn3NStorage {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
Copyright (C) 2020 3NSoft Inc.
|
|
3
|
+
Copyright (C) 2020, 2026 3NSoft Inc.
|
|
4
4
|
|
|
5
5
|
This program is free software: you can redistribute it and/or modify it under
|
|
6
6
|
the terms of the GNU General Public License as published by the Free Software
|
|
@@ -19,6 +19,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
19
19
|
exports.specs = void 0;
|
|
20
20
|
const random_node_1 = require("../../../lib-common-on-node/random-node");
|
|
21
21
|
const bytes_equal_1 = require("../../libs-for-tests/bytes-equal");
|
|
22
|
+
const buffer_utils_1 = require("../../../lib-common/buffer-utils");
|
|
23
|
+
const json_equal_1 = require("../../libs-for-tests/json-equal");
|
|
22
24
|
exports.specs = {
|
|
23
25
|
description: 'gives file sinks, able to splice file content',
|
|
24
26
|
its: []
|
|
@@ -119,4 +121,73 @@ it.func = async function (s) {
|
|
|
119
121
|
expect((0, bytes_equal_1.bytesEqual)(content.subarray(7000), chunk2)).toBe(true);
|
|
120
122
|
};
|
|
121
123
|
exports.specs.its.push(it);
|
|
124
|
+
// XXX enbale this when capturing this bug
|
|
125
|
+
it = {
|
|
126
|
+
expectation: 'usecase with removing and adding sections of json', disableIn: 'device-fs'
|
|
127
|
+
};
|
|
128
|
+
it.func = async function (s) {
|
|
129
|
+
const { testFS } = s;
|
|
130
|
+
const file = 'incremental-file';
|
|
131
|
+
const COMMA_BYTE = buffer_utils_1.utf8.pack(',');
|
|
132
|
+
const SQ_BRACKET_BYTE = buffer_utils_1.utf8.pack(']');
|
|
133
|
+
const completeContent = [
|
|
134
|
+
{
|
|
135
|
+
record: {
|
|
136
|
+
msgId: "F-vo1A4zInEBGq994-e3KvrE9a8QXLcr",
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
record: {
|
|
141
|
+
msgId: "JYXfEEcg3-iX7UDjG3BU0Jha9DYX_Jt-",
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
const TEST_XATTR = 'attr-for-test';
|
|
146
|
+
const attrValue1 = Date.now();
|
|
147
|
+
const attrValue2 = { comment: 'now json value', ts: Date.now() };
|
|
148
|
+
// the following splice pattern hit an error, hence seemingly out of the blue use case, but it is an echo
|
|
149
|
+
// XXX removal of fs.updateXAttrs() call removes error in this situation
|
|
150
|
+
console.log(`p 1`);
|
|
151
|
+
await testFS.writeTxtFile(file, `[]`);
|
|
152
|
+
console.log(`p 2`);
|
|
153
|
+
let sink = await testFS.getByteSink(file, { truncate: false });
|
|
154
|
+
console.log(`p 2.1`);
|
|
155
|
+
await sink.splice(1, 1);
|
|
156
|
+
console.log(`p 2.2`);
|
|
157
|
+
let bytes = buffer_utils_1.utf8.pack(JSON.stringify(completeContent[0]));
|
|
158
|
+
console.log(`p 2.3`);
|
|
159
|
+
await sink.splice(1, 0, bytes);
|
|
160
|
+
console.log(`p 2.4`);
|
|
161
|
+
await sink.splice(1 + bytes.length, 0, SQ_BRACKET_BYTE);
|
|
162
|
+
console.log(`p 2.5`);
|
|
163
|
+
await sink.done();
|
|
164
|
+
console.log(`p 2.6`);
|
|
165
|
+
await testFS.updateXAttrs(file, { set: { [TEST_XATTR]: attrValue1 } });
|
|
166
|
+
console.log(`p 3`);
|
|
167
|
+
globalThis.log$ = true;
|
|
168
|
+
sink = await testFS.getByteSink(file, { truncate: false });
|
|
169
|
+
console.log(`p 3.1 -------- getting size`);
|
|
170
|
+
const len = await sink.getSize();
|
|
171
|
+
console.log(`p 3.2 ------------ replacing one last byte in content, \n layout`, await sink.showLayout());
|
|
172
|
+
await sink.splice(len - 1, 1, COMMA_BYTE);
|
|
173
|
+
// await sink.splice(len-1, 1);
|
|
174
|
+
// console.log(`p 3.2+ ---------------------------------- replacing one last byte in content, \n layout`, await sink.showLayout());
|
|
175
|
+
// await sink.splice(len-1, 0, COMMA_BYTE);
|
|
176
|
+
// await sink.splice(len-1, 1, utf8.pack(', '));
|
|
177
|
+
console.log(`p 3.3 ----------- writing content after last replaced byte, \n layout`, await sink.showLayout());
|
|
178
|
+
bytes = buffer_utils_1.utf8.pack(JSON.stringify(completeContent[1]));
|
|
179
|
+
await sink.splice(len, 0, bytes);
|
|
180
|
+
console.log(`p 3.4 -------- turning logging off, \n layout`, await sink.showLayout());
|
|
181
|
+
globalThis.log$ = false;
|
|
182
|
+
await sink.splice(len + bytes.length, 0, SQ_BRACKET_BYTE);
|
|
183
|
+
console.log(`p 3.5, \n layout`, await sink.showLayout());
|
|
184
|
+
const attrWrite = testFS.updateXAttrs(file, { set: { [TEST_XATTR]: attrValue2 } });
|
|
185
|
+
console.log(`p 3.6`);
|
|
186
|
+
await sink.done();
|
|
187
|
+
await attrWrite;
|
|
188
|
+
console.log(`p 4`);
|
|
189
|
+
expect((0, json_equal_1.deepEqual)(completeContent, await testFS.readJSONFile(file))).toBeTrue();
|
|
190
|
+
console.log(`p 5`);
|
|
191
|
+
};
|
|
192
|
+
// specs.its.push(it);
|
|
122
193
|
Object.freeze(exports);
|
|
@@ -161,10 +161,36 @@ it.func = async function (s) {
|
|
|
161
161
|
await testFS.v.sync.upload(folder);
|
|
162
162
|
syncStatus = await testFS.v.sync.status(folder, true);
|
|
163
163
|
expect(syncStatus.state).toBe('synced');
|
|
164
|
+
expect(syncStatus.existsInSyncedParent).toBeFalsy();
|
|
164
165
|
// note again that upload is not uploading children
|
|
165
166
|
syncStatus = await testFS.v.sync.status(childPath, true);
|
|
166
167
|
expect(syncStatus.state).toBe('unsynced');
|
|
167
|
-
expect(syncStatus.existsInSyncedParent).
|
|
168
|
+
expect(syncStatus.existsInSyncedParent).toBeTrue();
|
|
169
|
+
const subFS = await testFS.writableSubRoot(folder);
|
|
170
|
+
syncStatus = await subFS.v.sync.status('', true);
|
|
171
|
+
expect(syncStatus.existsInSyncedParent).toBeFalsy();
|
|
172
|
+
await testFS.v.sync.upload('');
|
|
173
|
+
syncStatus = await testFS.v.sync.status(folder, true);
|
|
174
|
+
expect(syncStatus.existsInSyncedParent).toBeTrue();
|
|
175
|
+
syncStatus = await subFS.v.sync.status('', true);
|
|
176
|
+
expect(syncStatus.existsInSyncedParent).toBeTrue();
|
|
177
|
+
const childFolder = `child-folder`;
|
|
178
|
+
await subFS.makeFolder(childFolder);
|
|
179
|
+
// child never uploaded at this point
|
|
180
|
+
await subFS.v.sync.upload('').then(() => fail(`upload of folder must fail with child that was never uploaded`), (exc) => {
|
|
181
|
+
expect(exc.type).toBe('fs-sync');
|
|
182
|
+
expect(exc.childNeverUploaded).toBeTrue();
|
|
183
|
+
expect(exc.childName).toBe(childFolder);
|
|
184
|
+
});
|
|
185
|
+
// uploading child, but it isn't yet in synced version of parent
|
|
186
|
+
await subFS.v.sync.upload(childFolder);
|
|
187
|
+
syncStatus = await subFS.v.sync.status(childFolder);
|
|
188
|
+
expect(syncStatus.state).toBe('synced');
|
|
189
|
+
expect(syncStatus.existsInSyncedParent).toBeFalsy();
|
|
190
|
+
// syncing parent makes child findable from upper synced point, and sync UX guarantees can be provided
|
|
191
|
+
await subFS.v.sync.upload('');
|
|
192
|
+
syncStatus = await subFS.v.sync.status(childFolder);
|
|
193
|
+
expect(syncStatus.existsInSyncedParent).toBeTrue();
|
|
168
194
|
};
|
|
169
195
|
exports.specs.its.push(it);
|
|
170
196
|
it = { expectation: `should do own concurrently` };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
Copyright (C) 2022, 2025 3NSoft Inc.
|
|
3
|
+
Copyright (C) 2022, 2025 - 2026 3NSoft Inc.
|
|
4
4
|
|
|
5
5
|
This program is free software: you can redistribute it and/or modify it under
|
|
6
6
|
the terms of the GNU General Public License as published by the Free Software
|
|
@@ -23,6 +23,7 @@ const random_node_1 = require("../../../../lib-common-on-node/random-node");
|
|
|
23
23
|
const bytes_equal_1 = require("../../../libs-for-tests/bytes-equal");
|
|
24
24
|
const json_equal_1 = require("../../../libs-for-tests/json-equal");
|
|
25
25
|
const test_utils_1 = require("../test-utils");
|
|
26
|
+
const lodash_1 = require("lodash");
|
|
26
27
|
exports.specs = {
|
|
27
28
|
description: '--',
|
|
28
29
|
its: []
|
|
@@ -141,7 +142,7 @@ it.func = async function ({ dev1FS, dev2FS }) {
|
|
|
141
142
|
await fs1.v.sync.upload(file);
|
|
142
143
|
let fileEv = await fileEventAt2;
|
|
143
144
|
expect(fileEv.type).toBe('remote-change');
|
|
144
|
-
expect((0, json_equal_1.deepEqual)(fileEv.syncStatus, await fs2.v.sync.status(file))).toBeTrue();
|
|
145
|
+
expect((0, json_equal_1.deepEqual)(fileEv.syncStatus, (0, lodash_1.omit)(await fs2.v.sync.status(file), 'existsInSyncedParent', 'uploading'))).toBeTrue();
|
|
145
146
|
expect(await fs2.v.sync.isRemoteVersionOnDisk(file, fileEv.newRemoteVersion)).toBe('none');
|
|
146
147
|
let events = collectFileDownloadEvents(fs2, file);
|
|
147
148
|
startedDownload = await fs2.v.sync.startDownload(file, fileEv.newRemoteVersion);
|
|
@@ -198,6 +199,10 @@ it.func = async function ({ dev1FS, dev2FS }) {
|
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
201
|
];
|
|
202
|
+
// XXX enable attr writes, when sink's bug is fixed
|
|
203
|
+
// const TEST_XATTR = 'attr-for-test';
|
|
204
|
+
// const attrValue1 = Date.now();
|
|
205
|
+
// const attrValue2 = { comment: 'now json value', ts: Date.now() };
|
|
201
206
|
// we do this file writing and uploading, cause this pattern hit an error,
|
|
202
207
|
// hence, we add this test with this seemingly out of the blue setup
|
|
203
208
|
await fs1.writeTxtFile(file, `[]`);
|
|
@@ -209,6 +214,7 @@ it.func = async function ({ dev1FS, dev2FS }) {
|
|
|
209
214
|
await sink.splice(1, 0, bytes);
|
|
210
215
|
await sink.splice(1 + bytes.length, 0, SQ_BRACKET_BYTE);
|
|
211
216
|
await sink.done();
|
|
217
|
+
// await fs1.updateXAttrs(file, { set: { [TEST_XATTR]: attrValue1 } });
|
|
212
218
|
await fs1.v.sync.upload(file);
|
|
213
219
|
sink = await fs1.getByteSink(file, { truncate: false });
|
|
214
220
|
const len = await sink.getSize();
|
|
@@ -216,14 +222,17 @@ it.func = async function ({ dev1FS, dev2FS }) {
|
|
|
216
222
|
bytes = buffer_utils_1.utf8.pack(JSON.stringify(completeContent[1]));
|
|
217
223
|
await sink.splice(len, 0, bytes);
|
|
218
224
|
await sink.splice(len + bytes.length, 0, SQ_BRACKET_BYTE);
|
|
225
|
+
// const attrWrite = fs1.updateXAttrs(file, { set: { [TEST_XATTR]: attrValue2 } });
|
|
219
226
|
await sink.done();
|
|
227
|
+
// await attrWrite;
|
|
220
228
|
expect((0, json_equal_1.deepEqual)(completeContent, await fs1.readJSONFile(file))).toBeTrue();
|
|
221
229
|
await fs1.v.sync.upload(file);
|
|
222
230
|
// and on the second device
|
|
223
231
|
expect((await fs2.v.sync.status('')).state).toBe('behind');
|
|
224
232
|
await fs2.v.sync.adoptRemote('');
|
|
225
233
|
expect((await fs2.v.sync.status(file)).state).toBe('synced');
|
|
226
|
-
expect((0, json_equal_1.deepEqual)(completeContent, await fs2.readJSONFile(file))).toBeTrue();
|
|
234
|
+
expect((0, json_equal_1.deepEqual)(completeContent, await fs2.readJSONFile(file))).withContext(`contents of files should be same`).toBeTrue();
|
|
235
|
+
// expect(deepEqual(attrValue2, await fs2.getXAttr(file, TEST_XATTR))).withContext(`content of attr should be same`).toBeTrue();
|
|
227
236
|
};
|
|
228
237
|
exports.specs.its.push(it);
|
|
229
238
|
Object.freeze(exports);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
Copyright 2022 3NSoft Inc.
|
|
3
|
+
Copyright 2022, 2026 3NSoft Inc.
|
|
4
4
|
|
|
5
5
|
This program is free software: you can redistribute it and/or modify it under
|
|
6
6
|
the terms of the GNU General Public License as published by the Free Software
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
19
|
const msg_indexing_1 = require("../../../core/asmail/inbox/msg-indexing");
|
|
20
|
-
// import { MsgKeyInfo } from "../core/asmail/keyring";
|
|
21
20
|
const jasmine_utils_1 = require("../../libs-for-tests/jasmine-utils");
|
|
22
21
|
const setups_1 = require("../../libs-for-tests/setups");
|
|
23
22
|
const test_utils_1 = require("../test-utils");
|
|
@@ -91,7 +90,7 @@ const msgs = [
|
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
92
|
];
|
|
94
|
-
|
|
93
|
+
describe(`Inbox MsgIndex`, () => {
|
|
95
94
|
const baseSetup = (0, setups_1.setupWithUsers)();
|
|
96
95
|
const testFolder = `inbox-indexing-test`;
|
|
97
96
|
let dev1Index;
|
|
@@ -100,7 +99,7 @@ xdescribe(`Inbox MsgIndex`, () => {
|
|
|
100
99
|
await setupDevsAndFSs(baseSetup);
|
|
101
100
|
}, 20000);
|
|
102
101
|
beforeEach(async () => {
|
|
103
|
-
dev1Index = await msg_indexing_1.
|
|
102
|
+
dev1Index = await (0, msg_indexing_1.makeMsgIndex)(setup.dev1FS(), setup.dev1LocalFS(), async (err, msg) => console.error(msg, err));
|
|
104
103
|
});
|
|
105
104
|
(0, jasmine_utils_1.afterEachCond)(async () => {
|
|
106
105
|
if (!setup.isUp) {
|
|
@@ -125,9 +124,11 @@ xdescribe(`Inbox MsgIndex`, () => {
|
|
|
125
124
|
await dev1Index.remove(m0.m.msgId);
|
|
126
125
|
lst = await dev1Index.listMsgs(undefined);
|
|
127
126
|
expect(lst.length).toBe(1);
|
|
128
|
-
|
|
129
|
-
expect((0, bytes_equal_1.bytesEqual)(keyInfo.msgKey, m1.k.key))
|
|
130
|
-
|
|
127
|
+
let keyInfo = await dev1Index.getKeyFor(m1.m.msgId, m1.m.deliveryTS);
|
|
128
|
+
expect((0, bytes_equal_1.bytesEqual)(keyInfo.msgKey, m1.k.key)).toBeTrue();
|
|
129
|
+
// getting removed / non-existing message
|
|
130
|
+
keyInfo = await dev1Index.getKeyFor(m0.m.msgId, m0.m.deliveryTS);
|
|
131
|
+
expect(keyInfo).toBeUndefined();
|
|
131
132
|
}, undefined, setup);
|
|
132
133
|
(0, jasmine_utils_1.itCond)(`work with race conditions on different devices`, async () => {
|
|
133
134
|
for (const m of msgs.slice(0, 2)) {
|
|
@@ -135,11 +136,14 @@ xdescribe(`Inbox MsgIndex`, () => {
|
|
|
135
136
|
}
|
|
136
137
|
// sleep to allow time for propagation to dev2
|
|
137
138
|
await (0, sleep_1.sleep)(100);
|
|
138
|
-
const dev2Index = await msg_indexing_1.
|
|
139
|
+
const dev2Index = await (0, msg_indexing_1.makeMsgIndex)(setup.dev2FS(), setup.dev2LocalFS(), async (err, msg) => console.error(msg, err));
|
|
140
|
+
await (0, sleep_1.sleep)(100);
|
|
139
141
|
let lst = await dev2Index.listMsgs(undefined);
|
|
140
|
-
expect(lst.length)
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
expect(lst.length).withContext(`messages at a start of 2nd device`).toBe(2);
|
|
143
|
+
await dev1Index.add(msgs[2].m, msgs[2].k);
|
|
144
|
+
await (0, sleep_1.sleep)(100);
|
|
145
|
+
lst = await dev2Index.listMsgs(undefined);
|
|
146
|
+
expect(lst.length).withContext(`added message on the 1st device should show up on the 2nd`).toBe(3);
|
|
143
147
|
dev2Index.stopSyncing();
|
|
144
|
-
});
|
|
148
|
+
}, 10000);
|
|
145
149
|
});
|