@vrtmrz/livesync-commonlib 0.1.0-rc.9 → 0.1.1-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dist/API/DirectFileManipulator.d.ts +1 -1
- package/dist/API/DirectFileManipulatorV2.d.ts +16 -31
- package/dist/API/DirectFileManipulatorV2.js +35 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/interfaces/DatabaseFileAccess.d.ts +8 -0
- package/dist/interfaces/FileHandler.d.ts +17 -1
- package/dist/interfaces/FileReflectionProvenance.d.ts +32 -0
- package/dist/interfaces/FileReflectionProvenance.js +25 -0
- package/dist/managers/ConflictManager.js +42 -9
- package/dist/managers/EntryManager/EntryManager.d.ts +1 -0
- package/dist/managers/EntryManager/EntryManager.js +5 -1
- package/dist/managers/EntryManager/EntryManagerImpls.d.ts +1 -0
- package/dist/managers/EntryManager/EntryManagerImpls.js +29 -1
- package/dist/managers/LiveSyncManagers.js +1 -1
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +1 -0
- package/dist/pouchdb/LiveSyncLocalDB.js +3 -0
- package/dist/serviceFeatures/offlineScanner.js +3 -2
- package/dist/serviceFeatures/remoteConfig.d.ts +2 -2
- package/dist/serviceFeatures/remoteConfig.js +2 -2
- package/dist/serviceModules/ServiceDatabaseFileAccessBase.d.ts +5 -0
- package/dist/serviceModules/ServiceDatabaseFileAccessBase.js +69 -13
- package/dist/serviceModules/ServiceFileHandlerBase.d.ts +22 -1
- package/dist/serviceModules/ServiceFileHandlerBase.js +323 -48
- package/dist/services/HeadlessServices.d.ts +4 -0
- package/dist/services/HeadlessServices.js +7 -3
- package/dist/services/base/KeyValueDBService.d.ts +2 -1
- package/dist/services/base/KeyValueDBService.js +10 -6
- package/dist/services/base/PathService.d.ts +6 -0
- package/dist/services/base/PathService.js +4 -1
- package/dist/services/base/ReplicatorService.d.ts +1 -0
- package/dist/services/base/ReplicatorService.js +8 -6
- package/dist/services/implements/base/SvelteDialog.d.ts +5 -0
- package/dist/services/implements/headless/HeadlessAPIService.js +2 -0
- package/dist/worker/bgWorker.js +1 -1
- package/docs/conflict-resolution.md +173 -0
- package/docs/development.md +6 -0
- package/docs/proven-in-use.md +18 -6
- package/docs/releasing.md +10 -7
- package/package.json +6 -26
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Self-hosted LiveSync Commonlib
|
|
2
2
|
|
|
3
|
-
Commonlib is the ESM package which provides shared data, storage, replication, and host-context primitives for Self-hosted LiveSync, its CLI,
|
|
3
|
+
Commonlib is the ESM package which provides shared data, storage, replication, and host-context primitives for Self-hosted LiveSync, its CLI, WebApp, WebPeer, and LiveSync Bridge.
|
|
4
4
|
|
|
5
5
|
This package is primarily for maintainers of those clients and for integrations which deliberately reuse a reviewed Commonlib contract. It is not yet a general-purpose LiveSync SDK: the package does not provide a stable end-to-end client factory or own host UI, permissions, credentials, process lifecycle, or application persistence.
|
|
6
6
|
|
|
@@ -9,6 +9,9 @@ This package is primarily for maintainers of those clients and for integrations
|
|
|
9
9
|
>
|
|
10
10
|
> Existing contributions remain welcome and the normal contribution process has not changed. Pull requests which touch packaging, host lifecycle, or compatibility boundaries may need coordination or a rebase while this boundary settles; maintainers will identify affected work rather than asking contributors to adopt a new process pre-emptively.
|
|
11
11
|
|
|
12
|
+
> [!NOTE]
|
|
13
|
+
> The multilingual translation resources and application dialogues are now maintained in the main application repository, [Self-hosted LiveSync](https://github.com/vrtmrz/obsidian-livesync). New pull requests for them are welcome there. Thank you to everyone who has contributed translations and dialogue improvements here.
|
|
14
|
+
|
|
12
15
|
## Status and versioning
|
|
13
16
|
|
|
14
17
|
The package is pre-1.0. Pin an exact reviewed version, and review changes before upgrading. An entry in the package export map means that the import is deliberate and package-tested; it does not make every semantic detail of that entry a final 1.0 guarantee.
|
|
@@ -114,17 +117,17 @@ See [the remote configuration profile guide](docs/remote-configurations.md) for
|
|
|
114
117
|
|
|
115
118
|
## Contract scope
|
|
116
119
|
|
|
117
|
-
The package is currently an infrastructure and compatibility boundary. The context, rooted-storage,
|
|
120
|
+
The package is currently an infrastructure and compatibility boundary. The context, rooted-storage, standard-I/O, and revision-tree safety contracts have focused cross-platform, instance-isolation, or real-PouchDB tests. Platform details which cannot be shared, such as file timestamp fidelity and browser permission handling, remain host concerns and are documented separately.
|
|
118
121
|
|
|
119
122
|
`DirectFileManipulator` is useful for existing integrations, but its enumeration, watch ownership, failure, conflict, concurrency, readiness, and disposal semantics are not a stable high-level SDK contract. It is the migration source for the planned file client, not the shape to preserve as the new API.
|
|
120
123
|
|
|
121
124
|
The accepted replacement direction is an asynchronously created file client with stable `list`, `get`, `put`, `delete`, `watch`, and `close` operations. The operation result, conflict, concurrency, watch checkpoint, retry, and error contracts still require focused decisions and tests before that client can be published.
|
|
122
125
|
|
|
123
|
-
Package developers should read [the developer guide](docs/development.md). The focused contracts are described in [the storage guide](docs/platform-storage.md), [the standard-I/O guide](docs/platform-standard-io.md), [the settings lifecycle guide](docs/settings-lifecycle.md),
|
|
126
|
+
Package developers should read [the developer guide](docs/development.md). The focused contracts are described in [the storage guide](docs/platform-storage.md), [the standard-I/O guide](docs/platform-standard-io.md), [the settings lifecycle guide](docs/settings-lifecycle.md), [the remote configuration profile guide](docs/remote-configurations.md), and [the conflict-resolution guide](docs/conflict-resolution.md).
|
|
124
127
|
|
|
125
128
|
## Proven in maintained hosts
|
|
126
129
|
|
|
127
|
-
The package boundary is exercised by Self-hosted LiveSync's Obsidian plug-in, CLI,
|
|
130
|
+
The package boundary is exercised by Self-hosted LiveSync's Obsidian plug-in, CLI, WebApp, and WebPeer compositions. Their validation is not uniform: the Obsidian plug-in and CLI have the strongest device and long-running synchronisation evidence, while WebApp and WebPeer have focused adapter and runtime tests, browser smoke tests, a cross-application P2P scenario ending at the production CLI, and public Pages smoke validation.
|
|
128
131
|
|
|
129
132
|
See [Proven in maintained hosts](docs/proven-in-use.md) for the actual composition points, downstream tests, and remaining limits. The examples deliberately separate focused public entries which a new consumer may copy from `compat/*` imports which exist to migrate the current application.
|
|
130
133
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { DirectFileManipulator } from "./DirectFileManipulatorV2.js";
|
|
2
|
-
export type { DirectFileManipulatorOptions } from "./DirectFileManipulatorV2.js";
|
|
2
|
+
export type { DirectFileManipulatorOptions, DirectFileManipulatorRuntimeOptions, } from "./DirectFileManipulatorV2.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The API for manipulating files stored in the CouchDB by Self-hosted LiveSync or its families.
|
|
3
3
|
*/
|
|
4
|
-
import { type DocumentID, type FilePathWithPrefix, type EntryHasPath, type FilePath, type EntryDoc, type NewEntry, type PlainEntry, type LoadedEntry, type HashAlgorithm, type RemoteDBSettings, type ChunkSplitterVersion, type SyncParameters, type E2EEAlgorithm } from "../common/types.js";
|
|
4
|
+
import { type DocumentID, type FilePathWithPrefix, type EntryHasPath, type FilePath, type EntryDoc, type NewEntry, type PlainEntry, type LoadedEntry, type HashAlgorithm, type ObsidianLiveSyncSettings, type RemoteDBSettings, type ChunkSplitterVersion, type SyncParameters, type E2EEAlgorithm } from "../common/types.js";
|
|
5
5
|
import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "../pouchdb/LiveSyncLocalDB.js";
|
|
6
6
|
import { HeadlessServiceHub } from "../services/HeadlessServices.js";
|
|
7
7
|
import { HeadlessDatabaseService } from "../services/implements/headless/HeadlessDatabaseService.js";
|
|
@@ -34,6 +34,11 @@ export type DirectFileManipulatorOptions = {
|
|
|
34
34
|
chunkSplitterVersion?: ChunkSplitterVersion;
|
|
35
35
|
E2EEAlgorithm?: E2EEAlgorithm;
|
|
36
36
|
};
|
|
37
|
+
/** Host runtime capabilities used by a direct CouchDB connection. */
|
|
38
|
+
export type DirectFileManipulatorRuntimeOptions = Readonly<{
|
|
39
|
+
/** Fetch implementation passed to PouchDB. Omit it to use PouchDB's default transport. */
|
|
40
|
+
fetch?: typeof globalThis.fetch;
|
|
41
|
+
}>;
|
|
37
42
|
export type ReadyEntry = (NewEntry | PlainEntry) & {
|
|
38
43
|
data: string[];
|
|
39
44
|
};
|
|
@@ -54,6 +59,7 @@ export type EnumerateConditions = {
|
|
|
54
59
|
export declare class DirectFileManipulator implements LiveSyncLocalDBEnv {
|
|
55
60
|
liveSyncLocalDB: LiveSyncLocalDB;
|
|
56
61
|
options: DirectFileManipulatorOptions;
|
|
62
|
+
readonly runtimeOptions: DirectFileManipulatorRuntimeOptions;
|
|
57
63
|
ready: {
|
|
58
64
|
promise: Promise<void>;
|
|
59
65
|
resolve: (value: void | PromiseLike<void>) => void;
|
|
@@ -61,8 +67,8 @@ export declare class DirectFileManipulator implements LiveSyncLocalDBEnv {
|
|
|
61
67
|
};
|
|
62
68
|
services: HeadlessServiceHub<ServiceContext>;
|
|
63
69
|
init(): Promise<void>;
|
|
64
|
-
getBoundDatabaseService(options: () => DirectFileManipulatorOptions): typeof HeadlessDatabaseService;
|
|
65
|
-
constructor(options: DirectFileManipulatorOptions);
|
|
70
|
+
getBoundDatabaseService(options: () => DirectFileManipulatorOptions, runtimeOptions: DirectFileManipulatorRuntimeOptions): typeof HeadlessDatabaseService;
|
|
71
|
+
constructor(options: DirectFileManipulatorOptions, runtimeOptions?: DirectFileManipulatorRuntimeOptions);
|
|
66
72
|
$$id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix;
|
|
67
73
|
$$path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID>;
|
|
68
74
|
getInitialSyncParameters(setting: RemoteDBSettings): Promise<SyncParameters>;
|
|
@@ -73,7 +79,7 @@ export declare class DirectFileManipulator implements LiveSyncLocalDBEnv {
|
|
|
73
79
|
getSettings(): RemoteDBSettings;
|
|
74
80
|
close(): Promise<void>;
|
|
75
81
|
path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID>;
|
|
76
|
-
get settings():
|
|
82
|
+
get settings(): ObsidianLiveSyncSettings;
|
|
77
83
|
/**
|
|
78
84
|
* Get specific document from the Remote Database by path.
|
|
79
85
|
* @param path
|
|
@@ -103,7 +109,7 @@ export declare class DirectFileManipulator implements LiveSyncLocalDBEnv {
|
|
|
103
109
|
enumerate(_cond: EnumerateConditions): AsyncGenerator<never, void, unknown>;
|
|
104
110
|
_enumerate(startKey: string, endKey: string, opt: {
|
|
105
111
|
metaOnly: boolean;
|
|
106
|
-
}): AsyncGenerator<
|
|
112
|
+
}): AsyncGenerator<(import("../common/types.js").DatabaseEntry & import("../common/types.js").EntryBase & import("../common/types.js").EntryWithEden & {
|
|
107
113
|
path: FilePathWithPrefix;
|
|
108
114
|
children: string[];
|
|
109
115
|
type: import("../common/models/db.type.js").EntryTypes["NOTE_BINARY"];
|
|
@@ -111,39 +117,18 @@ export declare class DirectFileManipulator implements LiveSyncLocalDBEnv {
|
|
|
111
117
|
path: FilePathWithPrefix;
|
|
112
118
|
children: string[];
|
|
113
119
|
type: import("../common/models/db.type.js").EntryTypes["NOTE_PLAIN"];
|
|
114
|
-
} & PouchDB.Core.AllDocsMeta & PouchDB.Core.IdMeta & PouchDB.Core.RevisionIdMeta) |
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
} & {
|
|
119
|
-
deleted?: boolean;
|
|
120
|
-
} & PouchDB.Core.AllDocsMeta & PouchDB.Core.IdMeta & PouchDB.Core.RevisionIdMeta) | (import("../common/types.js").DatabaseEntry & import("../common/types.js").EntryBase & import("../common/types.js").EntryWithEden & {
|
|
120
|
+
} & PouchDB.Core.AllDocsMeta & PouchDB.Core.IdMeta & PouchDB.Core.RevisionIdMeta) | ReadyEntry, void, unknown>;
|
|
121
|
+
enumerateAllNormalDocs(opt: {
|
|
122
|
+
metaOnly: boolean;
|
|
123
|
+
}): AsyncGenerator<(import("../common/types.js").DatabaseEntry & import("../common/types.js").EntryBase & import("../common/types.js").EntryWithEden & {
|
|
121
124
|
path: FilePathWithPrefix;
|
|
122
125
|
children: string[];
|
|
123
126
|
type: import("../common/models/db.type.js").EntryTypes["NOTE_BINARY"];
|
|
124
|
-
} & {
|
|
125
|
-
data: string | string[];
|
|
126
|
-
datatype: import("../common/types.js").EntryTypeNotes;
|
|
127
127
|
} & PouchDB.Core.AllDocsMeta & PouchDB.Core.IdMeta & PouchDB.Core.RevisionIdMeta) | (import("../common/types.js").DatabaseEntry & import("../common/types.js").EntryBase & import("../common/types.js").EntryWithEden & {
|
|
128
128
|
path: FilePathWithPrefix;
|
|
129
129
|
children: string[];
|
|
130
130
|
type: import("../common/models/db.type.js").EntryTypes["NOTE_PLAIN"];
|
|
131
|
-
} &
|
|
132
|
-
data: string | string[];
|
|
133
|
-
datatype: import("../common/types.js").EntryTypeNotes;
|
|
134
|
-
} & PouchDB.Core.AllDocsMeta & PouchDB.Core.IdMeta & PouchDB.Core.RevisionIdMeta) | (import("../common/types.js").DatabaseEntry & import("../common/types.js").EntryBase & import("../common/types.js").EntryWithEden & {
|
|
135
|
-
path: FilePathWithPrefix;
|
|
136
|
-
children: string[];
|
|
137
|
-
type: import("../common/models/db.type.js").EntryTypes["NOTE_BINARY"];
|
|
138
|
-
} & {
|
|
139
|
-
deleted?: boolean;
|
|
140
|
-
} & {
|
|
141
|
-
data: string | string[];
|
|
142
|
-
datatype: import("../common/types.js").EntryTypeNotes;
|
|
143
|
-
} & PouchDB.Core.AllDocsMeta & PouchDB.Core.IdMeta & PouchDB.Core.RevisionIdMeta), void, unknown>, unknown>;
|
|
144
|
-
enumerateAllNormalDocs(opt: {
|
|
145
|
-
metaOnly: boolean;
|
|
146
|
-
}): AsyncGenerator<ReadyEntry, void, unknown>;
|
|
131
|
+
} & PouchDB.Core.AllDocsMeta & PouchDB.Core.IdMeta & PouchDB.Core.RevisionIdMeta) | ReadyEntry, void, unknown>;
|
|
147
132
|
watching: boolean;
|
|
148
133
|
changes: PouchDB.Core.Changes<EntryDoc> | undefined;
|
|
149
134
|
since: string;
|
|
@@ -42,32 +42,45 @@ function isReadyEntry(doc) {
|
|
|
42
42
|
class DirectFileManipulator {
|
|
43
43
|
liveSyncLocalDB;
|
|
44
44
|
options;
|
|
45
|
+
runtimeOptions;
|
|
45
46
|
ready = promiseWithResolvers();
|
|
46
47
|
services;
|
|
47
48
|
async init() {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
try {
|
|
50
|
+
await this.services.appLifecycle.onReady();
|
|
51
|
+
await this.liveSyncLocalDB.initializeDatabase();
|
|
52
|
+
this.liveSyncLocalDB.refreshSettings();
|
|
53
|
+
this.ready.resolve();
|
|
54
|
+
} catch (error) {
|
|
55
|
+
this.ready.reject(error);
|
|
56
|
+
}
|
|
52
57
|
}
|
|
53
|
-
getBoundDatabaseService(options) {
|
|
58
|
+
getBoundDatabaseService(options, runtimeOptions) {
|
|
54
59
|
const _option = options;
|
|
60
|
+
const _runtimeOptions = runtimeOptions;
|
|
55
61
|
return class HeadlessDatabaseServiceExt extends HeadlessDatabaseService {
|
|
56
62
|
createPouchDBInstance(_name, _options) {
|
|
57
63
|
const option = _option();
|
|
58
|
-
|
|
64
|
+
const pouchDBOptions = {
|
|
59
65
|
auth: { username: option.username, password: option.password }
|
|
60
|
-
}
|
|
66
|
+
};
|
|
67
|
+
if (_runtimeOptions.fetch) {
|
|
68
|
+
pouchDBOptions.fetch = _runtimeOptions.fetch;
|
|
69
|
+
}
|
|
70
|
+
return new PouchDB(option.url + "/" + option.database, pouchDBOptions);
|
|
61
71
|
}
|
|
62
72
|
};
|
|
63
73
|
}
|
|
64
|
-
constructor(options) {
|
|
74
|
+
constructor(options, runtimeOptions = {}) {
|
|
65
75
|
this.options = options;
|
|
76
|
+
this.runtimeOptions = runtimeOptions;
|
|
66
77
|
const getSettings = () => this.settings;
|
|
67
78
|
const context = new ServiceContext();
|
|
68
79
|
this.services = new HeadlessServiceHub(context, {
|
|
69
80
|
pouchDB: PouchDB,
|
|
70
|
-
database: this.getBoundDatabaseService(() => this.options)
|
|
81
|
+
database: this.getBoundDatabaseService(() => this.options, this.runtimeOptions),
|
|
82
|
+
getPathObfuscationPassphrase: () => this.options.obfuscatePassphrase ?? false,
|
|
83
|
+
databaseLifecycleMode: "direct-access"
|
|
71
84
|
});
|
|
72
85
|
this.services.setting.saveData.setHandler((data) => {
|
|
73
86
|
console.warn("Saving settings is not supported in DirectFileManipulator.");
|
|
@@ -77,8 +90,11 @@ class DirectFileManipulator {
|
|
|
77
90
|
console.warn("Loading settings is not supported in DirectFileManipulator.");
|
|
78
91
|
return Promise.resolve(getSettings());
|
|
79
92
|
});
|
|
93
|
+
this.services.setting.settings = getSettings();
|
|
80
94
|
this.services.databaseEvents.onDatabaseInitialisation.addHandler(this.$everyOnInitializeDatabase.bind(this));
|
|
81
95
|
this.liveSyncLocalDB = new LiveSyncLocalDB(this.options.url, this);
|
|
96
|
+
void this.ready.promise.catch(() => {
|
|
97
|
+
});
|
|
82
98
|
void this.init();
|
|
83
99
|
}
|
|
84
100
|
$$id2path(id, entry, stripPrefix) {
|
|
@@ -190,6 +206,7 @@ class DirectFileManipulator {
|
|
|
190
206
|
endpoint: "",
|
|
191
207
|
enableCompression: this.options.enableCompression ?? DEFAULT_SETTINGS.enableCompression,
|
|
192
208
|
handleFilenameCaseSensitive: this.options.handleFilenameCaseSensitive ?? DEFAULT_SETTINGS.handleFilenameCaseSensitive,
|
|
209
|
+
usePathObfuscation: !!this.options.obfuscatePassphrase,
|
|
193
210
|
E2EEAlgorithm: this.options.E2EEAlgorithm ?? E2EEAlgorithms.V2
|
|
194
211
|
}
|
|
195
212
|
};
|
|
@@ -289,7 +306,7 @@ class DirectFileManipulator {
|
|
|
289
306
|
async *enumerate(_cond) {
|
|
290
307
|
}
|
|
291
308
|
async *_enumerate(startKey, endKey, opt) {
|
|
292
|
-
if (opt.metaOnly) return this.liveSyncLocalDB.findEntries(startKey, endKey, {});
|
|
309
|
+
if (opt.metaOnly) return yield* this.liveSyncLocalDB.findEntries(startKey, endKey, {});
|
|
293
310
|
for await (const f of this.liveSyncLocalDB.findEntries(startKey, endKey, {})) {
|
|
294
311
|
yield await this.getByMeta(f);
|
|
295
312
|
}
|
|
@@ -337,7 +354,14 @@ class DirectFileManipulator {
|
|
|
337
354
|
}
|
|
338
355
|
}
|
|
339
356
|
Logger(`WATCH: PROCESSING: ${doc.path}`, LEVEL_VERBOSE, "watch");
|
|
340
|
-
|
|
357
|
+
let docX;
|
|
358
|
+
try {
|
|
359
|
+
docX = await this.getByMeta(doc);
|
|
360
|
+
} catch (ex) {
|
|
361
|
+
Logger(`WATCH: DOCUMENT LOAD FAILED: ${doc.path}`, LEVEL_INFO, "watch");
|
|
362
|
+
Logger(ex, LEVEL_VERBOSE, "watch");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
341
365
|
try {
|
|
342
366
|
await callback(docX, change.seq);
|
|
343
367
|
Logger(`WATCH: PROCESS DONE: ${doc.path}`, LEVEL_INFO, "watch");
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { DirectFileManipulator, type DirectFileManipulatorOptions } from "./API/DirectFileManipulator.js";
|
|
1
|
+
export { DirectFileManipulator, type DirectFileManipulatorOptions, type DirectFileManipulatorRuntimeOptions, } from "./API/DirectFileManipulator.js";
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,18 @@ import type { FilePathWithPrefix, LoadedEntry, MetaEntry, UXFileInfo, UXFileInfo
|
|
|
2
2
|
export interface DatabaseFileAccess {
|
|
3
3
|
delete: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string) => Promise<boolean>;
|
|
4
4
|
store: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise<boolean>;
|
|
5
|
+
/** Store a file as a child of an exact revision and return the created revision. */
|
|
6
|
+
storeWithBaseRevision: (file: UXFileInfo, baseRevision: string | undefined, skipCheck?: boolean) => Promise<string | false>;
|
|
5
7
|
storeAsConflictedRevision: (file: UXFileInfo, currentRev: string, skipCheck?: boolean) => Promise<boolean>;
|
|
8
|
+
/** Preserve unknown storage content as a conflict and return its exact revision. */
|
|
9
|
+
storeAsConflictedRevisionWithResult: (file: UXFileInfo, currentRev: string, skipCheck?: boolean) => Promise<string | false>;
|
|
10
|
+
/** Store a user deletion as a visible logical-deletion child of an exact revision. */
|
|
11
|
+
storeDeletionWithBaseRevision: (file: UXFileInfoStub | FilePathWithPrefix, baseRevision: string) => Promise<string | false>;
|
|
6
12
|
storeContent(path: FilePathWithPrefix, content: string): Promise<boolean>;
|
|
7
13
|
createChunks: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise<boolean>;
|
|
8
14
|
hasContentInRevisionHistory: (file: UXFileInfoStub | FilePathWithPrefix, content: string | string[] | Blob | ArrayBuffer, currentRev?: string) => Promise<boolean>;
|
|
15
|
+
/** Return every available revision whose content exactly matches the supplied bytes. */
|
|
16
|
+
findContentRevisions: (file: UXFileInfoStub | FilePathWithPrefix, content: string | string[] | Blob | ArrayBuffer, currentRev?: string) => Promise<string[]>;
|
|
9
17
|
fetch: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string, waitForReady?: boolean, skipCheck?: boolean) => Promise<UXFileInfo | false>;
|
|
10
18
|
fetchEntryFromMeta: (meta: MetaEntry, waitForReady?: boolean, skipCheck?: boolean) => Promise<LoadedEntry | false>;
|
|
11
19
|
fetchEntryMeta: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string, skipCheck?: boolean) => Promise<MetaEntry | false>;
|
|
@@ -3,11 +3,27 @@ import type { UXFileInfo, UXFileInfoStub, UXInternalFileInfoStub } from "../comm
|
|
|
3
3
|
export interface IFileHandler {
|
|
4
4
|
readFileFromStub(file: UXFileInfoStub | UXFileInfo): Promise<UXFileInfo>;
|
|
5
5
|
storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force?: boolean, onlyChunks?: boolean): Promise<boolean>;
|
|
6
|
+
/**
|
|
7
|
+
* Store the current storage content as a child of an exact live database revision.
|
|
8
|
+
*
|
|
9
|
+
* This explicit branch-selection boundary is intended for user-directed
|
|
10
|
+
* reconciliation. It rechecks that the selected revision is still live,
|
|
11
|
+
* and records the resulting revision as the storage provenance. When
|
|
12
|
+
* `createIfDifferent` is false, differing storage content is rejected
|
|
13
|
+
* instead of creating a child revision.
|
|
14
|
+
*/
|
|
15
|
+
storeFileToDBWithBaseRevision(info: UXFileInfoStub | UXFileInfo | FilePathWithPrefix, baseRevision: string, createIfDifferent?: boolean): Promise<boolean>;
|
|
6
16
|
deleteFileFromDB(info: UXFileInfoStub | UXInternalFileInfoStub | FilePath): Promise<boolean>;
|
|
7
17
|
renameFileInDB(info: UXFileInfoStub | UXFileInfo, oldPath: FilePath | FilePathWithPrefix): Promise<boolean>;
|
|
18
|
+
/**
|
|
19
|
+
* Create a logical deletion on one exact database revision.
|
|
20
|
+
*
|
|
21
|
+
* When device-local storage provenance names the deleted revision, the
|
|
22
|
+
* implementation clears that stale provenance without changing storage.
|
|
23
|
+
*/
|
|
8
24
|
deleteRevisionFromDB(info: UXFileInfoStub | FilePath | FilePathWithPrefix, rev: string): Promise<boolean | undefined>;
|
|
9
25
|
resolveConflictedByDeletingRevision(info: UXFileInfoStub | FilePath, rev: string): Promise<boolean | undefined>;
|
|
10
|
-
dbToStorageWithSpecificRev(info: UXFileInfoStub | UXFileInfo | FilePath | null, rev: string, force?: boolean): Promise<boolean>;
|
|
26
|
+
dbToStorageWithSpecificRev(info: UXFileInfoStub | UXFileInfo | FilePath | FilePathWithPrefix | null, rev: string, force?: boolean): Promise<boolean>;
|
|
11
27
|
dbToStorage(entryInfo: MetaEntry | FilePathWithPrefix, info: UXFileInfoStub | UXFileInfo | FilePath | null, force?: boolean): Promise<boolean>;
|
|
12
28
|
createAllChunks(showingNotice?: boolean): Promise<void>;
|
|
13
29
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FilePathWithPrefix } from "../common/types.js";
|
|
2
|
+
import type { SimpleStore } from "../common/utils.js";
|
|
3
|
+
export type FileReflectionProvenanceRecord = {
|
|
4
|
+
/** Exact database revision which most recently produced the storage state. */
|
|
5
|
+
revision: string;
|
|
6
|
+
/** Raw modification time observed from this device's storage after reflection. */
|
|
7
|
+
observedStorageMtime?: number;
|
|
8
|
+
};
|
|
9
|
+
export interface FileReflectionProvenance {
|
|
10
|
+
get(path: FilePathWithPrefix): Promise<FileReflectionProvenanceRecord | undefined>;
|
|
11
|
+
set(path: FilePathWithPrefix, record: FileReflectionProvenanceRecord): Promise<void>;
|
|
12
|
+
delete(path: FilePathWithPrefix): Promise<void>;
|
|
13
|
+
move(from: FilePathWithPrefix, to: FilePathWithPrefix): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Device-local provenance backed by an existing host-owned key-value store.
|
|
17
|
+
*
|
|
18
|
+
* The revision is authoritative. The raw storage mtime is diagnostic and may
|
|
19
|
+
* be used as a fast change hint, but it never proves content or branch identity.
|
|
20
|
+
* The host may construct this object before opening its store, but it must not
|
|
21
|
+
* invoke provenance operations until its normal storage lifecycle is ready.
|
|
22
|
+
* Store failures are reported to the caller rather than hidden by readiness
|
|
23
|
+
* waits, so lifecycle violations and reset races cannot hang file processing.
|
|
24
|
+
*/
|
|
25
|
+
export declare class StoredFileReflectionProvenance implements FileReflectionProvenance {
|
|
26
|
+
private readonly store;
|
|
27
|
+
constructor(store: SimpleStore<FileReflectionProvenanceRecord>);
|
|
28
|
+
get(path: FilePathWithPrefix): Promise<FileReflectionProvenanceRecord | undefined>;
|
|
29
|
+
set(path: FilePathWithPrefix, record: FileReflectionProvenanceRecord): Promise<void>;
|
|
30
|
+
delete(path: FilePathWithPrefix): Promise<void>;
|
|
31
|
+
move(from: FilePathWithPrefix, to: FilePathWithPrefix): Promise<void>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class StoredFileReflectionProvenance {
|
|
2
|
+
constructor(store) {
|
|
3
|
+
this.store = store;
|
|
4
|
+
}
|
|
5
|
+
store;
|
|
6
|
+
async get(path) {
|
|
7
|
+
return await this.store.get(path) ?? void 0;
|
|
8
|
+
}
|
|
9
|
+
async set(path, record) {
|
|
10
|
+
await this.store.set(path, record);
|
|
11
|
+
}
|
|
12
|
+
async delete(path) {
|
|
13
|
+
await this.store.delete(path);
|
|
14
|
+
}
|
|
15
|
+
async move(from, to) {
|
|
16
|
+
const record = await this.get(from);
|
|
17
|
+
if (record) {
|
|
18
|
+
await this.set(to, record);
|
|
19
|
+
}
|
|
20
|
+
await this.delete(from);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
StoredFileReflectionProvenance
|
|
25
|
+
};
|
|
@@ -15,6 +15,21 @@ import {
|
|
|
15
15
|
isObjectMargeApplicable
|
|
16
16
|
} from "../common/utils.js";
|
|
17
17
|
import { isErrorOfMissingDoc } from "../pouchdb/utils_couchdb.js";
|
|
18
|
+
function revisionGeneration(revision) {
|
|
19
|
+
const generation = Number(revision.split("-", 1)[0]);
|
|
20
|
+
return Number.isFinite(generation) ? generation : Number.MAX_SAFE_INTEGER;
|
|
21
|
+
}
|
|
22
|
+
function compareConflictCandidates(left, right) {
|
|
23
|
+
const generationDifference = revisionGeneration(left.revision) - revisionGeneration(right.revision);
|
|
24
|
+
if (generationDifference !== 0) return generationDifference;
|
|
25
|
+
const leftMtime = left.leaf === false || !Number.isFinite(left.leaf.mtime) ? Number.NEGATIVE_INFINITY : left.leaf.mtime;
|
|
26
|
+
const rightMtime = right.leaf === false || !Number.isFinite(right.leaf.mtime) ? Number.NEGATIVE_INFINITY : right.leaf.mtime;
|
|
27
|
+
const mtimeDifference = leftMtime - rightMtime;
|
|
28
|
+
if (mtimeDifference !== 0) return mtimeDifference;
|
|
29
|
+
if (left.revision < right.revision) return -1;
|
|
30
|
+
if (left.revision > right.revision) return 1;
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
18
33
|
class ConflictManager {
|
|
19
34
|
options;
|
|
20
35
|
constructor(options) {
|
|
@@ -258,13 +273,22 @@ class ConflictManager {
|
|
|
258
273
|
}
|
|
259
274
|
async tryAutoMergeSensibly(path, test, conflicts) {
|
|
260
275
|
const conflictedRev = conflicts[0];
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
276
|
+
let commonBase = "";
|
|
277
|
+
try {
|
|
278
|
+
const documentId = await this.options.pathService.path2id(path);
|
|
279
|
+
const [currentBranch, conflictedBranch] = await Promise.all([
|
|
280
|
+
this.database.get(documentId, { rev: test._rev, revs_info: true }),
|
|
281
|
+
this.database.get(documentId, { rev: conflictedRev, revs_info: true })
|
|
282
|
+
]);
|
|
283
|
+
const currentAvailable = new Set(
|
|
284
|
+
(currentBranch._revs_info || []).filter((revision) => revision.status === "available").map((revision) => revision.rev)
|
|
285
|
+
);
|
|
286
|
+
commonBase = (conflictedBranch._revs_info || []).filter((revision) => revision.status === "available" && currentAvailable.has(revision.rev)).sort((left, right) => Number(right.rev.split("-")[0]) - Number(left.rev.split("-")[0]))[0]?.rev ?? "";
|
|
287
|
+
} catch (ex) {
|
|
288
|
+
Logger(`Could not determine the common revision for ${path}`, LOG_LEVEL_VERBOSE);
|
|
289
|
+
Logger(ex, LOG_LEVEL_VERBOSE);
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
268
292
|
let p = void 0;
|
|
269
293
|
if (commonBase) {
|
|
270
294
|
if (isSensibleMargeApplicable(path)) {
|
|
@@ -302,9 +326,18 @@ class ConflictManager {
|
|
|
302
326
|
if (test == null) return { ok: MISSING_OR_ERROR };
|
|
303
327
|
if (!test._conflicts) return { ok: NOT_CONFLICTED };
|
|
304
328
|
if (test._conflicts.length == 0) return { ok: NOT_CONFLICTED };
|
|
305
|
-
const
|
|
329
|
+
const conflictCandidates = await Promise.all(
|
|
330
|
+
test._conflicts.map(
|
|
331
|
+
async (revision) => ({
|
|
332
|
+
revision,
|
|
333
|
+
leaf: await this.getConflictedDoc(path, revision)
|
|
334
|
+
})
|
|
335
|
+
)
|
|
336
|
+
);
|
|
337
|
+
conflictCandidates.sort(compareConflictCandidates);
|
|
338
|
+
const conflicts = conflictCandidates.map(({ revision }) => revision);
|
|
306
339
|
const leftLeaf = await this.getConflictedDoc(path, test._rev);
|
|
307
|
-
const rightLeaf =
|
|
340
|
+
const rightLeaf = conflictCandidates[0].leaf;
|
|
308
341
|
if (leftLeaf !== false && rightLeaf !== false && leftLeaf.data == rightLeaf.data && leftLeaf.deleted == rightLeaf.deleted) {
|
|
309
342
|
return { leftRev: test._rev, rightRev: conflicts[0], leftLeaf, rightLeaf };
|
|
310
343
|
}
|
|
@@ -33,5 +33,6 @@ export declare class EntryManager {
|
|
|
33
33
|
getDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions, dump?: boolean, waitForReady?: boolean, includeDeleted?: boolean): Promise<false | LoadedEntry>;
|
|
34
34
|
getDBEntryFromMeta(meta: LoadedEntry | MetaEntry, dump?: boolean, waitForReady?: boolean): Promise<false | LoadedEntry>;
|
|
35
35
|
deleteDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions): Promise<boolean>;
|
|
36
|
+
storeDeletionAtRevision(path: FilePathWithPrefix | FilePath, baseRevision: string): Promise<false | PouchDB.Core.Response>;
|
|
36
37
|
putDBEntry(note: SavingEntry, onlyChunks?: boolean, conflictBaseRev?: string): Promise<false | PouchDB.Core.Response>;
|
|
37
38
|
}
|
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
getDBEntryMetaByPath,
|
|
9
9
|
isTargetFile,
|
|
10
10
|
prepareChunk,
|
|
11
|
-
putDBEntry
|
|
11
|
+
putDBEntry,
|
|
12
|
+
storeDeletionByPathAtRevision
|
|
12
13
|
} from "./EntryManagerImpls.js";
|
|
13
14
|
class EntryManager {
|
|
14
15
|
options;
|
|
@@ -64,6 +65,9 @@ class EntryManager {
|
|
|
64
65
|
async deleteDBEntry(path, opt) {
|
|
65
66
|
return await deleteDBEntryByPath(this.serviceHost, this, path, opt);
|
|
66
67
|
}
|
|
68
|
+
async storeDeletionAtRevision(path, baseRevision) {
|
|
69
|
+
return await storeDeletionByPathAtRevision(this.serviceHost, this, path, baseRevision);
|
|
70
|
+
}
|
|
67
71
|
async putDBEntry(note, onlyChunks, conflictBaseRev) {
|
|
68
72
|
return await putDBEntry(this.serviceHost, this, note, onlyChunks, conflictBaseRev);
|
|
69
73
|
}
|
|
@@ -71,4 +71,5 @@ export declare function getDBEntryByPath(host: NecessaryServicesInterfaces<"path
|
|
|
71
71
|
datatype: import("../../common/types.js").EntryTypeNotes;
|
|
72
72
|
} & PouchDB.Core.IdMeta)>;
|
|
73
73
|
export declare function deleteDBEntryByPath(host: NecessaryServicesInterfaces<"path" | "setting", never>, { localDatabase }: NecessaryManagers<"localDatabase">, path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions): Promise<boolean>;
|
|
74
|
+
export declare function storeDeletionByPathAtRevision(host: NecessaryServicesInterfaces<"path" | "setting", never>, { localDatabase }: NecessaryManagers<"localDatabase">, path: FilePathWithPrefix | FilePath, baseRevision: string): Promise<PouchDB.Core.Response | false>;
|
|
74
75
|
export {};
|
|
@@ -484,6 +484,33 @@ async function deleteDBEntryByPath(host, { localDatabase }, path, opt) {
|
|
|
484
484
|
throw ex;
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
|
+
async function storeDeletionByPathAtRevision(host, { localDatabase }, path, baseRevision) {
|
|
488
|
+
if (!isTargetFile(host, path)) {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
const id = await host.services.path.path2id(path);
|
|
492
|
+
try {
|
|
493
|
+
return await serialized("file:" + path, async () => {
|
|
494
|
+
const obj = await localDatabase.get(id, {
|
|
495
|
+
rev: baseRevision
|
|
496
|
+
});
|
|
497
|
+
if (obj.type === "leaf") {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
delete obj._deleted;
|
|
501
|
+
obj.deleted = true;
|
|
502
|
+
obj.mtime = Date.now();
|
|
503
|
+
const result = await localDatabase.put(obj, { force: true });
|
|
504
|
+
Logger(`Entry soft-deleted from revision: ${path} (${obj._id.substring(0, 8)}-${result.rev})`);
|
|
505
|
+
return result;
|
|
506
|
+
}) ?? false;
|
|
507
|
+
} catch (ex) {
|
|
508
|
+
if (isErrorOfMissingDoc(ex)) {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
throw ex;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
487
514
|
export {
|
|
488
515
|
canUseOnDemandChunking,
|
|
489
516
|
computeChunkRetrievalMethod,
|
|
@@ -495,5 +522,6 @@ export {
|
|
|
495
522
|
isLegacyNote,
|
|
496
523
|
isTargetFile,
|
|
497
524
|
prepareChunk,
|
|
498
|
-
putDBEntry
|
|
525
|
+
putDBEntry,
|
|
526
|
+
storeDeletionByPathAtRevision
|
|
499
527
|
};
|
|
@@ -56,7 +56,7 @@ class LiveSyncManagers {
|
|
|
56
56
|
}
|
|
57
57
|
getManagerMembers() {
|
|
58
58
|
this.log("Creating LiveSync Managers...");
|
|
59
|
-
const database = this.options.
|
|
59
|
+
const database = this.options.database;
|
|
60
60
|
const changeManager = new ChangeManager({
|
|
61
61
|
database
|
|
62
62
|
});
|
|
@@ -190,6 +190,7 @@ export declare class LiveSyncLocalDB {
|
|
|
190
190
|
getDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions, dump?: boolean, waitForReady?: boolean, includeDeleted?: boolean): Promise<false | LoadedEntry>;
|
|
191
191
|
getDBEntryFromMeta(meta: LoadedEntry | MetaEntry, dump?: boolean, waitForReady?: boolean): Promise<false | LoadedEntry>;
|
|
192
192
|
deleteDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions): Promise<boolean>;
|
|
193
|
+
storeDeletionAtRevision(path: FilePathWithPrefix | FilePath, baseRevision: string): Promise<false | PouchDB.Core.Response>;
|
|
193
194
|
putDBEntry(note: SavingEntry, onlyChunks?: boolean, conflictBaseRev?: string): Promise<false | PouchDB.Core.Response>;
|
|
194
195
|
getConflictedDoc(path: FilePathWithPrefix, rev: string): Promise<false | diff_result_leaf>;
|
|
195
196
|
tryAutoMerge(path: FilePathWithPrefix, enableMarkdownAutoMerge: boolean): AutoMergeResult;
|
|
@@ -364,6 +364,9 @@ class LiveSyncLocalDB {
|
|
|
364
364
|
async deleteDBEntry(path, opt) {
|
|
365
365
|
return await this.managers.entryManager.deleteDBEntry(path, opt);
|
|
366
366
|
}
|
|
367
|
+
async storeDeletionAtRevision(path, baseRevision) {
|
|
368
|
+
return await this.managers.entryManager.storeDeletionAtRevision(path, baseRevision);
|
|
369
|
+
}
|
|
367
370
|
async putDBEntry(note, onlyChunks, conflictBaseRev) {
|
|
368
371
|
return await this.managers.entryManager.putDBEntry(note, onlyChunks, conflictBaseRev);
|
|
369
372
|
}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
isMetaEntry
|
|
9
9
|
} from "../common/types.js";
|
|
10
10
|
import { compareMTime, isAnyNote } from "../common/utils.js";
|
|
11
|
-
import { stripAllPrefixes } from "../string_and_binary/path.js";
|
|
11
|
+
import { shouldBeIgnored, stripAllPrefixes } from "../string_and_binary/path.js";
|
|
12
12
|
import { createInstanceLogFunction } from "../services/lib/logUtils.js";
|
|
13
13
|
import { BASE_IS_NEW, EVEN, TARGET_IS_NEW } from "../common/models/shared.const.symbols.js";
|
|
14
14
|
import { UnresolvedErrorManager } from "../services/base/UnresolvedErrorManager.js";
|
|
@@ -124,6 +124,7 @@ async function collectFilesOnStorage(host, settings, log) {
|
|
|
124
124
|
const filesStorageSrc = await host.serviceModules.storageAccess.getFiles();
|
|
125
125
|
const _filesStorage = [];
|
|
126
126
|
for (const f of filesStorageSrc) {
|
|
127
|
+
if (shouldBeIgnored(f.path)) continue;
|
|
127
128
|
if (await host.services.vault.isTargetFile(f.path)) {
|
|
128
129
|
_filesStorage.push(f);
|
|
129
130
|
}
|
|
@@ -147,7 +148,7 @@ async function collectDatabaseFiles(host, settings, log, showingNotice) {
|
|
|
147
148
|
"syncAll"
|
|
148
149
|
);
|
|
149
150
|
const path = getPathFromEntry(host, doc);
|
|
150
|
-
if (host.services.vault.isValidPath(path) && await host.services.vault.isTargetFile(path)) {
|
|
151
|
+
if (host.services.vault.isValidPath(path) && !shouldBeIgnored(path) && await host.services.vault.isTargetFile(path)) {
|
|
151
152
|
if (!isMetaEntry(doc)) {
|
|
152
153
|
log(`Invalid entry: ${path}`, LOG_LEVEL_INFO);
|
|
153
154
|
continue;
|
|
@@ -65,11 +65,11 @@ export declare function activateRemoteConfiguration(settings: ObsidianLiveSyncSe
|
|
|
65
65
|
*/
|
|
66
66
|
export declare function activateP2PRemoteConfiguration(settings: ObsidianLiveSyncSettings, id: string): ObsidianLiveSyncSettings | false;
|
|
67
67
|
/**
|
|
68
|
-
* Command: Switch
|
|
68
|
+
* Command: Switch active connection
|
|
69
69
|
*/
|
|
70
70
|
export declare function commandSwitchActiveRemote(host: RemoteConfigHost): Promise<void>;
|
|
71
71
|
/**
|
|
72
|
-
* Command:
|
|
72
|
+
* Command: Sync with a saved connection
|
|
73
73
|
*/
|
|
74
74
|
export declare function commandReplicateWithSpecificRemote(host: RemoteConfigHost): Promise<void>;
|
|
75
75
|
/**
|
|
@@ -311,12 +311,12 @@ function useRemoteConfigurationMigration(host) {
|
|
|
311
311
|
function useRemoteConfiguration(host) {
|
|
312
312
|
host.services.API.addCommand({
|
|
313
313
|
id: "livesync-switch-remote",
|
|
314
|
-
name: "Switch
|
|
314
|
+
name: "Switch active connection",
|
|
315
315
|
callback: () => commandSwitchActiveRemote(host)
|
|
316
316
|
});
|
|
317
317
|
host.services.API.addCommand({
|
|
318
318
|
id: "livesync-replicate-with-specific",
|
|
319
|
-
name: "
|
|
319
|
+
name: "Sync with a saved connection",
|
|
320
320
|
callback: () => commandReplicateWithSpecificRemote(host)
|
|
321
321
|
});
|
|
322
322
|
return true;
|
|
@@ -26,10 +26,15 @@ export declare class ServiceDatabaseFileAccessBase extends ServiceModuleBase<Ser
|
|
|
26
26
|
delete(file: UXFileInfoStub | FilePathWithPrefix, rev?: string): Promise<boolean>;
|
|
27
27
|
createChunks(file: UXFileInfo, force?: boolean, skipCheck?: boolean): Promise<boolean>;
|
|
28
28
|
store(file: UXFileInfo, force?: boolean, skipCheck?: boolean): Promise<boolean>;
|
|
29
|
+
storeWithBaseRevision(file: UXFileInfo, baseRevision: string | undefined, skipCheck?: boolean): Promise<string | false>;
|
|
29
30
|
storeAsConflictedRevision(file: UXFileInfo, currentRev: string, skipCheck?: boolean): Promise<boolean>;
|
|
31
|
+
storeAsConflictedRevisionWithResult(file: UXFileInfo, currentRev: string, skipCheck?: boolean): Promise<string | false>;
|
|
32
|
+
storeDeletionWithBaseRevision(file: UXFileInfoStub | FilePathWithPrefix, baseRevision: string): Promise<string | false>;
|
|
30
33
|
storeContent(path: FilePathWithPrefix, content: string): Promise<boolean>;
|
|
31
34
|
private __store;
|
|
32
35
|
private getParentRev;
|
|
36
|
+
private findContentRevisionsInternal;
|
|
37
|
+
findContentRevisions(file: UXFileInfoStub | FilePathWithPrefix, content: string | string[] | Blob | ArrayBuffer, currentRev?: string): Promise<string[]>;
|
|
33
38
|
hasContentInRevisionHistory(file: UXFileInfoStub | FilePathWithPrefix, content: string | string[] | Blob | ArrayBuffer, currentRev?: string): Promise<boolean>;
|
|
34
39
|
getConflictedRevs(file: UXFileInfoStub | FilePathWithPrefix): Promise<string[]>;
|
|
35
40
|
fetch(file: UXFileInfoStub | FilePathWithPrefix, rev?: string, waitForReady?: boolean, skipCheck?: boolean): Promise<UXFileInfo | false>;
|