@aiao/rxdb-adapter-electron 0.0.26 → 0.0.28

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.
Files changed (59) hide show
  1. package/README.md +122 -21
  2. package/dist/RxDBAdapterElectron.d.ts +27 -1
  3. package/dist/RxDBAdapterElectron.d.ts.map +1 -1
  4. package/dist/electron-sqlite-host.d.ts.map +1 -1
  5. package/dist/host.d.ts +1 -1
  6. package/dist/host.d.ts.map +1 -1
  7. package/dist/host.js +46 -40
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +20 -6
  11. package/dist/node-sqlite-engine.d.ts +10 -0
  12. package/dist/node-sqlite-engine.d.ts.map +1 -1
  13. package/dist/pglite/RxDBAdapterElectronPGlite.d.ts +53 -1
  14. package/dist/pglite/RxDBAdapterElectronPGlite.d.ts.map +1 -1
  15. package/dist/pglite/desktop-pglite-client.d.ts +27 -1
  16. package/dist/pglite/desktop-pglite-client.d.ts.map +1 -1
  17. package/dist/pglite/desktop-pglite-results.d.ts +14 -0
  18. package/dist/pglite/desktop-pglite-results.d.ts.map +1 -0
  19. package/dist/pglite/electron-pglite-backup-error.d.ts +32 -0
  20. package/dist/pglite/electron-pglite-backup-error.d.ts.map +1 -0
  21. package/dist/pglite/pglite-adapter.interface.d.ts +12 -0
  22. package/dist/pglite/pglite-adapter.interface.d.ts.map +1 -1
  23. package/dist/pglite/restore-electron-pglite-database.d.ts +68 -0
  24. package/dist/pglite/restore-electron-pglite-database.d.ts.map +1 -0
  25. package/dist/pglite-host/electron-pglite-host.d.ts +47 -32
  26. package/dist/pglite-host/electron-pglite-host.d.ts.map +1 -1
  27. package/dist/pglite-host/pglite-host-data-dir.d.ts +75 -0
  28. package/dist/pglite-host/pglite-host-data-dir.d.ts.map +1 -0
  29. package/dist/pglite-host/pglite-host-lock.d.ts +48 -0
  30. package/dist/pglite-host/pglite-host-lock.d.ts.map +1 -0
  31. package/dist/pglite-host/pglite-host-restore.d.ts +38 -0
  32. package/dist/pglite-host/pglite-host-restore.d.ts.map +1 -0
  33. package/dist/pglite-host/pglite-host-runtime.d.ts +90 -0
  34. package/dist/pglite-host/pglite-host-runtime.d.ts.map +1 -0
  35. package/dist/pglite-host.d.ts +2 -2
  36. package/dist/pglite-host.d.ts.map +1 -1
  37. package/dist/pglite-host.js +643 -120
  38. package/dist/pglite.d.ts +2 -0
  39. package/dist/pglite.d.ts.map +1 -1
  40. package/dist/pglite.js +341 -65
  41. package/package.json +13 -10
  42. package/src/RxDBAdapterElectron.ts +40 -1
  43. package/src/electron-sqlite-host.ts +3 -0
  44. package/src/host.ts +1 -0
  45. package/src/index.ts +1 -0
  46. package/src/node-sqlite-engine.ts +20 -1
  47. package/src/pglite/RxDBAdapterElectronPGlite.ts +80 -5
  48. package/src/pglite/desktop-pglite-client.ts +111 -17
  49. package/src/pglite/desktop-pglite-results.ts +24 -0
  50. package/src/pglite/electron-pglite-backup-error.ts +128 -0
  51. package/src/pglite/pglite-adapter.interface.ts +14 -0
  52. package/src/pglite/restore-electron-pglite-database.ts +429 -0
  53. package/src/pglite-host/electron-pglite-host.ts +409 -160
  54. package/src/pglite-host/pglite-host-data-dir.ts +224 -0
  55. package/src/pglite-host/pglite-host-lock.ts +154 -0
  56. package/src/pglite-host/pglite-host-restore.ts +460 -0
  57. package/src/pglite-host/pglite-host-runtime.ts +166 -0
  58. package/src/pglite-host.ts +7 -0
  59. package/src/pglite.ts +9 -0
@@ -1,4 +1,4 @@
1
- import { IPGliteClient, PGliteClientEvents, PGliteClientOptions } from '@aiao/rxdb-adapter-pglite';
1
+ import { IPGliteClient, PGliteClientEvents, PGliteClientOptions, PGliteDataDirItem } from '@aiao/rxdb-adapter-pglite';
2
2
  import { DesktopHostTransport } from '@aiao/rxdb-adapter-sqlite-core/desktop-host';
3
3
  import { EventDispatcher } from '@aiao/utils';
4
4
  import { QueryOptions, Results, Transaction } from '@electric-sql/pglite';
@@ -48,6 +48,8 @@ export declare class DesktopPGliteClient extends EventDispatcher<PGliteClientEve
48
48
  *
49
49
  * @param dbName - RxDB 逻辑库名;只用于变更事件的 `dbName` 字段,不参与落盘位置解析
50
50
  * @param options - PGlite 选项;**不得**含 `store` 或 `dataDir`
