@mengine/storage 0.1.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/dist/auto-reconnect-0HwaV1v8.js +250 -0
- package/dist/base-B0LSXWTK.d.ts +191 -0
- package/dist/idb.d.ts +119 -0
- package/dist/idb.js +225 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +27 -0
- package/dist/pg.d.ts +671 -0
- package/dist/pg.js +241 -0
- package/package.json +58 -0
package/dist/idb.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { n as BaseDocStorage, t as AutoReconnectConnection } from "./auto-reconnect-0HwaV1v8.js";
|
|
2
|
+
import { Task } from "@mengine/utils";
|
|
3
|
+
import { openDB } from "idb";
|
|
4
|
+
//#region src/impls/idb/schema.ts
|
|
5
|
+
const migrate = (db, oldVersion, _newVersion, trx) => {
|
|
6
|
+
if (!oldVersion) oldVersion = 0;
|
|
7
|
+
for (let i = oldVersion; i < migrations.length; i++) migrations[i](db, trx);
|
|
8
|
+
};
|
|
9
|
+
const init = (db) => {
|
|
10
|
+
db.createObjectStore("snapshots", {
|
|
11
|
+
keyPath: "docId",
|
|
12
|
+
autoIncrement: false
|
|
13
|
+
});
|
|
14
|
+
db.createObjectStore("updates", {
|
|
15
|
+
keyPath: ["docId", "seq"],
|
|
16
|
+
autoIncrement: false
|
|
17
|
+
}).createIndex("docId", "docId", { unique: false });
|
|
18
|
+
db.createObjectStore("sequences", {
|
|
19
|
+
keyPath: "docId",
|
|
20
|
+
autoIncrement: false
|
|
21
|
+
});
|
|
22
|
+
db.createObjectStore("locks", {
|
|
23
|
+
keyPath: "key",
|
|
24
|
+
autoIncrement: false
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
const migrations = [init];
|
|
28
|
+
const migrator = {
|
|
29
|
+
version: migrations.length,
|
|
30
|
+
migrate
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/impls/idb/connection.ts
|
|
34
|
+
var IDBConnection = class extends AutoReconnectConnection {
|
|
35
|
+
dbName;
|
|
36
|
+
constructor(dbName) {
|
|
37
|
+
super();
|
|
38
|
+
this.dbName = dbName;
|
|
39
|
+
}
|
|
40
|
+
get shareId() {
|
|
41
|
+
return `idb(${migrator.version}):${this.dbName}`;
|
|
42
|
+
}
|
|
43
|
+
async doConnect() {
|
|
44
|
+
const db = await openDB(this.dbName, migrator.version, { upgrade: migrator.migrate });
|
|
45
|
+
db.addEventListener("versionchange", this.handleVersionChange);
|
|
46
|
+
return {
|
|
47
|
+
db,
|
|
48
|
+
channel: new BroadcastChannel("idb:" + this.dbName)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
doDisconnect(db) {
|
|
52
|
+
db.db.removeEventListener("versionchange", this.handleVersionChange);
|
|
53
|
+
db.channel.close();
|
|
54
|
+
db.db.close();
|
|
55
|
+
}
|
|
56
|
+
handleVersionChange = (e) => {
|
|
57
|
+
if (e.newVersion !== migrator.version) this.error = /* @__PURE__ */ new Error("Database version mismatch, expected " + migrator.version + " but got " + e.newVersion);
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/impls/idb/lock.ts
|
|
62
|
+
var IndexedDBLocker = class {
|
|
63
|
+
dbConnection;
|
|
64
|
+
get db() {
|
|
65
|
+
return this.dbConnection.inner.db;
|
|
66
|
+
}
|
|
67
|
+
get channel() {
|
|
68
|
+
return this.dbConnection.inner.channel;
|
|
69
|
+
}
|
|
70
|
+
constructor(dbConnection) {
|
|
71
|
+
this.dbConnection = dbConnection;
|
|
72
|
+
}
|
|
73
|
+
async lock(domain, resource) {
|
|
74
|
+
const key = `${domain}:${resource}`;
|
|
75
|
+
return Task.spawn(async () => {
|
|
76
|
+
while (true) {
|
|
77
|
+
const trx = this.db.transaction("locks", "readwrite");
|
|
78
|
+
const record = await trx.store.get(key);
|
|
79
|
+
if (record) {
|
|
80
|
+
if (record.lockedAt.getTime() < Date.now() - 3e3) {
|
|
81
|
+
await trx.store.delete(key);
|
|
82
|
+
trx.commit();
|
|
83
|
+
await trx.done;
|
|
84
|
+
} else trx.abort();
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
await trx.store.put({
|
|
88
|
+
key,
|
|
89
|
+
lockedAt: /* @__PURE__ */ new Date()
|
|
90
|
+
});
|
|
91
|
+
trx.commit();
|
|
92
|
+
await trx.done;
|
|
93
|
+
return { [Symbol.asyncDispose]: async () => {
|
|
94
|
+
const trx = this.db.transaction("locks", "readwrite");
|
|
95
|
+
await trx.store.delete(key);
|
|
96
|
+
trx.commit();
|
|
97
|
+
await trx.done;
|
|
98
|
+
} };
|
|
99
|
+
}
|
|
100
|
+
}).timeout(3e3);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/impls/idb/index.ts
|
|
105
|
+
var IndexedDBDocStorage = class extends BaseDocStorage {
|
|
106
|
+
static identifier = "IndexedDBDocStorage";
|
|
107
|
+
connection = new IDBConnection(this.options.dbName);
|
|
108
|
+
locker = new IndexedDBLocker(this.connection);
|
|
109
|
+
get db() {
|
|
110
|
+
return this.connection.inner.db;
|
|
111
|
+
}
|
|
112
|
+
get channel() {
|
|
113
|
+
return this.connection.inner.channel;
|
|
114
|
+
}
|
|
115
|
+
async pushDocUpdate(update, origin) {
|
|
116
|
+
const trx = this.db.transaction([
|
|
117
|
+
"snapshots",
|
|
118
|
+
"updates",
|
|
119
|
+
"sequences"
|
|
120
|
+
], "readwrite");
|
|
121
|
+
if (!await trx.objectStore("snapshots").get(update.docId)) {
|
|
122
|
+
await trx.objectStore("snapshots").put({
|
|
123
|
+
docId: update.docId,
|
|
124
|
+
data: update.data,
|
|
125
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
126
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
let seq = await trx.objectStore("sequences").get(update.docId);
|
|
131
|
+
if (!seq) seq = {
|
|
132
|
+
docId: update.docId,
|
|
133
|
+
seq: 0
|
|
134
|
+
};
|
|
135
|
+
seq.seq++;
|
|
136
|
+
await trx.objectStore("sequences").put(seq);
|
|
137
|
+
await trx.objectStore("updates").put({
|
|
138
|
+
docId: update.docId,
|
|
139
|
+
seq: seq.seq,
|
|
140
|
+
data: update.data,
|
|
141
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
142
|
+
});
|
|
143
|
+
this.event.emit("update", {
|
|
144
|
+
update: {
|
|
145
|
+
docId: update.docId,
|
|
146
|
+
data: update.data
|
|
147
|
+
},
|
|
148
|
+
origin
|
|
149
|
+
});
|
|
150
|
+
this.channel.postMessage({
|
|
151
|
+
type: "update",
|
|
152
|
+
update: {
|
|
153
|
+
docId: update.docId,
|
|
154
|
+
data: update.data
|
|
155
|
+
},
|
|
156
|
+
origin
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
async getDocSnapshot(docId) {
|
|
160
|
+
return await this.db.transaction("snapshots", "readonly").store.get(docId) ?? null;
|
|
161
|
+
}
|
|
162
|
+
async deleteDoc(docId) {
|
|
163
|
+
{
|
|
164
|
+
const trx = this.db.transaction(["updates"], "readwrite");
|
|
165
|
+
const iter = trx.objectStore("updates").index("docId").iterate(IDBKeyRange.only(docId));
|
|
166
|
+
for await (const { primaryKey } of iter) await trx.objectStore("updates").delete(primaryKey);
|
|
167
|
+
trx.commit();
|
|
168
|
+
await trx.done;
|
|
169
|
+
}
|
|
170
|
+
{
|
|
171
|
+
const trx = this.db.transaction(["snapshots"], "readwrite");
|
|
172
|
+
await trx.objectStore("snapshots").delete(docId);
|
|
173
|
+
trx.commit();
|
|
174
|
+
}
|
|
175
|
+
await this.db.delete("locks", docId);
|
|
176
|
+
await this.db.delete("sequences", docId);
|
|
177
|
+
}
|
|
178
|
+
async setDocSnapshot(snapshot) {
|
|
179
|
+
const trx = this.db.transaction("snapshots", "readwrite");
|
|
180
|
+
const record = await trx.store.get(snapshot.docId);
|
|
181
|
+
if (!record || record.updatedAt <= snapshot.updatedAt) await trx.store.put({
|
|
182
|
+
docId: snapshot.docId,
|
|
183
|
+
data: snapshot.data,
|
|
184
|
+
createdAt: record?.createdAt ?? snapshot.createdAt,
|
|
185
|
+
updatedAt: snapshot.updatedAt
|
|
186
|
+
});
|
|
187
|
+
trx.commit();
|
|
188
|
+
await trx.done;
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
async getDocUpdates(docId) {
|
|
192
|
+
return (await this.db.transaction("updates", "readonly").store.index("docId").getAll(docId)).map((update) => ({
|
|
193
|
+
docId,
|
|
194
|
+
seq: update.seq,
|
|
195
|
+
data: update.data,
|
|
196
|
+
createdAt: update.createdAt
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
async markUpdatesMerged(docId, updates) {
|
|
200
|
+
const trx = this.db.transaction("updates", "readwrite");
|
|
201
|
+
await Promise.all(updates.map((update) => trx.store.delete([docId, update.seq])));
|
|
202
|
+
trx.commit();
|
|
203
|
+
await trx.done;
|
|
204
|
+
return updates.length;
|
|
205
|
+
}
|
|
206
|
+
docUpdateListener = 0;
|
|
207
|
+
subscribeDocUpdate(callback) {
|
|
208
|
+
if (this.docUpdateListener === 0) this.channel.addEventListener("message", this.handleChannelMessage);
|
|
209
|
+
this.docUpdateListener++;
|
|
210
|
+
const dispose = super.subscribeDocUpdate(callback);
|
|
211
|
+
return () => {
|
|
212
|
+
dispose();
|
|
213
|
+
this.docUpdateListener--;
|
|
214
|
+
if (this.docUpdateListener === 0) this.channel.removeEventListener("message", this.handleChannelMessage);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
handleChannelMessage = (event) => {
|
|
218
|
+
if (event.data.type === "update") this.event.emit("update", {
|
|
219
|
+
update: event.data.update,
|
|
220
|
+
origin: event.data.origin
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
//#endregion
|
|
225
|
+
export { IndexedDBDocStorage };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as DocStorage, c as DocUpdateRecord, d as Connection, f as ConnectionStatus, i as DocSnapshotRecord, l as share, o as DocStorageOptions, p as DummyConnection, r as DocDiff, s as DocUpdate, t as BaseDocStorage, u as AutoReconnectConnection } from "./base-B0LSXWTK.js";
|
|
2
|
+
export { AutoReconnectConnection, BaseDocStorage, Connection, ConnectionStatus, DocDiff, DocSnapshotRecord, DocStorage, DocStorageOptions, DocUpdate, DocUpdateRecord, DummyConnection, share };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { n as BaseDocStorage, t as AutoReconnectConnection } from "./auto-reconnect-0HwaV1v8.js";
|
|
2
|
+
import { Task } from "@mengine/utils";
|
|
3
|
+
//#region src/connection/api.ts
|
|
4
|
+
var DummyConnection = class {
|
|
5
|
+
status = "connected";
|
|
6
|
+
inner;
|
|
7
|
+
connect() {}
|
|
8
|
+
disconnect() {}
|
|
9
|
+
waitForConnected() {
|
|
10
|
+
return Task.resolve();
|
|
11
|
+
}
|
|
12
|
+
onStatusChanged(_cb) {
|
|
13
|
+
return () => {};
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/connection/shared-connection.ts
|
|
18
|
+
const CONNECTIONS = /* @__PURE__ */ new Map();
|
|
19
|
+
function share(conn) {
|
|
20
|
+
if (!conn.shareId) throw new Error(`Connection ${conn.constructor.name} is not shareable.\nIf you want to make it shareable, please override [shareId].`);
|
|
21
|
+
const existing = CONNECTIONS.get(conn.shareId);
|
|
22
|
+
if (existing) return existing;
|
|
23
|
+
CONNECTIONS.set(conn.shareId, conn);
|
|
24
|
+
return conn;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { AutoReconnectConnection, BaseDocStorage, DummyConnection, share };
|