@dabble/patches 0.2.3 → 0.2.5
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.
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Unsubscriber } from '../event-signal.js';
|
|
1
2
|
import type { JSONPatch } from '../json-patch/JSONPatch.js';
|
|
2
3
|
import type { Change, PatchesSnapshot } from '../types.js';
|
|
3
4
|
/**
|
|
@@ -29,12 +30,16 @@ export declare class PatchesDoc<T extends object = object> {
|
|
|
29
30
|
get id(): string | null;
|
|
30
31
|
/** Current local state (committed + sending + pending). */
|
|
31
32
|
get state(): T;
|
|
33
|
+
/** Alias for state. */
|
|
34
|
+
get value(): T;
|
|
32
35
|
/** Last committed revision number from the server. */
|
|
33
36
|
get committedRev(): number;
|
|
34
37
|
/** Are there changes currently awaiting server confirmation? */
|
|
35
38
|
get isSending(): boolean;
|
|
36
39
|
/** Are there local changes that haven't been sent yet? */
|
|
37
40
|
get hasPending(): boolean;
|
|
41
|
+
/** Subscribe to be notified whenever value changes. */
|
|
42
|
+
subscribe(onUpdate: (newValue: T) => void): Unsubscriber;
|
|
38
43
|
/**
|
|
39
44
|
* Exports the document state for persistence.
|
|
40
45
|
* NOTE: Any changes currently marked as `sending` are included in the
|
|
@@ -37,6 +37,10 @@ export class PatchesDoc {
|
|
|
37
37
|
get state() {
|
|
38
38
|
return this._state;
|
|
39
39
|
}
|
|
40
|
+
/** Alias for state. */
|
|
41
|
+
get value() {
|
|
42
|
+
return this._state;
|
|
43
|
+
}
|
|
40
44
|
/** Last committed revision number from the server. */
|
|
41
45
|
get committedRev() {
|
|
42
46
|
return this._committedRev;
|
|
@@ -49,6 +53,12 @@ export class PatchesDoc {
|
|
|
49
53
|
get hasPending() {
|
|
50
54
|
return this._pendingChanges.length > 0;
|
|
51
55
|
}
|
|
56
|
+
/** Subscribe to be notified whenever value changes. */
|
|
57
|
+
subscribe(onUpdate) {
|
|
58
|
+
const unsub = this.onUpdate(onUpdate);
|
|
59
|
+
onUpdate(this._state);
|
|
60
|
+
return unsub;
|
|
61
|
+
}
|
|
52
62
|
/**
|
|
53
63
|
* Exports the document state for persistence.
|
|
54
64
|
* NOTE: Any changes currently marked as `sending` are included in the
|
|
@@ -14,13 +14,18 @@ import type { PatchesStore, TrackedDoc } from './PatchesStore.js';
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class IndexedDBStore implements PatchesStore {
|
|
16
16
|
private db;
|
|
17
|
-
private dbName
|
|
17
|
+
private dbName?;
|
|
18
18
|
private dbPromise;
|
|
19
19
|
/** Subscribe to be notified after local state changes are saved to the database. */
|
|
20
20
|
readonly onPendingChanges: import("../event-signal.js").Signal<(docId: string, changes: Change[]) => void>;
|
|
21
|
-
constructor(dbName
|
|
21
|
+
constructor(dbName?: string);
|
|
22
22
|
private initDB;
|
|
23
23
|
private getDB;
|
|
24
|
+
/**
|
|
25
|
+
* Set the name of the database, loads a new database connection.
|
|
26
|
+
* @param dbName - The new name of the database.
|
|
27
|
+
*/
|
|
28
|
+
setName(dbName: string): void;
|
|
24
29
|
/**
|
|
25
30
|
* Closes the database connection. After calling this method, the store
|
|
26
31
|
* will no longer be usable. A new instance must be created to reopen
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { signal } from '../event-signal.js';
|
|
2
2
|
import { transformPatch } from '../json-patch/transformPatch.js';
|
|
3
|
-
import { applyChanges } from '../utils.js';
|
|
3
|
+
import { applyChanges, deferred } from '../utils.js';
|
|
4
4
|
const DB_VERSION = 1;
|
|
5
5
|
const SNAPSHOT_INTERVAL = 200;
|
|
6
6
|
/**
|
|
@@ -21,36 +21,52 @@ export class IndexedDBStore {
|
|
|
21
21
|
/** Subscribe to be notified after local state changes are saved to the database. */
|
|
22
22
|
this.onPendingChanges = signal();
|
|
23
23
|
this.dbName = dbName;
|
|
24
|
-
this.dbPromise =
|
|
24
|
+
this.dbPromise = deferred();
|
|
25
|
+
if (this.dbName) {
|
|
26
|
+
this.initDB();
|
|
27
|
+
}
|
|
25
28
|
}
|
|
26
29
|
async initDB() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
30
|
+
if (!this.dbName)
|
|
31
|
+
return;
|
|
32
|
+
const request = indexedDB.open(this.dbName, DB_VERSION);
|
|
33
|
+
request.onerror = () => this.dbPromise.reject(request.error);
|
|
34
|
+
request.onsuccess = () => {
|
|
35
|
+
this.db = request.result;
|
|
36
|
+
this.dbPromise.resolve(this.db);
|
|
37
|
+
};
|
|
38
|
+
request.onupgradeneeded = event => {
|
|
39
|
+
const db = event.target.result;
|
|
40
|
+
// Create stores
|
|
41
|
+
if (!db.objectStoreNames.contains('snapshots')) {
|
|
42
|
+
db.createObjectStore('snapshots', { keyPath: 'docId' });
|
|
43
|
+
}
|
|
44
|
+
if (!db.objectStoreNames.contains('committedChanges')) {
|
|
45
|
+
db.createObjectStore('committedChanges', { keyPath: ['docId', 'rev'] });
|
|
46
|
+
}
|
|
47
|
+
if (!db.objectStoreNames.contains('pendingChanges')) {
|
|
48
|
+
db.createObjectStore('pendingChanges', { keyPath: ['docId', 'rev'] });
|
|
49
|
+
}
|
|
50
|
+
if (!db.objectStoreNames.contains('docs')) {
|
|
51
|
+
db.createObjectStore('docs', { keyPath: 'docId' });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
51
54
|
}
|
|
52
55
|
getDB() {
|
|
53
|
-
return this.dbPromise;
|
|
56
|
+
return this.dbPromise.promise;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Set the name of the database, loads a new database connection.
|
|
60
|
+
* @param dbName - The new name of the database.
|
|
61
|
+
*/
|
|
62
|
+
setName(dbName) {
|
|
63
|
+
this.dbName = dbName;
|
|
64
|
+
if (this.db) {
|
|
65
|
+
this.db.close();
|
|
66
|
+
this.db = null;
|
|
67
|
+
this.dbPromise = deferred();
|
|
68
|
+
}
|
|
69
|
+
this.initDB();
|
|
54
70
|
}
|
|
55
71
|
/**
|
|
56
72
|
* Closes the database connection. After calling this method, the store
|
|
@@ -58,14 +74,17 @@ export class IndexedDBStore {
|
|
|
58
74
|
* the database.
|
|
59
75
|
*/
|
|
60
76
|
async close() {
|
|
61
|
-
await this.dbPromise;
|
|
77
|
+
await this.dbPromise.promise;
|
|
62
78
|
if (this.db) {
|
|
63
79
|
this.db.close();
|
|
64
80
|
this.db = null;
|
|
65
|
-
this.dbPromise =
|
|
81
|
+
this.dbPromise = deferred();
|
|
82
|
+
this.dbPromise.resolve(null);
|
|
66
83
|
}
|
|
67
84
|
}
|
|
68
85
|
async deleteDB() {
|
|
86
|
+
if (!this.dbName)
|
|
87
|
+
return;
|
|
69
88
|
await this.close();
|
|
70
89
|
await new Promise((resolve, reject) => {
|
|
71
90
|
const request = indexedDB.deleteDatabase(this.dbName);
|
package/dist/types.d.ts
CHANGED
package/dist/utils.js
CHANGED
|
@@ -81,9 +81,23 @@ export function rebaseChanges(serverChanges, localChanges) {
|
|
|
81
81
|
export function deferred() {
|
|
82
82
|
let resolve;
|
|
83
83
|
let reject;
|
|
84
|
+
let _status = 'pending';
|
|
84
85
|
const promise = new Promise((_resolve, _reject) => {
|
|
85
|
-
resolve =
|
|
86
|
-
|
|
86
|
+
resolve = (value) => {
|
|
87
|
+
_resolve(value);
|
|
88
|
+
_status = 'fulfilled';
|
|
89
|
+
};
|
|
90
|
+
reject = (reason) => {
|
|
91
|
+
_reject(reason);
|
|
92
|
+
_status = 'rejected';
|
|
93
|
+
};
|
|
87
94
|
});
|
|
88
|
-
return {
|
|
95
|
+
return {
|
|
96
|
+
promise,
|
|
97
|
+
resolve,
|
|
98
|
+
reject,
|
|
99
|
+
get status() {
|
|
100
|
+
return _status;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
89
103
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dabble/patches",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Immutable JSON Patch implementation based on RFC 6902 supporting operational transformation and last-writer-wins",
|
|
5
5
|
"author": "Jacob Wright <jacwright@gmail.com>",
|
|
6
6
|
"bugs": {
|