51
+ * @throws `RxDBBackupError` 数据目录正在被恢复(`restore_in_progress`)或留着未完成的恢复
52
+ * (`restore_incomplete`)时,见 {@link DesktopPGliteClient.#assertOpened}
51
53
  * @throws {@link RxDBAdapterDesktopError} 指定了落盘位置、协议版本不匹配或 host 打不开数据目录时
52
54
  */
53
55
  init(dbName: string, options: PGliteClientOptions): Promise<void>;
@@ -98,6 +100,30 @@ export declare class DesktopPGliteClient extends EventDispatcher<PGliteClientEve
98
100
  * @returns 形如 `PostgreSQL 17.x ...` 的版本串
99
101
  */
100
102
  version(): Promise<string>;
103
+ /**
104
+ * 在 host 的一致快照上逐项读取数据目录(US-217)。
105
+ *
106
+ * @remarks
107
+ * host 在本会话那条唯一连接上开事务并 `CHECKPOINT`,`fn` 返回之前一直占着它:没有写入能落进
108
+ * 正在读的文件,快照边界前已提交的 WAL 也都已落进数据文件(AC#16)。
109
+ *
110
+ * - **独占**:整段排在本地互斥里,同一客户端的其他语句等快照结束再发。否则它们在 host 上
111
+ * 排在快照后面,而快照又在等 renderer 来取下一块——两边互等。
112
+ * - **背压**:`items` 每被取一项才发一次 `pg.backup.next`,同一时刻至多一个在途请求,
113
+ * renderer 不取,host 就不读下一块(AC#21)。
114
+ * - **收尾**:无论 `fn` 读完、提前返回还是抛出,都发 `pg.backup.end` 交还连接(AC#19)。
115
+ * `fn` 失败时收尾失败不覆盖原始错误;`fn` 成功而收尾失败则整体失败——快照没结束的
116
+ * 备份不能报成功。
117
+ *
118
+ * 等连接空闲的上限沿用构造参数里的 `beginTimeout`(与 `pg.begin` 同档):契约方法不带
119
+ * 备份选项,`lockTimeoutMs` 传不到这里;到期由 host 以 `transaction_unavailable` 拒绝。
120
+ *
121
+ * @param fn - 消费快照的回调;`items` 只在回调返回之前有效
122
+ * @returns 回调的返回值
123
+ * @throws {@link RxDBAdapterDesktopError} 会话已关闭、host 不带备份能力(`unsupported_operation`)、
124
+ * 等不到连接或收尾失败时;`fn` 抛出的错误原样重抛
125
+ */
126
+ snapshotDataDir<T>(fn: (items: AsyncIterable<PGliteDataDirItem>) => Promise<T>): Promise<T>;
101
127
  /**
102
128
  * 断开会话。
103
129
  *
@@ -1 +1 @@
1
- {"version":3,"file":"desktop-pglite-client.d.ts","sourceRoot":"","sources":["../../src/pglite/desktop-pglite-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAML,KAAK,oBAAoB,EAI1B,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAM/E,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IACzC,iCAAiC;IACjC,QAAQ,CAAC,SAAS,EAAE,oBAAoB,CAAC;IACzC,0CAA0C;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAuED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,mBAAoB,SAAQ,eAAe,CAAC,kBAAkB,CAAE,YAAW,aAAa;;IAcnG,+CAA+C;IAC/C,IAAI,SAAS,IAAI,MAAM,CAKtB;IAED,gCAAgC;IAChC,IAAI,gBAAgB,IAAI,MAAM,CAK7B;IAED,0BAA0B;IAC1B,IAAI,wBAAwB,IAAI,MAAM,CAErC;gBAEW,OAAO,EAAE,0BAA0B;IAW/C;;;;;;;;;;;OAWG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BvE;;;;;;;;OAQG;IACG,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAM9F;;;;;;;OAOG;IACG,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAMzF;;;;;;;OAOG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAMrE;;;;;;;;;;;OAWG;IACG,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAK3E;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;IAShC;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAKjC;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CA0MlC"}
1
+ {"version":3,"file":"desktop-pglite-client.d.ts","sourceRoot":"","sources":["../../src/pglite/desktop-pglite-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAQL,KAAK,oBAAoB,EAK1B,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAQ/E,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IACzC,iCAAiC;IACjC,QAAQ,CAAC,SAAS,EAAE,oBAAoB,CAAC;IACzC,0CAA0C;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAgED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,mBAAoB,SAAQ,eAAe,CAAC,kBAAkB,CAAE,YAAW,aAAa;;IAcnG,+CAA+C;IAC/C,IAAI,SAAS,IAAI,MAAM,CAKtB;IAED,gCAAgC;IAChC,IAAI,gBAAgB,IAAI,MAAM,CAK7B;IAED,0BAA0B;IAC1B,IAAI,wBAAwB,IAAI,MAAM,CAErC;gBAEW,OAAO,EAAE,0BAA0B;IAW/C;;;;;;;;;;;;;OAaG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BvE;;;;;;;;OAQG;IACG,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAM9F;;;;;;;OAOG;IACG,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAMzF;;;;;;;OAOG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAMrE;;;;;;;;;;;OAWG;IACG,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAK3E;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;IAShC;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,iBAAiB,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAgCjG;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAKjC;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAiPlC"}
@@ -0,0 +1,14 @@
1
+ import { DesktopPgliteQueryResult } from '@aiao/rxdb-adapter-sqlite-core/desktop-host';
2
+ import { Results } from '@electric-sql/pglite';
3
+ /**
4
+ * 把 host 的应答还原成 PGlite 的 `Results`。
5
+ *
6
+ * @remarks
7
+ * `rows` 里的值已经是 PG 的原生 JS 表示(bigint / `Uint8Array` / `Date` / 普通对象),
8
+ * 结构化克隆逐值搬过来,这里不做任何再解析——多一层转换就多一处能悄悄丢精度的地方。
9
+ *
10
+ * @param result - 已通过协议校验的查询结果
11
+ * @returns 与浏览器路径同形状的结果
12
+ */
13
+ export declare const toDesktopPGliteResults: <T>(result: DesktopPgliteQueryResult) => Results<T>;
14
+ //# sourceMappingURL=desktop-pglite-results.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktop-pglite-results.d.ts","sourceRoot":"","sources":["../../src/pglite/desktop-pglite-results.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,6CAA6C,CAAC;AAC5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,GAAI,CAAC,EAAE,QAAQ,wBAAwB,KAAG,OAAO,CAAC,CAAC,CAIpF,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { RxDBBackupError } from '@aiao/rxdb';
2
+ /** 备份读源库,恢复写目标库;同一个桌面错误码在两个方向上的归因不同。 */
3
+ export type ElectronPGliteBackupDirection = 'backup' | 'restore';
4
+ /**
5
+ * 把桌面 PGlite 备份 / 恢复途中的任意失败归一成 {@link RxDBBackupError}。
6
+ *
7
+ * @remarks
8
+ * 备份与恢复的公共契约只认 `RxDBBackupError.code`;桌面错误码属于 host 线协议,漏到调用方手里,
9
+ * 按契约写的 `switch (error.code)` 就会静默落进 default。已经是 `RxDBBackupError` 的原样返回;
10
+ * 其余一律带着原始错误作为 `cause`:
11
+ *
12
+ * | 桌面错误码 | 备份契约 |
13
+ * | --- | --- |
14
+ * | `database_busy` | `target_busy` |
15
+ * | `restore_in_progress` / `restore_incomplete` / `target_not_empty` / `cleanup_pending` | 同名码 |
16
+ * | `protocol_violation` | `unsupported_combination`(`details.field` 为 `protocolVersion`) |
17
+ * | `unsupported_operation` / `unsupported_runtime_engine` / `invalid_database_name` | `unsupported_combination` |
18
+ * | `disk_full` | `storage_full` |
19
+ * | `transaction_unavailable` | `lock_timeout` |
20
+ * | `session_closed` | `invalid_state` |
21
+ * | `database_corrupted` | 恢复为 `corrupt_archive`,备份为 `io_error` |
22
+ * | 其余桌面错误及非桌面错误 | `io_error` |
23
+ *
24
+ * 目标状态类的码(前两行)`details` 为 `{ field: 'dataDirectoryName', actual: 目录名 }`。
25
+ *
26
+ * @param error - 捕获到的任意值
27
+ * @param dataDirectoryName - 本次操作的数据目录名
28
+ * @param direction - 发生在备份还是恢复
29
+ * @returns 归一后的备份错误
30
+ */
31
+ export declare const toElectronPGliteBackupError: (error: unknown, dataDirectoryName: string, direction: ElectronPGliteBackupDirection) => RxDBBackupError;
32
+ //# sourceMappingURL=electron-pglite-backup-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"electron-pglite-backup-error.d.ts","sourceRoot":"","sources":["../../src/pglite/electron-pglite-backup-error.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAyD,MAAM,YAAY,CAAC;AAOpG,wCAAwC;AACxC,MAAM,MAAM,6BAA6B,GAAG,QAAQ,GAAG,SAAS,CAAC;AA8EjE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,2BAA2B,GACtC,OAAO,OAAO,EACd,mBAAmB,MAAM,EACzB,WAAW,6BAA6B,KACvC,eAIF,CAAC"}
@@ -53,4 +53,16 @@ export interface ElectronPGliteOptions {
53
53
  */
54
54
  readonly batchTimeout?: number;
55
55
  }
56
+ /**
57
+ * 解析实例对应的逻辑数据目录名。
58
+ *
59
+ * @remarks
60
+ * 适配器连接与恢复入口必须算出同一个名字:恢复写进的目录就是之后 `connect` 打开的目录,两处各写一遍
61
+ * 默认规则迟早会分叉。
62
+ *
63
+ * @param dbName - `rxdb.config.dbName`
64
+ * @param options - 适配器选项;只读取 `dataDirectoryName`
65
+ * @returns 显式给出的名字,否则为 `<dbName>-pgdata`;不做校验
66
+ */
67
+ export declare const resolveDataDirectoryName: (dbName: string, options: ElectronPGliteOptions) => string;
56
68
  //# sourceMappingURL=pglite-adapter.interface.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pglite-adapter.interface.d.ts","sourceRoot":"","sources":["../../src/pglite/pglite-adapter.interface.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6CAA6C,CAAC;AAExF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,EAAG,iBAA0B,CAAC;AAEvD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,YAAY,CAAC;AAEvD,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,oBAAoB,CAAC;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC"}
1
+ {"version":3,"file":"pglite-adapter.interface.d.ts","sourceRoot":"","sources":["../../src/pglite/pglite-adapter.interface.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6CAA6C,CAAC;AAExF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,EAAG,iBAA0B,CAAC;AAEvD;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,YAAY,CAAC;AAEvD,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,oBAAoB,CAAC;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GAAI,QAAQ,MAAM,EAAE,SAAS,qBAAqB,KAAG,MAChB,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { RxDB, RxDBRestoreResult } from '@aiao/rxdb';
2
+ import { PGliteRestoreOptions } from '@aiao/rxdb-adapter-pglite';
3
+ import { ElectronPGliteOptions } from './pglite-adapter.interface.js';
4
+ /**
5
+ * 恢复目标:一个**尚未连接**的 RxDB 实例,加上它将来连接时交给
6
+ * {@link RxDBAdapterElectronPGlite} 的同一份选项。
7
+ *
8
+ * @remarks
9
+ * 数据目录名、传输层、实体结构指纹与加密认证域都按这份配置判定;与之后连接时的选项不一致,
10
+ * 恢复出来的库就会落在另一个目录,或者被判成不兼容。
11
+ */
12
+ export interface ElectronPGliteRestoreTarget {
13
+ readonly rxdb: RxDB;
14
+ readonly options: ElectronPGliteOptions;
15
+ }
16
+ /**
17
+ * 把 {@link RxDBAdapterElectronPGlite.backup} 产出的归档恢复进 host 上的一个新数据目录。
18
+ *
19
+ * @remarks
20
+ * 只写入**空**目标,不做合并或覆盖。步骤:
21
+ *
22
+ * 1. 在读取归档之前拒绝已连接的目标实例(`target_busy`,`details.field` 为 `rxdb`)、协议版本不一致
23
+ * 或未启用备份的 host(`unsupported_combination`)。
24
+ * 2. host **先**取得目录的独占权,**再**检查未完成标记与空状态;独占一直保持到提交或失败处理结束,
25
+ * 期间其他窗口的 `connect`、其他恢复与清理请求都被拒绝。
26
+ * 3. 读 manifest,在写入第一个字节之前判定兼容性:适配器、引擎大版本、扩展(目标 host 必须全部提供)、
27
+ * 系统表 / 变更编码版本、实体结构指纹与加密认证域(`incompatible_archive` /
28
+ * `auth_domain_mismatch`)。
29
+ * 4. host 持久化未完成标记,归档逐项写进目录;读到结尾、摘要通过后才在私有实例上启动 PostgreSQL,
30
+ * 核对引擎与系统表水位,关闭实例并逐一 fsync,最后删除标记、交还独占。
31
+ *
32
+ * 任一步失败都由 host 删除目录与标记,目标回到从未恢复过的状态;连这一步也失败时报
33
+ * `cleanup_pending`,调 {@link cleanupIncompleteElectronPGliteRestore} 重试。恢复途中窗口或进程
34
+ * 退出时标记留在盘上,下一次连接以 `restore_incomplete` 拒绝,同样用它清理后再恢复。
35
+ *
36
+ * 归档只含数据库:结果与 manifest 的 `scope.externalFiles` 恒为 `excluded`,外置文件须另行备份恢复。
37
+ * 输入流在失败时被取消,成功时只释放读锁。桌面错误码按 {@link toElectronPGliteBackupError} 归一。
38
+ *
39
+ * @param source - 归档字节流
40
+ * @param target - 尚未连接的目标实例与其 adapter 选项
41
+ * @param options - 取消信号与阶段回调;四个阶段都会触发
42
+ * @returns 结束标记(条目数、字节数、SHA-256)、manifest 与范围
43
+ * @throws RxDBBackupError `target_busy` / `target_not_empty` / `restore_in_progress` / `restore_incomplete` /
44
+ * `unsupported_combination` / `incompatible_archive` / `auth_domain_mismatch` / `corrupt_archive` /
45
+ * `truncated_archive` / `aborted` / `io_error` / `storage_full` / `cleanup_pending`
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const target = { rxdb, options: {} };
50
+ * await restoreElectronPGliteDatabase(file.stream(), target);
51
+ * rxdb.adapter(ELECTRON_PGLITE_ADAPTER_NAME, db => new RxDBAdapterElectronPGlite(db, target.options));
52
+ * await rxdb.connect(ELECTRON_PGLITE_ADAPTER_NAME);
53
+ * ```
54
+ */
55
+ export declare const restoreElectronPGliteDatabase: (source: ReadableStream<Uint8Array>, target: ElectronPGliteRestoreTarget, options?: PGliteRestoreOptions) => Promise<RxDBRestoreResult>;
56
+ /**
57
+ * 清理一次没做完的恢复(窗口或进程在恢复中途退出、或恢复失败后清理本身也失败)。
58
+ *
59
+ * @remarks
60
+ * host 先取目录的独占权:目录正被打开或正在恢复时报 `target_busy`,从不删别人正在用的库。
61
+ * 没有未完成标记时什么都不做——一个正常的库永远不会被这里删掉。
62
+ *
63
+ * @param target - 恢复时使用的同一个目标
64
+ * @returns 确实清理了残留时为 `true`
65
+ * @throws RxDBBackupError `target_busy` / `unsupported_combination` / `cleanup_pending`
66
+ */
67
+ export declare const cleanupIncompleteElectronPGliteRestore: (target: ElectronPGliteRestoreTarget) => Promise<boolean>;
68
+ //# sourceMappingURL=restore-electron-pglite-database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restore-electron-pglite-database.d.ts","sourceRoot":"","sources":["../../src/pglite/restore-electron-pglite-database.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAQL,KAAK,IAAI,EAKT,KAAK,iBAAiB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAkBnC,OAAO,EAA0C,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAEnH;;;;;;;GAOG;AACH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;CACzC;AAqRD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,6BAA6B,GACxC,QAAQ,cAAc,CAAC,UAAU,CAAC,EAClC,QAAQ,2BAA2B,EACnC,UAAS,oBAAyB,KACjC,OAAO,CAAC,iBAAiB,CAc3B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sCAAsC,GAAU,QAAQ,2BAA2B,KAAG,OAAO,CAAC,OAAO,CAuBjH,CAAC"}
@@ -1,4 +1,6 @@
1
1
  import { DesktopPgliteNotifyMessage, DesktopPgliteResponse } from '@aiao/rxdb-adapter-sqlite-core/desktop-host';
2
+ import { ElectronPgliteProbeRuntime, ElectronPgliteRuntime } from './pglite-host-runtime.js';
3
+ export type { ElectronPgliteProbeRuntime, ElectronPgliteRuntime, ElectronPgliteRuntimeResult, ElectronPgliteTransaction } from './pglite-host-runtime.js';
2
4
  /**
3
5
  * host 代 renderer 订阅的 NOTIFY 频道。
4
6
  *
@@ -8,35 +10,41 @@ import { DesktopPgliteNotifyMessage, DesktopPgliteResponse } from '@aiao/rxdb-ad
8
10
  * 查询在桌面下永远不刷新——而浏览器下一切正常,排查时几乎不会怀疑到订阅清单上。
9
11
  */
10
12
  export declare const DESKTOP_PGLITE_WATCH_CHANNELS: readonly string[];
11
- /** 一条语句的结果;`ElectronPgliteRuntime` 与 PGlite 的 `Results` 在此结构相容。 */
12
- export interface ElectronPgliteRuntimeResult {
13
- readonly rows: Record<string, unknown>[];
14
- readonly fields: {
15
- name: string;
16
- dataTypeID: number;
17
- }[];
18
- readonly affectedRows?: number;
19
- }
20
- /** 事务句柄;对应 PGlite 的 `Transaction`,只声明 host 真正用到的两个操作。 */
21
- export interface ElectronPgliteTransaction {
22
- query(sql: string, params?: unknown[]): Promise<ElectronPgliteRuntimeResult>;
23
- exec(sql: string): Promise<ElectronPgliteRuntimeResult[]>;
24
- }
25
13
  /**
26
- * host 需要的 PGlite 能力子集。
14
+ * 备份与恢复所需的宿主配置(US-217)。
27
15
  *
28
16
  * @remarks
29
- * 刻意不写成 `PGliteInterface`:宿主应用极可能传进来的不是裸 `PGlite`,而是跑在 worker
30
- * 里的 `PGliteWorker`——PGlite 的 WASM 在主进程 JS 线程上是**同步**执行的,一条重查询会
31
- * 把整个窗口的 IPC 一起卡住(故事「冻结带来的三条实现约束」第 3 条)。声明成结构类型后,
32
- * 两者都能直接传入,而这份清单也顺便说清了 host 只需要这五个操作。
17
+ * 不传时 host 只提供连接:`pg.engine`、`pg.backup.*`、`pg.restore.*` 一律报 `unsupported_operation`,
18
+ * `pg.open` 也不取目录锁,与引入备份之前的行为逐项一致。传了之后每个打开的数据目录旁边都有一把
19
+ * 跨进程的目录锁:同一目录上的恢复与连接互斥,另一个进程里的 host 也不例外(AC#12)。
33
20
  */
34
- export interface ElectronPgliteRuntime {
35
- query(sql: string, params?: unknown[]): Promise<ElectronPgliteRuntimeResult>;
36
- exec(sql: string): Promise<ElectronPgliteRuntimeResult[]>;
37
- transaction<T>(callback: (tx: ElectronPgliteTransaction) => Promise<T>): Promise<T>;
38
- listen(channel: string, callback: (payload: string) => void): Promise<unknown>;
39
- close(): Promise<void>;
21
+ export interface ElectronPgliteBackupOptions {
22
+ /**
23
+ * 逻辑数据目录名到物理路径。
24
+ *
25
+ * @remarks
26
+ * **必须**就是 `createRuntime` 为同一个名字打开的目录:备份从这里读文件,恢复往这里写文件,
27
+ * 目录锁与恢复标记也建在它旁边。两者不一致时备份报 `host_internal_error`,而不是悄悄读出别的目录。
28
+ * 传进来的名字已过白名单校验,不含任何路径分隔符。
29
+ */
30
+ readonly resolveDataDirectory: (dataDirectoryName: string) => string;
31
+ /**
32
+ * 创建一个不落盘的探针运行时,用来回答 `pg.engine`。
33
+ *
34
+ * @remarks
35
+ * 例如 `new PGlite()`(内存实例)。必须与 `createRuntime` 用同一份 PGlite、带同一组扩展,
36
+ * 否则回答的引擎版本对真实数据目录不成立。探针用完即关,结果在 host 生命周期内缓存;
37
+ * 起不来时不缓存,下一次 `pg.engine` 重试。
38
+ */
39
+ readonly createProbeRuntime: () => Promise<ElectronPgliteProbeRuntime>;
40
+ /**
41
+ * `createRuntime` 为每个实例装载的扩展名。
42
+ *
43
+ * @remarks
44
+ * 只列影响数据兼容性的扩展,不含 `live`(它不改变数据目录);没有扩展时传空数组。
45
+ * 归档据此声明所需扩展,恢复端不满足时在写入目标之前拒绝(AC#5)。
46
+ */
47
+ readonly extensions: readonly string[];
40
48
  }
41
49
  /** {@link createElectronPgliteHost} 的入参。 */
42
50
  export interface ElectronPgliteHostOptions {
@@ -49,7 +57,7 @@ export interface ElectronPgliteHostOptions {
49
57
  * 数据会静默落在谁也没打算用的位置,而症状是「重启后数据没了」。
50
58
  *
51
59
  * 传进来的名字已过白名单校验,不含任何路径分隔符,因此 `join(root, name)` 不会越出 `root`。
52
- * 同一个名字在 host 生命周期内只会被调用一次(AC#7)。
60
+ * 同一个名字同一时刻至多只有一个活着的运行时(AC#7);恢复校验同样经它打开目标目录。
53
61
  */
54
62
  readonly createRuntime: (dataDirectoryName: string) => Promise<ElectronPgliteRuntime>;
55
63
  /** 把裸 NOTIFY 送达对应会话的 renderer,例如 `webContents.send`。 */
@@ -62,6 +70,8 @@ export interface ElectronPgliteHostOptions {
62
70
  * 把送达失败当成写失败回给调用方只会诱发一次重复写入。不传则丢弃。
63
71
  */
64
72
  readonly onDeliveryError?: (error: unknown) => void;
73
+ /** 备份与恢复支持;不传则不提供(见 {@link ElectronPgliteBackupOptions})。 */
74
+ readonly backup?: ElectronPgliteBackupOptions;
65
75
  }
66
76
  /** 桌面 PGlite host 实例。 */
67
77
  export interface ElectronPgliteHost {
@@ -79,24 +89,28 @@ export interface ElectronPgliteHost {
79
89
  handle(request: unknown, ownerId: number): Promise<DesktopPgliteResponse>;
80
90
  /** 当前打开的会话数。 */
81
91
  readonly openSessionCount: number;
82
- /** 当前活着的 PGlite 实例数;同一个数据目录上的多个会话只算一个。 */
92
+ /** 当前活着的 PGlite 实例数;同一个数据目录上的多个会话只算一个,恢复校验用的私有实例不计入。 */
83
93
  readonly openInstanceCount: number;
84
94
  /** 当前挂起的事务数;正常静止时应为 0。 */
85
95
  readonly openTransactionCount: number;
96
+ /** 当前进行中的备份快照数;快照同样占着连接,但不计入 `openTransactionCount`。 */
97
+ readonly openBackupCount: number;
98
+ /** 当前进行中的恢复数。 */
99
+ readonly openRestoreCount: number;
86
100
  /**
87
- * 回收某个窗口名下的全部会话与事务。
101
+ * 回收某个窗口名下的全部会话、事务、快照与恢复。
88
102
  *
89
103
  * @remarks
90
104
  * 这是本方案的**前提而非收尾**:挂起的 callback 独占 PGlite 的连接锁,渲染进程崩在
91
105
  * 事务中间而没人回收的话,之后任何查询都会永远排队——表征是「数据库不响应」,
92
106
  * 与那次崩溃毫无关联线索。调用方必须把它挂到 `render-process-gone` 与 `destroyed`
93
- * 两个事件上(AC#3)。
107
+ * 两个事件上(AC#3)。被打断的恢复保留恢复标记,之后的连接据此被拒绝(AC#11)。
94
108
  *
95
109
  * @param ownerId - 已崩溃或已销毁的 `webContents.id`
96
110
  * @returns 本次回滚掉的事务条数
97
111
  */
98
112
  releaseOwner(ownerId: number): Promise<number>;
99
- /** 关闭全部会话与实例,通常在应用退出前调用。 */
113
+ /** 关闭全部会话与实例、打断全部恢复,通常在应用退出前调用。 */
100
114
  closeAll(): Promise<void>;
101
115
  }
102
116
  /**
@@ -104,8 +118,9 @@ export interface ElectronPgliteHost {
104
118
  *
105
119
  * @remarks
106
120
  * 与 SQLite host 相反,同一个数据目录上的多个会话**共享一个** PGlite 实例(AC#7):
107
- * PGlite 是嵌入式单写者,同一份数据目录被两个实例同时打开会直接损坏它。跨窗口的并发
108
- * 因此由这一条连接的排队来串行化,而不是交给文件锁。
121
+ * PGlite 是嵌入式单写者,同一份数据目录被两个实例同时打开会直接损坏它。同一 host 内跨窗口的
122
+ * 并发因此由这一条连接的排队来串行化;启用备份时每个实例还在数据目录旁持有一把跨进程的
123
+ * 目录锁,恢复据此确认目标没有被任何进程连着(AC#12)。
109
124
  *
110
125
  * @param options - host 配置
111
126
  * @returns host 实例
@@ -1 +1 @@
1
- {"version":3,"file":"electron-pglite-host.d.ts","sourceRoot":"","sources":["../../src/pglite-host/electron-pglite-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAIL,KAAK,0BAA0B,EAG/B,KAAK,qBAAqB,EAC3B,MAAM,6CAA6C,CAAC;AAarD;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,MAAM,EAIzD,CAAC;AAEH,mEAAmE;AACnE,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACxD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,yDAAyD;AACzD,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC7E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,EAAE,CAAC,CAAC;CAC3D;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAC7E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,EAAE,CAAC,CAAC;IAC1D,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,yBAAyB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpF,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,4CAA4C;AAC5C,MAAM,WAAW,yBAAyB;IACxC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACtF,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACnE;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACrD;AAED,yBAAyB;AACzB,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;OAUG;IACH,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC1E,gBAAgB;IAChB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,0CAA0C;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,0BAA0B;IAC1B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,4BAA4B;IAC5B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAqFD;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB,CAoV/F"}
1
+ {"version":3,"file":"electron-pglite-host.d.ts","sourceRoot":"","sources":["../../src/pglite-host/electron-pglite-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EASL,KAAK,0BAA0B,EAE/B,KAAK,qBAAqB,EAC3B,MAAM,6CAA6C,CAAC;AASrD,OAAO,EAIL,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,EAE3B,MAAM,0BAA0B,CAAC;AAElC,YAAY,EACV,0BAA0B,EAC1B,qBAAqB,EACrB,2BAA2B,EAC3B,yBAAyB,EAC1B,MAAM,0BAA0B,CAAC;AAYlC;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS,MAAM,EAIzD,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;;;;;OAOG;IACH,QAAQ,CAAC,oBAAoB,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,MAAM,CAAC;IACrE;;;;;;;OAOG;IACH,QAAQ,CAAC,kBAAkB,EAAE,MAAM,OAAO,CAAC,0BAA0B,CAAC,CAAC;IACvE;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,4CAA4C;AAC5C,MAAM,WAAW,yBAAyB;IACxC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACtF,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACnE;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACpD,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,2BAA2B,CAAC;CAC/C;AAED,yBAAyB;AACzB,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;OAUG;IACH,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC1E,gBAAgB;IAChB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,wDAAwD;IACxD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,0BAA0B;IAC1B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,wDAAwD;IACxD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,iBAAiB;IACjB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,mCAAmC;IACnC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAoED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB,CAsjB/F"}
@@ -0,0 +1,75 @@
1
+ import { RxDBAdapterDesktopError, DesktopPgliteDataDirItem } from '@aiao/rxdb-adapter-sqlite-core/desktop-host';
2
+ /**
3
+ * 取 Node 文件系统错误上的 errno 名(`ENOENT` 等)。
4
+ *
5
+ * @param error - 原始错误
6
+ * @returns errno 名;不是 errno 形状的错误时为 `undefined`
7
+ */
8
+ export declare const errnoOf: (error: unknown) => string | undefined;
9
+ /**
10
+ * 把文件系统错误归一成桌面错误。
11
+ *
12
+ * @remarks
13
+ * 已经是桌面错误的原样透传:遍历中途抛出的「文件变短」之类的判定比外层的笼统描述更准。
14
+ * 磁盘写满必须单独成码,renderer 据此报 `storage_full`,调用方才知道腾出空间后重试即可。
15
+ *
16
+ * @param error - 原始错误
17
+ * @param detail - 面向开发者的描述
18
+ * @returns 桌面错误
19
+ */
20
+ export declare const toHostIoError: (error: unknown, detail: string) => RxDBAdapterDesktopError;
21
+ /**
22
+ * 按确定顺序逐块读出一个 PGlite 数据目录。
23
+ *
24
+ * @remarks
25
+ * 调用方必须已经占住该目录上唯一的运行时连接并做完 `CHECKPOINT`:遍历期间没有语句能改动文件,
26
+ * 目录树因此就是一个一致快照。同一时刻只有一块数据在途,与库大小无关;消费方不取下一项,
27
+ * 就不会读下一块。提前结束(`return()`)时当前文件句柄在 `finally` 里关闭。
28
+ *
29
+ * @param directory - 数据目录的物理路径
30
+ * @returns 数据目录各项;每个数据块独占自己的 `ArrayBuffer`
31
+ * @throws {@link RxDBAdapterDesktopError} `disk_full` / `permission_denied` / `file_not_found` / `host_internal_error`
32
+ */
33
+ export declare function walkPgliteDataDirectory(directory: string): AsyncGenerator<DesktopPgliteDataDirItem>;
34
+ /**
35
+ * 判定恢复目标是否可写:不存在,或是一个空目录。
36
+ *
37
+ * @remarks
38
+ * 同名的普通文件算「不空」而不是 I/O 故障:它同样意味着那里已经有别的东西,恢复不能覆盖。
39
+ *
40
+ * @param directory - 数据目录的物理路径
41
+ * @returns 可以在此恢复时为 `true`
42
+ * @throws {@link RxDBAdapterDesktopError} 目录读不出来时
43
+ */
44
+ export declare const isEmptyOrMissing: (directory: string) => Promise<boolean>;
45
+ /**
46
+ * 数据目录里是否有 `PG_VERSION`。
47
+ *
48
+ * @remarks
49
+ * PGlite 打开一个没有 `PG_VERSION` 的目录时会**就地 initdb**。恢复校验前必须先确认它在,
50
+ * 否则一份缺了它的归档会被悄悄换成一个全新的空库,然后以「系统表水位不符」之类不相干的
51
+ * 原因失败,甚至通过校验。
52
+ *
53
+ * @param directory - 数据目录的物理路径
54
+ * @returns 存在且是普通文件时为 `true`
55
+ */
56
+ export declare const hasPgVersion: (directory: string) => Promise<boolean>;
57
+ /**
58
+ * 把整棵数据目录(连同它在父目录里的目录项)逐一落盘。
59
+ *
60
+ * @remarks
61
+ * 恢复标记只能在这一步完成之后删除:先删标记再掉电,重启后看到的是一个「没有标记、
62
+ * 却缺了几页」的库,与完整恢复无从区分(AC#11)。
63
+ *
64
+ * @param directory - 数据目录的物理路径
65
+ * @throws {@link RxDBAdapterDesktopError} 落盘失败时
66
+ */
67
+ export declare const syncPgliteDataDirectory: (directory: string) => Promise<void>;
68
+ /**
69
+ * 删除一棵数据目录;不存在时视为已删除。
70
+ *
71
+ * @param directory - 数据目录的物理路径
72
+ * @throws {@link RxDBAdapterDesktopError} 删除失败时
73
+ */
74
+ export declare const removePgliteDataDirectory: (directory: string) => Promise<void>;
75
+ //# sourceMappingURL=pglite-host-data-dir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pglite-host-data-dir.d.ts","sourceRoot":"","sources":["../../src/pglite-host/pglite-host-data-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAGL,uBAAuB,EACvB,KAAK,wBAAwB,EAE9B,MAAM,6CAA6C,CAAC;AAcrD;;;;;GAKG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAGjD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,OAAO,EAAE,QAAQ,MAAM,KAAG,uBAI9D,CAAC;AA2CF;;;;;;;;;;;GAWG;AACH,wBAAuB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAAC,wBAAwB,CAAC,CAM1G;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAU,WAAW,MAAM,KAAG,OAAO,CAAC,OAAO,CASzE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GAAU,WAAW,MAAM,KAAG,OAAO,CAAC,OAAO,CAOrE,CAAC;AAgCF;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,GAAU,WAAW,MAAM,KAAG,OAAO,CAAC,IAAI,CAO7E,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,GAAU,WAAW,MAAM,KAAG,OAAO,CAAC,IAAI,CAO/E,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * PGlite 数据目录的跨进程独占锁与恢复标记(US-217)。
3
+ *
4
+ * @module pglite-host/pglite-host-lock
5
+ */
6
+ /** 一个已持有的数据目录锁。 */
7
+ export interface PgliteDirectoryLock {
8
+ /** 目录上是否留有未完成恢复的标记。 */
9
+ hasMarker(): boolean;
10
+ /** 持久化恢复标记;返回时标记已经落盘。 */
11
+ markRestoring(): void;
12
+ /** 删除恢复标记;返回时删除已经落盘。 */
13
+ clearRestoring(): void;
14
+ /** 释放锁;重复调用无副作用。 */
15
+ release(): void;
16
+ }
17
+ /**
18
+ * 数据目录对应的锁文件路径:与数据目录同级的隐藏文件。
19
+ *
20
+ * @remarks
21
+ * 库名不能以 `.` 开头({@link assertValidDesktopDatabaseName}),锁文件名因此不会与任何库名撞车;
22
+ * 放在数据目录外面,恢复删除整个目录时锁与标记都还在。
23
+ *
24
+ * @param directory - 数据目录的物理路径
25
+ * @returns 锁文件的物理路径
26
+ */
27
+ export declare const pgliteLockPathOf: (directory: string) => string;
28
+ /**
29
+ * 取得一个 PGlite 数据目录的独占使用权。
30
+ *
31
+ * @remarks
32
+ * Node 上的 PGlite(NODEFS)自己没有任何跨进程锁:两个进程打开同一个数据目录会各自写坏对方。
33
+ * 这里借 SQLite 的文件锁(POSIX `fcntl` / Windows `LockFileEx`)实现独占:
34
+ *
35
+ * - 锁由操作系统持有,进程被强杀时随之释放,不会像 pid 文件那样留下需要人工判断的陈旧锁,
36
+ * 也不怕 pid 被复用(AC#11);
37
+ * - 同一进程内的第二条连接同样撞锁,同一 host 里的重复打开也被挡住;
38
+ * - 恢复标记与锁在同一个文件里,写入在拿锁之后、提交即落盘,任何改动目标的动作都排在它后面。
39
+ *
40
+ * 锁文件本身从不删除:删掉一个仍被别的进程锁着的文件,后来者会在新文件上拿到「独占」,锁就裂成两把。
41
+ *
42
+ * @param directory - 数据目录的物理路径;不要求存在,其父目录不存在时会被创建
43
+ * @returns 已持有的锁
44
+ * @throws {@link RxDBAdapterDesktopError} `database_busy`:别的连接(其他进程或同一进程内)正持有该目录;
45
+ * `permission_denied` / `disk_full` / `open_failed`:锁文件无法创建或写入
46
+ */
47
+ export declare const acquirePgliteDirectoryLock: (directory: string) => PgliteDirectoryLock;
48
+ //# sourceMappingURL=pglite-host-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pglite-host-lock.d.ts","sourceRoot":"","sources":["../../src/pglite-host/pglite-host-lock.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,mBAAmB;AACnB,MAAM,WAAW,mBAAmB;IAClC,uBAAuB;IACvB,SAAS,IAAI,OAAO,CAAC;IACrB,yBAAyB;IACzB,aAAa,IAAI,IAAI,CAAC;IACtB,wBAAwB;IACxB,cAAc,IAAI,IAAI,CAAC;IACvB,oBAAoB;IACpB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,WAAW,MAAM,KAAG,MACU,CAAC;AA0DhE;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,0BAA0B,GAAI,WAAW,MAAM,KAAG,mBAyC9D,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { DesktopPgliteResponse, DesktopPgliteRestoreQueryRequest, DesktopPgliteRestoreStepRequest, DesktopPgliteRestoreTargetRequest, DesktopPgliteRestoreWriteRequest } from '@aiao/rxdb-adapter-sqlite-core/desktop-host';
2
+ import { ElectronPgliteRuntime } from './pglite-host-runtime.js';
3
+ /** 恢复协调器处理的全部请求。 */
4
+ export type DesktopPgliteRestoreRequest = DesktopPgliteRestoreTargetRequest | DesktopPgliteRestoreStepRequest | DesktopPgliteRestoreWriteRequest | DesktopPgliteRestoreQueryRequest;
5
+ /** {@link createPgliteRestoreCoordinator} 的依赖。 */
6
+ export interface PgliteRestoreCoordinatorOptions {
7
+ /** 起一个运行时;恢复校验经它打开目标目录,与普通连接用同一份 PGlite 与扩展。 */
8
+ readonly createRuntime: (dataDirectoryName: string) => Promise<ElectronPgliteRuntime>;
9
+ /** 逻辑名到物理路径;必须就是 `createRuntime` 打开的目录。 */
10
+ readonly resolveDataDirectory: (dataDirectoryName: string) => string;
11
+ /** 本 host 是否正连着该目录;正在关闭的实例要等它关完再回答。 */
12
+ readonly isInUse: (dataDirectoryName: string) => Promise<boolean>;
13
+ }
14
+ /** 一个 host 内全部进行中的恢复。 */
15
+ export interface PgliteRestoreCoordinator {
16
+ /** 进行中的恢复数。 */
17
+ readonly openCount: number;
18
+ /** 该名字是否正被本 host 的恢复占着(含正在取锁的);`pg.open` 据此报 `restore_in_progress`。 */
19
+ isReserved(dataDirectoryName: string): boolean;
20
+ /**
21
+ * 处理一条恢复请求。
22
+ *
23
+ * @throws {@link RxDBAdapterDesktopError} 失败以桌面错误抛出,由 host 统一转成错误应答
24
+ */
25
+ handle(request: DesktopPgliteRestoreRequest, ownerId: number): Promise<DesktopPgliteResponse>;
26
+ /** 打断某个窗口名下的全部恢复:关资源、交还独占,**保留**恢复标记(AC#11)。 */
27
+ releaseOwner(ownerId: number): Promise<void>;
28
+ /** 打断全部恢复,语义同 {@link PgliteRestoreCoordinator.releaseOwner}。 */
29
+ closeAll(): Promise<void>;
30
+ }
31
+ /**
32
+ * 创建一个 host 的恢复协调器。
33
+ *
34
+ * @param options - 运行时工厂、目录解析与本 host 的占用查询
35
+ * @returns 恢复协调器
36
+ */
37
+ export declare const createPgliteRestoreCoordinator: (options: PgliteRestoreCoordinatorOptions) => PgliteRestoreCoordinator;
38
+ //# sourceMappingURL=pglite-host-restore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pglite-host-restore.d.ts","sourceRoot":"","sources":["../../src/pglite-host/pglite-host-restore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAKL,KAAK,qBAAqB,EAC1B,KAAK,gCAAgC,EACrC,KAAK,+BAA+B,EACpC,KAAK,iCAAiC,EACtC,KAAK,gCAAgC,EACtC,MAAM,6CAA6C,CAAC;AAarD,OAAO,EAA8B,KAAK,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAElG,oBAAoB;AACpB,MAAM,MAAM,2BAA2B,GACnC,iCAAiC,GACjC,+BAA+B,GAC/B,gCAAgC,GAChC,gCAAgC,CAAC;AAKrC,kDAAkD;AAClD,MAAM,WAAW,+BAA+B;IAC9C,gDAAgD;IAChD,QAAQ,CAAC,aAAa,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACtF,2CAA2C;IAC3C,QAAQ,CAAC,oBAAoB,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,MAAM,CAAC;IACrE,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED,yBAAyB;AACzB,MAAM,WAAW,wBAAwB;IACvC,eAAe;IACf,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uEAAuE;IACvE,UAAU,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/C;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,2BAA2B,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC9F,gDAAgD;IAChD,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,gEAAgE;IAChE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAsFD;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,+BAA+B,KAAG,wBAsSzF,CAAC"}
@@ -0,0 +1,90 @@
1
+ import { DesktopPgliteQueryResult } from '@aiao/rxdb-adapter-sqlite-core/desktop-host';
2
+ /** 一条语句的结果;`ElectronPgliteRuntime` 与 PGlite 的 `Results` 在此结构相容。 */
3
+ export interface ElectronPgliteRuntimeResult {
4
+ readonly rows: Record<string, unknown>[];
5
+ readonly fields: {
6
+ name: string;
7
+ dataTypeID: number;
8
+ }[];
9
+ readonly affectedRows?: number;
10
+ }
11
+ /** 事务句柄;对应 PGlite 的 `Transaction`,只声明 host 真正用到的两个操作。 */
12
+ export interface ElectronPgliteTransaction {
13
+ query(sql: string, params?: unknown[]): Promise<ElectronPgliteRuntimeResult>;
14
+ exec(sql: string): Promise<ElectronPgliteRuntimeResult[]>;
15
+ }
16
+ /**
17
+ * host 需要的 PGlite 能力子集。
18
+ *
19
+ * @remarks
20
+ * 刻意不写成 `PGliteInterface`:宿主应用极可能传进来的不是裸 `PGlite`,而是跑在 worker
21
+ * 里的 `PGliteWorker`——PGlite 的 WASM 在主进程 JS 线程上是**同步**执行的,一条重查询会
22
+ * 把整个窗口的 IPC 一起卡住(故事「冻结带来的三条实现约束」第 3 条)。声明成结构类型后,
23
+ * 两者都能直接传入,而这份清单也顺便说清了 host 只需要这五个操作。
24
+ */
25
+ export interface ElectronPgliteRuntime {
26
+ query(sql: string, params?: unknown[]): Promise<ElectronPgliteRuntimeResult>;
27
+ exec(sql: string): Promise<ElectronPgliteRuntimeResult[]>;
28
+ transaction<T>(callback: (tx: ElectronPgliteTransaction) => Promise<T>): Promise<T>;
29
+ listen(channel: string, callback: (payload: string) => void): Promise<unknown>;
30
+ close(): Promise<void>;
31
+ }
32
+ /**
33
+ * 回答 `pg.engine` 的探针运行时:只需要查询与关闭(US-217)。
34
+ *
35
+ * @remarks
36
+ * 裸 `new PGlite()`(内存实例)即满足;它必须与 `createRuntime` 起的实例是同一份 PGlite、
37
+ * 带同一组扩展,否则回答的引擎版本对真实数据目录不成立。
38
+ */
39
+ export type ElectronPgliteProbeRuntime = Pick<ElectronPgliteRuntime, 'query' | 'close'>;
40
+ /**
41
+ * 把 PGlite 的结果收窄成能过结构化克隆的形状。
42
+ *
43
+ * @remarks
44
+ * `fields` 上除了 `name` / `dataTypeID` 还挂着 PGlite 自己的解析器,整个丢给
45
+ * `ipcRenderer.invoke` 会以 DataCloneError 失败——而报错点在 IPC 层,看上去与 SQL 毫无关系。
46
+ * `rows` 里的 bigint / Uint8Array / Date 都是结构化克隆原生支持的,原样带走(AC#1)。
47
+ *
48
+ * @param result - 运行时返回的结果
49
+ * @returns 可以跨进程传递的结果
50
+ */
51
+ export declare const toWireResult: (result: ElectronPgliteRuntimeResult) => DesktopPgliteQueryResult;
52
+ /**
53
+ * 跑一条语句,把引擎抛出的错误归一成 `statement_failed`。
54
+ *
55
+ * @remarks
56
+ * 不归一的话,一条写错的 SQL 会以 `host_internal_error` 回到 renderer——那个码的含义是
57
+ * 「host 有缺陷」,会把调用方的排查引向完全错误的方向。
58
+ *
59
+ * @param run - 执行语句
60
+ * @returns 语句结果
61
+ * @throws {@link RxDBAdapterDesktopError} 语句失败时为 `statement_failed`;已经是桌面错误的原样透传
62
+ */
63
+ export declare const runStatement: <T>(run: () => Promise<T>) => Promise<T>;
64
+ /** 一条挂起中的事务:`transaction(cb)` 的 callback 停在 host 持有的 promise 上。 */
65
+ export interface SuspendedTransaction {
66
+ readonly tx: ElectronPgliteTransaction;
67
+ /** 结掉 callback:`resolve` 提交,`reject` 让 callback 抛出从而回滚。 */
68
+ readonly settle: {
69
+ readonly resolve: () => void;
70
+ readonly reject: (error: Error) => void;
71
+ };
72
+ /** `transaction(...)` 本身;等它落地才算真的提交/回滚完。 */
73
+ readonly finished: Promise<unknown>;
74
+ }
75
+ /**
76
+ * 在运行时上开一条事务并把它挂起,直到调用方结掉 `settle`。
77
+ *
78
+ * @remarks
79
+ * PGlite 只提供 callback 形态的事务(`transaction(cb)`,返回即 COMMIT、抛出即 ROLLBACK),
80
+ * 而 `cb` 跨不了 IPC。这里进入 `cb` 后立刻 `await` 一个由 host 持有的 promise,事务因此在
81
+ * PostgreSQL 侧一直开着、连接一直被占住。`pg.begin` 用它把多次 IPC 串成一条真事务,
82
+ * `pg.backup.begin` 用它在读快照期间挡住一切写入(AC#16)。
83
+ *
84
+ * @param runtime - 运行时
85
+ * @param timeout - 等待连接空闲的上限(毫秒)
86
+ * @returns 已挂起的事务
87
+ * @throws {@link RxDBAdapterDesktopError} `transaction_unavailable`:到期时连接仍被别的事务占着
88
+ */
89
+ export declare const suspendTransaction: (runtime: ElectronPgliteRuntime, timeout: number) => Promise<SuspendedTransaction>;
90
+ //# sourceMappingURL=pglite-host-runtime.d.ts.map