@aiao/rxdb-adapter-electron 0.0.0-stage → 0.0.26
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/LICENSE +21 -0
- package/README.md +293 -2
- package/dist/RxDBAdapterElectron.d.ts +35 -0
- package/dist/RxDBAdapterElectron.d.ts.map +1 -0
- package/dist/electron-adapter.interface.d.ts +23 -0
- package/dist/electron-adapter.interface.d.ts.map +1 -0
- package/dist/electron-file-host.d.ts +52 -0
- package/dist/electron-file-host.d.ts.map +1 -0
- package/dist/electron-sqlite-host.d.ts +65 -0
- package/dist/electron-sqlite-host.d.ts.map +1 -0
- package/dist/host.d.ts +19 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +762 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/node-sqlite-engine.d.ts +87 -0
- package/dist/node-sqlite-engine.d.ts.map +1 -0
- package/dist/pglite/RxDBAdapterElectronPGlite.d.ts +44 -0
- package/dist/pglite/RxDBAdapterElectronPGlite.d.ts.map +1 -0
- package/dist/pglite/desktop-pglite-client.d.ts +118 -0
- package/dist/pglite/desktop-pglite-client.d.ts.map +1 -0
- package/dist/pglite/pglite-adapter.interface.d.ts +56 -0
- package/dist/pglite/pglite-adapter.interface.d.ts.map +1 -0
- package/dist/pglite-host/electron-pglite-host.d.ts +114 -0
- package/dist/pglite-host/electron-pglite-host.d.ts.map +1 -0
- package/dist/pglite-host.d.ts +17 -0
- package/dist/pglite-host.d.ts.map +1 -0
- package/dist/pglite-host.js +215 -0
- package/dist/pglite.d.ts +20 -0
- package/dist/pglite.d.ts.map +1 -0
- package/dist/pglite.js +217 -0
- package/dist/sqlite-script.d.ts +26 -0
- package/dist/sqlite-script.d.ts.map +1 -0
- package/package.json +91 -3
- package/src/RxDBAdapterElectron.ts +66 -0
- package/src/electron-adapter.interface.ts +25 -0
- package/src/electron-file-host.ts +758 -0
- package/src/electron-sqlite-host.ts +298 -0
- package/src/host.ts +63 -0
- package/src/index.ts +70 -0
- package/src/node-sqlite-engine.ts +612 -0
- package/src/pglite/RxDBAdapterElectronPGlite.ts +77 -0
- package/src/pglite/desktop-pglite-client.ts +520 -0
- package/src/pglite/pglite-adapter.interface.ts +64 -0
- package/src/pglite-host/electron-pglite-host.ts +580 -0
- package/src/pglite-host.ts +43 -0
- package/src/pglite.ts +42 -0
- package/src/sqlite-script.ts +134 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { DESKTOP_PGLITE_DEFAULT_BEGIN_TIMEOUT_MS as e, DESKTOP_PGLITE_MAX_BEGIN_TIMEOUT_MS as t, DESKTOP_PGLITE_PROTOCOL_VERSION as n, DESKTOP_PGLITE_PROTOCOL_VERSION as r, RxDBAdapterDesktopError as i, RxDBAdapterDesktopError as a, assertValidDesktopDatabaseName as o, isDesktopPgliteRequestKind as s, parseDesktopPgliteRequest as c, parseDesktopPgliteRequest as l } from "@aiao/rxdb-adapter-sqlite-core/desktop-host";
|
|
2
|
+
import { randomUUID as u } from "node:crypto";
|
|
3
|
+
//#region src/pglite-host/electron-pglite-host.ts
|
|
4
|
+
var d = "desktop-pglite://app-scope", f = Object.freeze([
|
|
5
|
+
"rxdb_change_notify",
|
|
6
|
+
"rxdb_branch_notify",
|
|
7
|
+
"rxdb_migration_notify"
|
|
8
|
+
]), p = Symbol("pg.begin timed out"), m = async (e, t) => {
|
|
9
|
+
let n;
|
|
10
|
+
try {
|
|
11
|
+
return await Promise.race([e, new Promise((e) => {
|
|
12
|
+
n = setTimeout(() => e(p), t);
|
|
13
|
+
})]);
|
|
14
|
+
} finally {
|
|
15
|
+
clearTimeout(n);
|
|
16
|
+
}
|
|
17
|
+
}, h = (e) => ({
|
|
18
|
+
rows: e.rows,
|
|
19
|
+
fields: e.fields.map((e) => ({
|
|
20
|
+
name: e.name,
|
|
21
|
+
dataTypeID: e.dataTypeID
|
|
22
|
+
})),
|
|
23
|
+
affectedRows: e.affectedRows
|
|
24
|
+
}), g = (e) => e instanceof a ? {
|
|
25
|
+
kind: "error",
|
|
26
|
+
code: e.code,
|
|
27
|
+
message: e.detail
|
|
28
|
+
} : {
|
|
29
|
+
kind: "error",
|
|
30
|
+
code: "host_internal_error",
|
|
31
|
+
message: e instanceof Error ? e.message : String(e)
|
|
32
|
+
}, _ = async (e) => {
|
|
33
|
+
try {
|
|
34
|
+
return await e();
|
|
35
|
+
} catch (e) {
|
|
36
|
+
throw e instanceof a ? e : new a("statement_failed", e instanceof Error ? e.message : String(e), { cause: e });
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function v(e) {
|
|
40
|
+
let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map(), o = (t, n, r) => {
|
|
41
|
+
try {
|
|
42
|
+
e.postNotify({
|
|
43
|
+
kind: "pg.notify",
|
|
44
|
+
sessionId: t,
|
|
45
|
+
channel: n,
|
|
46
|
+
payload: r
|
|
47
|
+
});
|
|
48
|
+
} catch (t) {
|
|
49
|
+
e.onDeliveryError?.(t);
|
|
50
|
+
}
|
|
51
|
+
}, s = (t) => {
|
|
52
|
+
let n = /* @__PURE__ */ new Set();
|
|
53
|
+
return {
|
|
54
|
+
sessions: n,
|
|
55
|
+
ready: (async () => {
|
|
56
|
+
let r = await e.createRuntime(t);
|
|
57
|
+
for (let e of f) await r.listen(e, (t) => {
|
|
58
|
+
for (let r of n) o(r, e, t);
|
|
59
|
+
});
|
|
60
|
+
return r;
|
|
61
|
+
})()
|
|
62
|
+
};
|
|
63
|
+
}, c = (e, t) => {
|
|
64
|
+
let r = n.get(e);
|
|
65
|
+
if (!r) throw new a("session_closed", `session ${e} is not open on this host`);
|
|
66
|
+
if (r.owner !== t) throw new a("permission_denied", `session ${e} belongs to another window`);
|
|
67
|
+
return r;
|
|
68
|
+
}, v = async (e) => {
|
|
69
|
+
let n = t.get(e.dataDirectoryName);
|
|
70
|
+
if (!n) throw new a("host_internal_error", `session references a released instance for ${e.dataDirectoryName}`);
|
|
71
|
+
return n.ready;
|
|
72
|
+
}, y = (e, t) => {
|
|
73
|
+
let n = i.get(e);
|
|
74
|
+
if (!n || n.sessionId !== t) throw new a("transaction_not_found", `transaction ${e} is unknown, already finished, or not owned by session ${t}`);
|
|
75
|
+
return n;
|
|
76
|
+
}, b = async (e, t) => {
|
|
77
|
+
let n = i.get(e);
|
|
78
|
+
if (!n) return;
|
|
79
|
+
i.delete(e), t ? n.settle.reject(t) : n.settle.resolve();
|
|
80
|
+
let r = await n.finished.then(() => void 0, (e) => e);
|
|
81
|
+
if (!(t || r === void 0)) return r instanceof a ? r : new a("statement_failed", r instanceof Error ? r.message : String(r), { cause: r });
|
|
82
|
+
}, x = async (e, i) => {
|
|
83
|
+
let { dataDirectoryName: o } = e.storage, c = t.get(o) ?? s(o);
|
|
84
|
+
t.set(o, c);
|
|
85
|
+
try {
|
|
86
|
+
await c.ready;
|
|
87
|
+
} catch (e) {
|
|
88
|
+
throw c.sessions.size === 0 && t.delete(o), new a("open_failed", `the application could not open a PGlite runtime for ${o}`, { cause: e });
|
|
89
|
+
}
|
|
90
|
+
let l = u();
|
|
91
|
+
return c.sessions.add(l), n.set(l, {
|
|
92
|
+
dataDirectoryName: o,
|
|
93
|
+
owner: i
|
|
94
|
+
}), {
|
|
95
|
+
kind: "pg.open",
|
|
96
|
+
result: {
|
|
97
|
+
sessionId: l,
|
|
98
|
+
resolvedLocation: `${d}/${o}`,
|
|
99
|
+
protocolVersion: r
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}, S = async (e) => {
|
|
103
|
+
let r = n.get(e);
|
|
104
|
+
if (!r) return;
|
|
105
|
+
n.delete(e);
|
|
106
|
+
let i = t.get(r.dataDirectoryName);
|
|
107
|
+
i && (i.sessions.delete(e), !(i.sessions.size > 0) && (t.delete(r.dataDirectoryName), await (await i.ready.catch(() => void 0))?.close()));
|
|
108
|
+
}, C = async (e, t) => {
|
|
109
|
+
let n = [...i.entries()].filter(([, t]) => t.sessionId === e).map(([e]) => e);
|
|
110
|
+
for (let e of n) await b(e, new a("transaction_not_found", t));
|
|
111
|
+
return n.length;
|
|
112
|
+
}, w = async (e, t) => {
|
|
113
|
+
let n = await v(c(e.sessionId, t)), r, o = new Promise((e, t) => {
|
|
114
|
+
r = {
|
|
115
|
+
resolve: e,
|
|
116
|
+
reject: t
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
o.catch(() => void 0);
|
|
120
|
+
let s, l = new Promise((e) => {
|
|
121
|
+
s = e;
|
|
122
|
+
}), d = n.transaction(async (e) => {
|
|
123
|
+
s(e), await o;
|
|
124
|
+
});
|
|
125
|
+
d.catch(() => void 0);
|
|
126
|
+
let f = await m(l, e.timeout);
|
|
127
|
+
if (f === p) throw r.reject(new a("transaction_unavailable", "the begin that opened this transaction timed out")), new a("transaction_unavailable", `waited ${e.timeout}ms for the PGlite connection but another transaction still holds it`);
|
|
128
|
+
let h = u();
|
|
129
|
+
return i.set(h, {
|
|
130
|
+
sessionId: e.sessionId,
|
|
131
|
+
tx: f,
|
|
132
|
+
settle: r,
|
|
133
|
+
finished: d
|
|
134
|
+
}), {
|
|
135
|
+
kind: "pg.begin",
|
|
136
|
+
result: { transactionId: h }
|
|
137
|
+
};
|
|
138
|
+
}, T = async (e, t) => {
|
|
139
|
+
let n = c(e.sessionId, t), r = [...e.params];
|
|
140
|
+
if (e.transactionId !== void 0) {
|
|
141
|
+
let t = y(e.transactionId, e.sessionId);
|
|
142
|
+
return {
|
|
143
|
+
kind: "pg.query",
|
|
144
|
+
result: h(await _(() => t.tx.query(e.sql, r)))
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
let i = await v(n);
|
|
148
|
+
return {
|
|
149
|
+
kind: "pg.query",
|
|
150
|
+
result: h(await _(() => i.query(e.sql, r)))
|
|
151
|
+
};
|
|
152
|
+
}, E = async (e, t) => {
|
|
153
|
+
let n = c(e.sessionId, t), r = e.transactionId === void 0 ? await v(n) : y(e.transactionId, e.sessionId).tx;
|
|
154
|
+
return {
|
|
155
|
+
kind: "pg.exec",
|
|
156
|
+
result: (await _(() => r.exec(e.sql))).map(h)
|
|
157
|
+
};
|
|
158
|
+
}, D = async (e, t) => {
|
|
159
|
+
c(e.sessionId, t), y(e.transactionId, e.sessionId);
|
|
160
|
+
let n = e.kind === "pg.rollback" ? new a("write_aborted", "rollback requested") : null, r = await b(e.transactionId, n);
|
|
161
|
+
if (r) throw r;
|
|
162
|
+
return { kind: e.kind };
|
|
163
|
+
}, O = async (e, t) => {
|
|
164
|
+
let n = await v(c(e.sessionId, t)), r = (await _(() => n.query("SELECT version() AS version"))).rows[0]?.version;
|
|
165
|
+
if (typeof r != "string") throw new a("host_internal_error", "PostgreSQL did not report a version string");
|
|
166
|
+
return {
|
|
167
|
+
kind: "pg.version",
|
|
168
|
+
result: r
|
|
169
|
+
};
|
|
170
|
+
}, k = async (e, t) => (c(e.sessionId, t), await C(e.sessionId, `session ${e.sessionId} was closed`), await S(e.sessionId), { kind: "pg.close" }), A = (e, t) => {
|
|
171
|
+
switch (e.kind) {
|
|
172
|
+
case "pg.handshake": return Promise.resolve({
|
|
173
|
+
kind: "pg.handshake",
|
|
174
|
+
result: { protocolVersion: r }
|
|
175
|
+
});
|
|
176
|
+
case "pg.open": return x(e, t);
|
|
177
|
+
case "pg.query": return T(e, t);
|
|
178
|
+
case "pg.exec": return E(e, t);
|
|
179
|
+
case "pg.begin": return w(e, t);
|
|
180
|
+
case "pg.commit":
|
|
181
|
+
case "pg.rollback": return D(e, t);
|
|
182
|
+
case "pg.version": return O(e, t);
|
|
183
|
+
case "pg.close": return k(e, t);
|
|
184
|
+
default: throw new a("protocol_violation", `unsupported pglite request kind: ${String(e.kind)}`);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
return {
|
|
188
|
+
handle: async (e, t) => {
|
|
189
|
+
try {
|
|
190
|
+
return await A(l(e), t);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
return g(e);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
get openSessionCount() {
|
|
196
|
+
return n.size;
|
|
197
|
+
},
|
|
198
|
+
get openInstanceCount() {
|
|
199
|
+
return t.size;
|
|
200
|
+
},
|
|
201
|
+
get openTransactionCount() {
|
|
202
|
+
return i.size;
|
|
203
|
+
},
|
|
204
|
+
releaseOwner: async (e) => {
|
|
205
|
+
let t = [...n.entries()].filter(([, t]) => t.owner === e).map(([e]) => e), r = 0;
|
|
206
|
+
for (let n of t) r += await C(n, `owner ${e} is gone`), await S(n);
|
|
207
|
+
return r;
|
|
208
|
+
},
|
|
209
|
+
closeAll: async () => {
|
|
210
|
+
for (let e of [...n.keys()]) await C(e, "the host is shutting down"), await S(e);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
export { e as DESKTOP_PGLITE_DEFAULT_BEGIN_TIMEOUT_MS, t as DESKTOP_PGLITE_MAX_BEGIN_TIMEOUT_MS, n as DESKTOP_PGLITE_PROTOCOL_VERSION, f as DESKTOP_PGLITE_WATCH_CHANNELS, i as RxDBAdapterDesktopError, o as assertValidDesktopDatabaseName, v as createElectronPgliteHost, s as isDesktopPgliteRequestKind, c as parseDesktopPgliteRequest };
|
package/dist/pglite.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@aiao/rxdb-adapter-electron` 的 **PGlite renderer 侧**入口。
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* 与默认入口分开而不是并进去,理由是依赖:默认入口只要共享层,本入口却要
|
|
6
|
+
* `@aiao/rxdb-adapter-pglite` 这个可选 peer。合并的话,任何一个只用 SQLite 的应用
|
|
7
|
+
* 都会在 import 适配器时撞上「找不到 `@aiao/rxdb-adapter-pglite`」——而它压根没打算用 PGlite。
|
|
8
|
+
*
|
|
9
|
+
* 本入口**不引用 PostgreSQL 运行时**:WASM 实例活在主进程,renderer 这边只有一层协议
|
|
10
|
+
* 代理,从 `@electric-sql/pglite` 拿的只有类型和 `/template` 那个约 2 KB 的模板编译子路径,
|
|
11
|
+
* 因此 bundle 里不会多出几十兆的 PostgreSQL。特权侧请用
|
|
12
|
+
* `@aiao/rxdb-adapter-electron/pglite-host`。
|
|
13
|
+
*
|
|
14
|
+
* @module @aiao/rxdb-adapter-electron/pglite
|
|
15
|
+
*/
|
|
16
|
+
export { DEFAULT_DATA_DIRECTORY_SUFFIX as DESKTOP_DEFAULT_DATA_DIRECTORY_SUFFIX, ADAPTER_NAME as ELECTRON_PGLITE_ADAPTER_NAME, type ElectronPGliteOptions } from './pglite/pglite-adapter.interface.js';
|
|
17
|
+
export { RxDBAdapterElectronPGlite } from './pglite/RxDBAdapterElectronPGlite.js';
|
|
18
|
+
export { DesktopPGliteClient, type DesktopPGliteClientOptions } from './pglite/desktop-pglite-client.js';
|
|
19
|
+
export { DESKTOP_HOST_TRANSPORT_KEY, DESKTOP_PGLITE_DEFAULT_BEGIN_TIMEOUT_MS, DESKTOP_PGLITE_MAX_BEGIN_TIMEOUT_MS, DESKTOP_PGLITE_PROTOCOL_VERSION, RxDBAdapterDesktopError, resolveDesktopHostTransport, type DesktopHostTransport, type DesktopPgliteNotifyMessage, type DesktopPgliteQueryResult, type DesktopPgliteRequest, type DesktopPgliteResponse, type RxDBAdapterDesktopErrorCode } from '@aiao/rxdb-adapter-sqlite-core/desktop-host';
|
|
20
|
+
//# sourceMappingURL=pglite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pglite.d.ts","sourceRoot":"","sources":["../src/pglite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACL,6BAA6B,IAAI,qCAAqC,EACtE,YAAY,IAAI,4BAA4B,EAC5C,KAAK,qBAAqB,EAC3B,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;AAElF,OAAO,EAAE,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,MAAM,mCAAmC,CAAC;AAIzG,OAAO,EACL,0BAA0B,EAC1B,uCAAuC,EACvC,mCAAmC,EACnC,+BAA+B,EAC/B,uBAAuB,EACvB,2BAA2B,EAC3B,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,2BAA2B,EACjC,MAAM,6CAA6C,CAAC"}
|
package/dist/pglite.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { DESKTOP_HOST_TRANSPORT_KEY as e, DESKTOP_PGLITE_DEFAULT_BEGIN_TIMEOUT_MS as t, DESKTOP_PGLITE_MAX_BEGIN_TIMEOUT_MS as n, DESKTOP_PGLITE_PROTOCOL_VERSION as r, RxDBAdapterDesktopError as i, RxDBAdapterDesktopError as a, assertDesktopPgliteResponse as o, assertValidDesktopDatabaseName as s, parseDesktopPgliteHandshakeResult as c, parseDesktopPgliteNotifyMessage as l, parseDesktopPgliteOpenResult as u, resolveDesktopHostTransport as d, resolveDesktopHostTransport as f } from "@aiao/rxdb-adapter-sqlite-core/desktop-host";
|
|
2
|
+
import { DEFAULT_NOTIFY_BATCH_TIMEOUT_MS as p, PGliteNotificationBatcher as m, RxDBAdapterPGlite as h } from "@aiao/rxdb-adapter-pglite";
|
|
3
|
+
import { EventDispatcher as g } from "@aiao/utils";
|
|
4
|
+
import { query as _ } from "@electric-sql/pglite/template";
|
|
5
|
+
//#region src/pglite/pglite-adapter.interface.ts
|
|
6
|
+
var v = "pglite-electron", y = "-pgdata", b = (e) => ({
|
|
7
|
+
rows: e.rows,
|
|
8
|
+
fields: e.fields.map((e) => ({
|
|
9
|
+
name: e.name,
|
|
10
|
+
dataTypeID: e.dataTypeID
|
|
11
|
+
})),
|
|
12
|
+
affectedRows: e.affectedRows
|
|
13
|
+
}), x = (e) => {
|
|
14
|
+
if (e !== void 0) throw new a("protocol_violation", "the desktop PGlite protocol carries no QueryOptions; parsers, serializers, onNotice and blob cannot cross the process boundary");
|
|
15
|
+
}, S = (e) => {
|
|
16
|
+
if (e.store !== void 0 || e.dataDir !== void 0) throw new a("protocol_violation", "the desktop PGlite host owns storage; store and dataDir must not be set on the renderer side");
|
|
17
|
+
}, C = (e) => new a("protocol_violation", `${e}() cannot be proxied to the desktop PGlite host; use query() or exec() instead`), w = (e) => {
|
|
18
|
+
queueMicrotask(() => {
|
|
19
|
+
throw e;
|
|
20
|
+
});
|
|
21
|
+
}, T = class extends g {
|
|
22
|
+
#e;
|
|
23
|
+
#t;
|
|
24
|
+
#n = "";
|
|
25
|
+
#r;
|
|
26
|
+
#i;
|
|
27
|
+
#a;
|
|
28
|
+
#o = !1;
|
|
29
|
+
#s;
|
|
30
|
+
#c = Promise.resolve();
|
|
31
|
+
#l = Promise.resolve();
|
|
32
|
+
get sessionId() {
|
|
33
|
+
if (!this.#r) throw new a("session_closed", "the desktop PGlite client has not been initialised yet");
|
|
34
|
+
return this.#r;
|
|
35
|
+
}
|
|
36
|
+
get resolvedLocation() {
|
|
37
|
+
if (!this.#i) throw new a("session_closed", "the desktop PGlite client has not been initialised yet");
|
|
38
|
+
return this.#i;
|
|
39
|
+
}
|
|
40
|
+
get pendingNotificationCount() {
|
|
41
|
+
return this.#t.pendingCount;
|
|
42
|
+
}
|
|
43
|
+
constructor(e) {
|
|
44
|
+
super(), this.#e = e, this.#t = new m({
|
|
45
|
+
resolveDbName: () => this.#n,
|
|
46
|
+
emit: (e) => this.dispatchEvent(e.type, e),
|
|
47
|
+
batchTimeout: e.batchTimeout ?? p,
|
|
48
|
+
onParseError: (e) => console.error("Failed to parse desktop NOTIFY payload:", e)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async init(e, t) {
|
|
52
|
+
if (S(t), this.#r) throw new a("protocol_violation", `the desktop PGlite client is already bound to session ${this.#r}`);
|
|
53
|
+
this.#n = e;
|
|
54
|
+
let n = o("pg.handshake", await this.#e.transport.request({ kind: "pg.handshake" }));
|
|
55
|
+
c(n.result);
|
|
56
|
+
let r = o("pg.open", await this.#e.transport.request({
|
|
57
|
+
kind: "pg.open",
|
|
58
|
+
storage: {
|
|
59
|
+
engine: "pglite",
|
|
60
|
+
dataDirectoryName: this.#e.dataDirectoryName
|
|
61
|
+
}
|
|
62
|
+
})), i = await this.#u(r.result);
|
|
63
|
+
this.#r = i.sessionId, this.#i = i.resolvedLocation, this.#a = this.#e.transport.subscribe((e) => this.#y(e)), await this.#d();
|
|
64
|
+
}
|
|
65
|
+
async query(e, t, n) {
|
|
66
|
+
return this.#v(), x(n), this.#g(async () => this.#m(void 0, e, t));
|
|
67
|
+
}
|
|
68
|
+
async sql(e, ...t) {
|
|
69
|
+
this.#v();
|
|
70
|
+
let n = _(e, ...t);
|
|
71
|
+
return this.#g(async () => this.#m(void 0, n.query, n.params));
|
|
72
|
+
}
|
|
73
|
+
async exec(e, t) {
|
|
74
|
+
return this.#v(), x(t), this.#g(async () => this.#h(void 0, e));
|
|
75
|
+
}
|
|
76
|
+
async transaction(e) {
|
|
77
|
+
return this.#v(), this.#g(() => this.#p(e));
|
|
78
|
+
}
|
|
79
|
+
async version() {
|
|
80
|
+
return this.#v(), this.#g(async () => o("pg.version", await this.#_({
|
|
81
|
+
kind: "pg.version",
|
|
82
|
+
sessionId: this.sessionId
|
|
83
|
+
})).result);
|
|
84
|
+
}
|
|
85
|
+
async disconnect() {
|
|
86
|
+
this.#s ??= this.#f(!0), await this.#s;
|
|
87
|
+
}
|
|
88
|
+
async forceClose() {
|
|
89
|
+
this.#s ??= this.#f(!1), await this.#s;
|
|
90
|
+
}
|
|
91
|
+
async #u(e) {
|
|
92
|
+
try {
|
|
93
|
+
return u(e);
|
|
94
|
+
} catch (t) {
|
|
95
|
+
let n = (typeof e == "object" && e ? e : void 0)?.sessionId;
|
|
96
|
+
throw typeof n == "string" && await this.#e.transport.request({
|
|
97
|
+
kind: "pg.close",
|
|
98
|
+
sessionId: n
|
|
99
|
+
}).catch(() => void 0), t;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async #d() {
|
|
103
|
+
try {
|
|
104
|
+
await this.#e.transport.subscriptionReady?.();
|
|
105
|
+
} catch (e) {
|
|
106
|
+
let t = this.#r;
|
|
107
|
+
throw this.#o = !0, this.#a?.(), await this.#_({
|
|
108
|
+
kind: "pg.close",
|
|
109
|
+
sessionId: t
|
|
110
|
+
}).catch(() => void 0), new a("host_unavailable", `desktop PGlite session ${String(t)} could not subscribe to host notifications`, { cause: e });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async #f(e) {
|
|
114
|
+
e && await this.#l.catch(() => void 0), this.#o = !0, e && await this.#c.catch(() => void 0);
|
|
115
|
+
let t = this.#r;
|
|
116
|
+
this.#a?.(), this.#t.clear(), this.removeAllEventListeners(), t && o("pg.close", await this.#e.transport.request({
|
|
117
|
+
kind: "pg.close",
|
|
118
|
+
sessionId: t
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
async #p(e) {
|
|
122
|
+
let t = o("pg.begin", await this.#_({
|
|
123
|
+
kind: "pg.begin",
|
|
124
|
+
sessionId: this.sessionId,
|
|
125
|
+
...this.#e.beginTimeout === void 0 ? {} : { timeout: this.#e.beginTimeout }
|
|
126
|
+
})).result.transactionId, n = !1, r = async (e) => {
|
|
127
|
+
n = !0, o(e, await this.#_({
|
|
128
|
+
kind: e,
|
|
129
|
+
sessionId: this.sessionId,
|
|
130
|
+
transactionId: t
|
|
131
|
+
}));
|
|
132
|
+
}, i = () => {
|
|
133
|
+
if (n) throw new a("transaction_not_found", `transaction ${t} is already closed`);
|
|
134
|
+
}, s = {
|
|
135
|
+
query: async (e, n, r) => (i(), x(r), this.#m(t, e, n)),
|
|
136
|
+
exec: async (e, n) => (i(), x(n), this.#h(t, e)),
|
|
137
|
+
rollback: async () => {
|
|
138
|
+
i(), await r("pg.rollback");
|
|
139
|
+
},
|
|
140
|
+
sql: async (e, ...n) => {
|
|
141
|
+
i();
|
|
142
|
+
let r = _(e, ...n);
|
|
143
|
+
return this.#m(t, r.query, r.params);
|
|
144
|
+
},
|
|
145
|
+
listen: () => {
|
|
146
|
+
throw C("listen");
|
|
147
|
+
},
|
|
148
|
+
get closed() {
|
|
149
|
+
return n;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
try {
|
|
153
|
+
let t = await e(s);
|
|
154
|
+
return n || await r("pg.commit"), t;
|
|
155
|
+
} catch (e) {
|
|
156
|
+
throw n || await r("pg.rollback").catch(() => void 0), e;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async #m(e, t, n) {
|
|
160
|
+
let r = {
|
|
161
|
+
kind: "pg.query",
|
|
162
|
+
sessionId: this.sessionId,
|
|
163
|
+
sql: t,
|
|
164
|
+
params: n ?? [],
|
|
165
|
+
...e === void 0 ? {} : { transactionId: e }
|
|
166
|
+
};
|
|
167
|
+
return b(o("pg.query", await this.#_(r)).result);
|
|
168
|
+
}
|
|
169
|
+
async #h(e, t) {
|
|
170
|
+
let n = {
|
|
171
|
+
kind: "pg.exec",
|
|
172
|
+
sessionId: this.sessionId,
|
|
173
|
+
sql: t,
|
|
174
|
+
...e === void 0 ? {} : { transactionId: e }
|
|
175
|
+
};
|
|
176
|
+
return o("pg.exec", await this.#_(n)).result.map((e) => b(e));
|
|
177
|
+
}
|
|
178
|
+
#g(e) {
|
|
179
|
+
let t = this.#l.then(e);
|
|
180
|
+
return this.#l = t.then(void 0, () => void 0), t;
|
|
181
|
+
}
|
|
182
|
+
#_(e) {
|
|
183
|
+
let t = this.#c.then(() => this.#e.transport.request(e));
|
|
184
|
+
return this.#c = t.then(void 0, () => void 0), t;
|
|
185
|
+
}
|
|
186
|
+
#v() {
|
|
187
|
+
if (this.#o || !this.#r) throw new a("session_closed", `desktop PGlite session ${String(this.#r)} is not open`);
|
|
188
|
+
}
|
|
189
|
+
#y(e) {
|
|
190
|
+
if (!this.#o && typeof e == "object" && e && e.kind === "pg.notify" && e.sessionId === this.#r) try {
|
|
191
|
+
let t = l(e);
|
|
192
|
+
this.#t.accept(t.channel, t.payload);
|
|
193
|
+
} catch (e) {
|
|
194
|
+
w(e);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}, E = class extends h {
|
|
198
|
+
#e;
|
|
199
|
+
#t;
|
|
200
|
+
name = v;
|
|
201
|
+
get dataDirectoryName() {
|
|
202
|
+
return this.#e;
|
|
203
|
+
}
|
|
204
|
+
constructor(e, t = {}) {
|
|
205
|
+
super(e, {}), this.#t = t, this.#e = t.dataDirectoryName ?? `${e.config.dbName}-pgdata`, s(this.#e);
|
|
206
|
+
}
|
|
207
|
+
createClient() {
|
|
208
|
+
return new T({
|
|
209
|
+
transport: this.#t.transport ?? f(),
|
|
210
|
+
dataDirectoryName: this.#e,
|
|
211
|
+
...this.#t.beginTimeout === void 0 ? {} : { beginTimeout: this.#t.beginTimeout },
|
|
212
|
+
...this.#t.batchTimeout === void 0 ? {} : { batchTimeout: this.#t.batchTimeout }
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
//#endregion
|
|
217
|
+
export { y as DESKTOP_DEFAULT_DATA_DIRECTORY_SUFFIX, e as DESKTOP_HOST_TRANSPORT_KEY, t as DESKTOP_PGLITE_DEFAULT_BEGIN_TIMEOUT_MS, n as DESKTOP_PGLITE_MAX_BEGIN_TIMEOUT_MS, r as DESKTOP_PGLITE_PROTOCOL_VERSION, T as DesktopPGliteClient, v as ELECTRON_PGLITE_ADAPTER_NAME, i as RxDBAdapterDesktopError, E as RxDBAdapterElectronPGlite, d as resolveDesktopHostTransport };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL 脚本切分。
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* wasm 后端能拿到 `sqlite3_prepare_v3` 的 `pzTail`,由 SQLite 自己逐条吐出语句;`node:sqlite`
|
|
6
|
+
* 没有对应出口——`prepare()` 只编译第一条语句并**静默丢掉**后面所有语句,`exec()` 跑完整个脚本
|
|
7
|
+
* 却不返回任何结果集。多语句脚本里带 `RETURNING` 的那条因此会人间蒸发(分支切换正是这个形状)。
|
|
8
|
+
*
|
|
9
|
+
* 所以切分只能自己做。状态机逐字搬自 SQLite 的 `complete.c`(即 `sqlite3_complete()` 的实现),
|
|
10
|
+
* 与 SQLite 对「一条语句到哪里结束」的判断同源;自己发明规则迟早会在触发器体或字符串字面量上分叉。
|
|
11
|
+
*
|
|
12
|
+
* @module sqlite-script
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* 把一段可能含多条语句的 SQL 脚本切成单条语句。
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* 切片保留原文(含语句间的空白与注释),因此把结果按顺序拼回去与原串等价,
|
|
19
|
+
* 报错信息里的 SQL 也仍是调用方写的那一份。只含空白或注释的片段不会成为语句——
|
|
20
|
+
* 它们 prepare 出来是空语句,`node:sqlite` 会直接抛错。
|
|
21
|
+
*
|
|
22
|
+
* @param sql - 一条或多条 SQL 组成的脚本
|
|
23
|
+
* @returns 按出现顺序排列的语句原文;脚本里没有实质语句时为空数组
|
|
24
|
+
*/
|
|
25
|
+
export declare function splitSqliteScript(sql: string): string[];
|
|
26
|
+
//# sourceMappingURL=sqlite-script.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-script.d.ts","sourceRoot":"","sources":["../src/sqlite-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA0FH;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAoBvD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,94 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiao/rxdb-adapter-electron",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.26",
|
|
4
|
+
"description": "Desktop host adapter for local SQLite files in Electron and Tauri applications.",
|
|
5
|
+
"homepage": "https://rxdb.netlify.app/docs/adapters",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/aiao-io/rxdb/issues"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"aiao",
|
|
11
|
+
"rxdb",
|
|
12
|
+
"sqlite",
|
|
13
|
+
"electron",
|
|
14
|
+
"tauri",
|
|
15
|
+
"desktop"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/aiao-io/rxdb.git",
|
|
21
|
+
"directory": "packages/rxdb-adapter-electron"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"module": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"exports": {
|
|
29
|
+
"./package.json": "./package.json",
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./host": {
|
|
36
|
+
"@aiao/source": "./src/host.ts",
|
|
37
|
+
"types": "./dist/host.d.ts",
|
|
38
|
+
"import": "./dist/host.js",
|
|
39
|
+
"default": "./dist/host.js"
|
|
40
|
+
},
|
|
41
|
+
"./pglite": {
|
|
42
|
+
"@aiao/source": "./src/pglite.ts",
|
|
43
|
+
"types": "./dist/pglite.d.ts",
|
|
44
|
+
"import": "./dist/pglite.js",
|
|
45
|
+
"default": "./dist/pglite.js"
|
|
46
|
+
},
|
|
47
|
+
"./pglite-host": {
|
|
48
|
+
"@aiao/source": "./src/pglite-host.ts",
|
|
49
|
+
"types": "./dist/pglite-host.d.ts",
|
|
50
|
+
"import": "./dist/pglite-host.js",
|
|
51
|
+
"default": "./dist/pglite-host.js"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist",
|
|
56
|
+
"src",
|
|
57
|
+
"!src/**/*.spec.*",
|
|
58
|
+
"!src/**/*.test.*",
|
|
59
|
+
"!src/**/__tests__/**",
|
|
60
|
+
"!**/*.tsbuildinfo"
|
|
61
|
+
],
|
|
62
|
+
"publishConfig": {
|
|
63
|
+
"access": "public"
|
|
64
|
+
},
|
|
65
|
+
"nx": {
|
|
66
|
+
"name": "rxdb-adapter-electron",
|
|
67
|
+
"tags": [
|
|
68
|
+
"js-lib"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"@aiao/utils": "0.0.26",
|
|
73
|
+
"@aiao/rxdb-adapter-sqlite-core": "0.0.26",
|
|
74
|
+
"@aiao/rxdb": "0.0.26"
|
|
75
|
+
},
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"@electric-sql/pglite": "^0.5.8",
|
|
78
|
+
"@aiao/rxdb-adapter-pglite": "0.0.26"
|
|
79
|
+
},
|
|
80
|
+
"peerDependenciesMeta": {
|
|
81
|
+
"@aiao/rxdb-adapter-pglite": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"@electric-sql/pglite": {
|
|
85
|
+
"optional": true
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"devDependencies": {
|
|
89
|
+
"@electric-sql/pglite": "^0.5.8",
|
|
90
|
+
"@aiao/rxdb-adapter-pglite": "0.0.26",
|
|
91
|
+
"@aiao/rxdb-plugin-history": "0.0.26",
|
|
92
|
+
"@aiao/rxdb-test": "0.0.26"
|
|
93
|
+
}
|
|
6
94
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Electron 本地 SQLite 适配器。
|
|
3
|
+
*
|
|
4
|
+
* @module RxDBAdapterElectron
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RxDB } from '@aiao/rxdb';
|
|
8
|
+
import { RxDBAdapterSqliteBase } from '@aiao/rxdb-adapter-sqlite-core';
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_DATABASE_SUFFIX,
|
|
11
|
+
DesktopSqliteClient,
|
|
12
|
+
assertValidDesktopDatabaseName,
|
|
13
|
+
resolveDesktopHostTransport,
|
|
14
|
+
type DesktopOptions
|
|
15
|
+
} from '@aiao/rxdb-adapter-sqlite-core/desktop-host';
|
|
16
|
+
import { ADAPTER_NAME } from './electron-adapter.interface.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 把 RxDB 接到 Electron 应用私有目录里的真实 SQLite 文件上。
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* 数据落在主进程的 `node:sqlite` 连接里,renderer 只通过一条窄传输层发请求,
|
|
23
|
+
* 因此它既拿不到文件系统句柄,也拿不到物理路径(AC#3)。
|
|
24
|
+
*
|
|
25
|
+
* 适配器本身没有自己的查询实现:它复用 {@link RxDBAdapterSqliteBase} 的全套 SQL、事务
|
|
26
|
+
* 与分支切换逻辑,只把「客户端从哪来」换成了桌面 host。Tauri 侧是同构的另一个类
|
|
27
|
+
* (`@aiao/rxdb-adapter-tauri` 的 `RxDBAdapterTauri`),两者共用同一份线协议与 renderer client。
|
|
28
|
+
*/
|
|
29
|
+
export class RxDBAdapterElectron extends RxDBAdapterSqliteBase {
|
|
30
|
+
readonly #databaseName: string;
|
|
31
|
+
override readonly name: string = ADAPTER_NAME;
|
|
32
|
+
|
|
33
|
+
/** 解析出的逻辑数据库名,与 host 侧的物理文件一一对应。 */
|
|
34
|
+
get databaseName(): string {
|
|
35
|
+
return this.#databaseName;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param rxdb - 绑定的 RxDB 实例
|
|
40
|
+
* @param options - 传输层与逻辑数据库名
|
|
41
|
+
* @throws {@link RxDBAdapterDesktopError} 逻辑数据库名越出应用作用域时抛 `invalid_database_name`
|
|
42
|
+
*/
|
|
43
|
+
constructor(
|
|
44
|
+
rxdb: RxDB,
|
|
45
|
+
readonly options: DesktopOptions = {}
|
|
46
|
+
) {
|
|
47
|
+
super(rxdb, options);
|
|
48
|
+
this.#databaseName = options.databaseName ?? `${rxdb.config.dbName}${DEFAULT_DATABASE_SUFFIX}`;
|
|
49
|
+
// 在构造期就校验:名字非法时根本没有能打开的库,等发出 IPC 才报错只会让原因离现场更远。
|
|
50
|
+
assertValidDesktopDatabaseName(this.#databaseName);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected override async createClient(): Promise<DesktopSqliteClient> {
|
|
54
|
+
return DesktopSqliteClient.connect(
|
|
55
|
+
this.options.transport ?? resolveDesktopHostTransport(),
|
|
56
|
+
{ engine: 'sqlite', databaseName: this.#databaseName },
|
|
57
|
+
{ batchTimeout: this.options.batchTimeout }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
declare module '@aiao/rxdb' {
|
|
63
|
+
interface RxDBAdapters {
|
|
64
|
+
[ADAPTER_NAME]: RxDBAdapterElectron;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Electron 适配器在本包内的身份。
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* 选项形状与逻辑文件名后缀已下沉到 `@aiao/rxdb-adapter-sqlite-core/desktop-host`
|
|
6
|
+
* (两个桌面运行时共用同一份);留在这里的只有**注册名**——它是每个运行时包各自的身份,
|
|
7
|
+
* 共享层不替它们定名。
|
|
8
|
+
*
|
|
9
|
+
* 名字按 `<引擎>-<运行时>` 构成(与 `@aiao/rxdb-adapter-miniprogram` 的
|
|
10
|
+
* `wa-sqlite-miniprogram` 同构)。它不能只叫 `electron`:US-208 会在同一个运行时上
|
|
11
|
+
* 再立一个 PGlite 适配器(`pglite-electron`),到时两者都要能同时注册。
|
|
12
|
+
*
|
|
13
|
+
* @module electron-adapter.interface
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { DesktopHostAdapterName } from '@aiao/rxdb-adapter-sqlite-core/desktop-host';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 适配器在 `RxDBAdapters` 注册表中的名字。
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* `satisfies` 把它钉在共享层的登记表上:这个名字改了而 `DESKTOP_HOST_ADAPTER_NAMES` 没跟上,
|
|
23
|
+
* 是编译错误而不是某个下游在运行期才发现的 `adapter_mismatch`。
|
|
24
|
+
*/
|
|
25
|
+
export const ADAPTER_NAME = 'sqlite-electron' as const satisfies DesktopHostAdapterName;
|