@lhremote/core 0.0.0 → 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.
Files changed (218) hide show
  1. package/LICENSE +661 -0
  2. package/dist/cdp/client.d.ts +100 -0
  3. package/dist/cdp/client.d.ts.map +1 -0
  4. package/dist/cdp/client.integration.test.d.ts +2 -0
  5. package/dist/cdp/client.integration.test.d.ts.map +1 -0
  6. package/dist/cdp/client.integration.test.js +70 -0
  7. package/dist/cdp/client.integration.test.js.map +1 -0
  8. package/dist/cdp/client.js +286 -0
  9. package/dist/cdp/client.js.map +1 -0
  10. package/dist/cdp/client.test.d.ts +2 -0
  11. package/dist/cdp/client.test.d.ts.map +1 -0
  12. package/dist/cdp/client.test.js +269 -0
  13. package/dist/cdp/client.test.js.map +1 -0
  14. package/dist/cdp/discovery.d.ts +14 -0
  15. package/dist/cdp/discovery.d.ts.map +1 -0
  16. package/dist/cdp/discovery.integration.test.d.ts +2 -0
  17. package/dist/cdp/discovery.integration.test.d.ts.map +1 -0
  18. package/dist/cdp/discovery.integration.test.js +25 -0
  19. package/dist/cdp/discovery.integration.test.js.map +1 -0
  20. package/dist/cdp/discovery.js +31 -0
  21. package/dist/cdp/discovery.js.map +1 -0
  22. package/dist/cdp/discovery.test.d.ts +2 -0
  23. package/dist/cdp/discovery.test.d.ts.map +1 -0
  24. package/dist/cdp/discovery.test.js +54 -0
  25. package/dist/cdp/discovery.test.js.map +1 -0
  26. package/dist/cdp/errors.d.ts +28 -0
  27. package/dist/cdp/errors.d.ts.map +1 -0
  28. package/dist/cdp/errors.js +40 -0
  29. package/dist/cdp/errors.js.map +1 -0
  30. package/dist/cdp/errors.test.d.ts +2 -0
  31. package/dist/cdp/errors.test.d.ts.map +1 -0
  32. package/dist/cdp/errors.test.js +35 -0
  33. package/dist/cdp/errors.test.js.map +1 -0
  34. package/dist/cdp/index.d.ts +5 -0
  35. package/dist/cdp/index.d.ts.map +1 -0
  36. package/dist/cdp/index.js +5 -0
  37. package/dist/cdp/index.js.map +1 -0
  38. package/dist/cdp/instance-discovery.d.ts +30 -0
  39. package/dist/cdp/instance-discovery.d.ts.map +1 -0
  40. package/dist/cdp/instance-discovery.integration.test.d.ts +2 -0
  41. package/dist/cdp/instance-discovery.integration.test.d.ts.map +1 -0
  42. package/dist/cdp/instance-discovery.integration.test.js +34 -0
  43. package/dist/cdp/instance-discovery.integration.test.js.map +1 -0
  44. package/dist/cdp/instance-discovery.js +130 -0
  45. package/dist/cdp/instance-discovery.js.map +1 -0
  46. package/dist/cdp/instance-discovery.test.d.ts +2 -0
  47. package/dist/cdp/instance-discovery.test.d.ts.map +1 -0
  48. package/dist/cdp/instance-discovery.test.js +110 -0
  49. package/dist/cdp/instance-discovery.test.js.map +1 -0
  50. package/dist/cdp/testing/launch-chromium.d.ts +24 -0
  51. package/dist/cdp/testing/launch-chromium.d.ts.map +1 -0
  52. package/dist/cdp/testing/launch-chromium.js +90 -0
  53. package/dist/cdp/testing/launch-chromium.js.map +1 -0
  54. package/dist/db/client.d.ts +13 -0
  55. package/dist/db/client.d.ts.map +1 -0
  56. package/dist/db/client.integration.test.d.ts +2 -0
  57. package/dist/db/client.integration.test.d.ts.map +1 -0
  58. package/dist/db/client.integration.test.js +59 -0
  59. package/dist/db/client.integration.test.js.map +1 -0
  60. package/dist/db/client.js +17 -0
  61. package/dist/db/client.js.map +1 -0
  62. package/dist/db/client.test.d.ts +2 -0
  63. package/dist/db/client.test.d.ts.map +1 -0
  64. package/dist/db/client.test.js +46 -0
  65. package/dist/db/client.test.js.map +1 -0
  66. package/dist/db/discovery.d.ts +14 -0
  67. package/dist/db/discovery.d.ts.map +1 -0
  68. package/dist/db/discovery.integration.test.d.ts +2 -0
  69. package/dist/db/discovery.integration.test.d.ts.map +1 -0
  70. package/dist/db/discovery.integration.test.js +99 -0
  71. package/dist/db/discovery.integration.test.js.map +1 -0
  72. package/dist/db/discovery.js +74 -0
  73. package/dist/db/discovery.js.map +1 -0
  74. package/dist/db/discovery.test.d.ts +2 -0
  75. package/dist/db/discovery.test.d.ts.map +1 -0
  76. package/dist/db/discovery.test.js +123 -0
  77. package/dist/db/discovery.test.js.map +1 -0
  78. package/dist/db/errors.d.ts +21 -0
  79. package/dist/db/errors.d.ts.map +1 -0
  80. package/dist/db/errors.js +33 -0
  81. package/dist/db/errors.js.map +1 -0
  82. package/dist/db/errors.test.d.ts +2 -0
  83. package/dist/db/errors.test.d.ts.map +1 -0
  84. package/dist/db/errors.test.js +32 -0
  85. package/dist/db/errors.test.js.map +1 -0
  86. package/dist/db/index.d.ts +5 -0
  87. package/dist/db/index.d.ts.map +1 -0
  88. package/dist/db/index.js +5 -0
  89. package/dist/db/index.js.map +1 -0
  90. package/dist/db/repositories/index.d.ts +2 -0
  91. package/dist/db/repositories/index.d.ts.map +1 -0
  92. package/dist/db/repositories/index.js +2 -0
  93. package/dist/db/repositories/index.js.map +1 -0
  94. package/dist/db/repositories/profile.d.ts +32 -0
  95. package/dist/db/repositories/profile.d.ts.map +1 -0
  96. package/dist/db/repositories/profile.integration.test.d.ts +2 -0
  97. package/dist/db/repositories/profile.integration.test.d.ts.map +1 -0
  98. package/dist/db/repositories/profile.integration.test.js +119 -0
  99. package/dist/db/repositories/profile.integration.test.js.map +1 -0
  100. package/dist/db/repositories/profile.js +126 -0
  101. package/dist/db/repositories/profile.js.map +1 -0
  102. package/dist/db/repositories/profile.test.d.ts +2 -0
  103. package/dist/db/repositories/profile.test.d.ts.map +1 -0
  104. package/dist/db/repositories/profile.test.js +126 -0
  105. package/dist/db/repositories/profile.test.js.map +1 -0
  106. package/dist/db/testing/create-fixture.d.ts +8 -0
  107. package/dist/db/testing/create-fixture.d.ts.map +1 -0
  108. package/dist/db/testing/create-fixture.js +286 -0
  109. package/dist/db/testing/create-fixture.js.map +1 -0
  110. package/dist/db/testing/open-fixture.d.ts +14 -0
  111. package/dist/db/testing/open-fixture.d.ts.map +1 -0
  112. package/dist/db/testing/open-fixture.js +21 -0
  113. package/dist/db/testing/open-fixture.js.map +1 -0
  114. package/dist/index.d.ts +5 -1
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +7 -1
  117. package/dist/index.js.map +1 -1
  118. package/dist/services/app.d.ts +62 -0
  119. package/dist/services/app.d.ts.map +1 -0
  120. package/dist/services/app.js +198 -0
  121. package/dist/services/app.js.map +1 -0
  122. package/dist/services/app.test.d.ts +2 -0
  123. package/dist/services/app.test.d.ts.map +1 -0
  124. package/dist/services/app.test.js +265 -0
  125. package/dist/services/app.test.js.map +1 -0
  126. package/dist/services/errors.d.ts +45 -0
  127. package/dist/services/errors.d.ts.map +1 -0
  128. package/dist/services/errors.js +66 -0
  129. package/dist/services/errors.js.map +1 -0
  130. package/dist/services/errors.test.d.ts +2 -0
  131. package/dist/services/errors.test.d.ts.map +1 -0
  132. package/dist/services/errors.test.js +71 -0
  133. package/dist/services/errors.test.js.map +1 -0
  134. package/dist/services/index.d.ts +8 -0
  135. package/dist/services/index.d.ts.map +1 -0
  136. package/dist/services/index.js +8 -0
  137. package/dist/services/index.js.map +1 -0
  138. package/dist/services/instance-lifecycle.d.ts +38 -0
  139. package/dist/services/instance-lifecycle.d.ts.map +1 -0
  140. package/dist/services/instance-lifecycle.js +87 -0
  141. package/dist/services/instance-lifecycle.js.map +1 -0
  142. package/dist/services/instance-lifecycle.test.d.ts +2 -0
  143. package/dist/services/instance-lifecycle.test.d.ts.map +1 -0
  144. package/dist/services/instance-lifecycle.test.js +152 -0
  145. package/dist/services/instance-lifecycle.test.js.map +1 -0
  146. package/dist/services/instance.d.ts +50 -0
  147. package/dist/services/instance.d.ts.map +1 -0
  148. package/dist/services/instance.js +121 -0
  149. package/dist/services/instance.js.map +1 -0
  150. package/dist/services/instance.test.d.ts +2 -0
  151. package/dist/services/instance.test.d.ts.map +1 -0
  152. package/dist/services/instance.test.js +181 -0
  153. package/dist/services/instance.test.js.map +1 -0
  154. package/dist/services/launcher.d.ts +51 -0
  155. package/dist/services/launcher.d.ts.map +1 -0
  156. package/dist/services/launcher.js +147 -0
  157. package/dist/services/launcher.js.map +1 -0
  158. package/dist/services/launcher.test.d.ts +2 -0
  159. package/dist/services/launcher.test.d.ts.map +1 -0
  160. package/dist/services/launcher.test.js +126 -0
  161. package/dist/services/launcher.test.js.map +1 -0
  162. package/dist/services/profile.d.ts +44 -0
  163. package/dist/services/profile.d.ts.map +1 -0
  164. package/dist/services/profile.js +83 -0
  165. package/dist/services/profile.js.map +1 -0
  166. package/dist/services/profile.test.d.ts +2 -0
  167. package/dist/services/profile.test.d.ts.map +1 -0
  168. package/dist/services/profile.test.js +145 -0
  169. package/dist/services/profile.test.js.map +1 -0
  170. package/dist/services/status.d.ts +33 -0
  171. package/dist/services/status.d.ts.map +1 -0
  172. package/dist/services/status.js +76 -0
  173. package/dist/services/status.js.map +1 -0
  174. package/dist/services/status.test.d.ts +2 -0
  175. package/dist/services/status.test.d.ts.map +1 -0
  176. package/dist/services/status.test.js +207 -0
  177. package/dist/services/status.test.js.map +1 -0
  178. package/dist/testing/e2e-helpers.d.ts +41 -0
  179. package/dist/testing/e2e-helpers.d.ts.map +1 -0
  180. package/dist/testing/e2e-helpers.js +111 -0
  181. package/dist/testing/e2e-helpers.js.map +1 -0
  182. package/dist/types/account.d.ts +13 -0
  183. package/dist/types/account.d.ts.map +1 -0
  184. package/dist/types/account.js +2 -0
  185. package/dist/types/account.js.map +1 -0
  186. package/dist/types/account.test.d.ts +2 -0
  187. package/dist/types/account.test.d.ts.map +1 -0
  188. package/dist/types/account.test.js +24 -0
  189. package/dist/types/account.test.js.map +1 -0
  190. package/dist/types/cdp.d.ts +18 -0
  191. package/dist/types/cdp.d.ts.map +1 -0
  192. package/dist/types/cdp.js +2 -0
  193. package/dist/types/cdp.js.map +1 -0
  194. package/dist/types/cdp.test.d.ts +2 -0
  195. package/dist/types/cdp.test.d.ts.map +1 -0
  196. package/dist/types/cdp.test.js +28 -0
  197. package/dist/types/cdp.test.js.map +1 -0
  198. package/dist/types/index.d.ts +4 -0
  199. package/dist/types/index.d.ts.map +1 -0
  200. package/dist/types/index.js +2 -0
  201. package/dist/types/index.js.map +1 -0
  202. package/dist/types/instance.d.ts +36 -0
  203. package/dist/types/instance.d.ts.map +1 -0
  204. package/dist/types/instance.js +8 -0
  205. package/dist/types/instance.js.map +1 -0
  206. package/dist/types/instance.test.d.ts +2 -0
  207. package/dist/types/instance.test.d.ts.map +1 -0
  208. package/dist/types/instance.test.js +57 -0
  209. package/dist/types/instance.test.js.map +1 -0
  210. package/dist/types/profile.d.ts +50 -0
  211. package/dist/types/profile.d.ts.map +1 -0
  212. package/dist/types/profile.js +8 -0
  213. package/dist/types/profile.js.map +1 -0
  214. package/dist/types/profile.test.d.ts +2 -0
  215. package/dist/types/profile.test.d.ts.map +1 -0
  216. package/dist/types/profile.test.js +103 -0
  217. package/dist/types/profile.test.js.map +1 -0
  218. package/package.json +20 -8
@@ -0,0 +1,34 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
2
+ import { pidToPorts, portToPid } from "pid-port";
3
+ import psList from "ps-list";
4
+ import { launchChromium, } from "./testing/launch-chromium.js";
5
+ describe("instance-discovery packages (integration)", () => {
6
+ let chromium;
7
+ beforeAll(async () => {
8
+ chromium = await launchChromium();
9
+ }, 30_000);
10
+ afterAll(async () => {
11
+ await chromium.close();
12
+ });
13
+ it("portToPid should find the Chromium process by port", async () => {
14
+ const pid = await portToPid({ port: chromium.port, host: "*" });
15
+ expect(pid).toEqual(expect.any(Number));
16
+ expect(pid).toBeGreaterThan(0);
17
+ });
18
+ it("psList should include the Chromium process with correct ppid", async () => {
19
+ const processes = await psList();
20
+ const chromiumProc = processes.find((p) => p.pid === chromium.process.pid);
21
+ expect(chromiumProc).toBeDefined();
22
+ expect(chromiumProc?.ppid).toEqual(expect.any(Number));
23
+ });
24
+ it("pidToPorts should include the Chromium CDP port", async () => {
25
+ const pid = await portToPid({ port: chromium.port, host: "*" });
26
+ if (pid === undefined) {
27
+ throw new Error("Expected portToPid to return a PID");
28
+ }
29
+ const ports = await pidToPorts(pid);
30
+ expect(ports).toBeInstanceOf(Set);
31
+ expect(ports.has(chromium.port)).toBe(true);
32
+ });
33
+ });
34
+ //# sourceMappingURL=instance-discovery.integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance-discovery.integration.test.js","sourceRoot":"","sources":["../../src/cdp/instance-discovery.integration.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,MAAM,MAAM,SAAS,CAAC;AAC7B,OAAO,EACL,cAAc,GAEf,MAAM,8BAA8B,CAAC;AAEtC,QAAQ,CAAC,2CAA2C,EAAE,GAAG,EAAE;IACzD,IAAI,QAA0B,CAAC;IAE/B,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,QAAQ,GAAG,MAAM,cAAc,EAAE,CAAC;IACpC,CAAC,EAAE,MAAM,CAAC,CAAC;IAEX,QAAQ,CAAC,KAAK,IAAI,EAAE;QAClB,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QAEhE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,SAAS,GAAG,MAAM,MAAM,EAAE,CAAC;QACjC,MAAM,YAAY,GAAG,SAAS,CAAC,IAAI,CACjC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC,OAAO,CAAC,GAAG,CACtC,CAAC;QAEF,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QAChE,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;QAEpC,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,130 @@
1
+ import { pidToPorts, portToPid } from "pid-port";
2
+ import psList from "ps-list";
3
+ /**
4
+ * Default CDP port used by the LinkedHelper launcher process.
5
+ */
6
+ const DEFAULT_LAUNCHER_PORT = 9222;
7
+ /**
8
+ * Discover the dynamic CDP port of a running LinkedHelper instance process.
9
+ *
10
+ * LinkedHelper spawns a separate Electron process for each LinkedIn account.
11
+ * That process listens for CDP connections on a dynamic port that changes
12
+ * every session. This function discovers the port cross-platform using
13
+ * `pid-port` and `ps-list`.
14
+ *
15
+ * The heuristic is:
16
+ * 1. Find the launcher PID by looking for a process listening on `launcherPort`.
17
+ * 2. Find child processes of the launcher.
18
+ * 3. Among those children, find one listening on a TCP port that is NOT the
19
+ * launcher port and that responds to the CDP `/json/list` endpoint.
20
+ *
21
+ * The instance process may listen on multiple ports (e.g. a web content
22
+ * server and a CDP debugging server). We probe each candidate with an
23
+ * HTTP fetch to `/json/list` to ensure we return the actual CDP port.
24
+ *
25
+ * @param launcherPort - The known launcher CDP port (default 9222).
26
+ * @returns The dynamic instance CDP port, or `null` if no running instance was found.
27
+ */
28
+ export async function discoverInstancePort(launcherPort = DEFAULT_LAUNCHER_PORT) {
29
+ const launcherPid = await findPidListeningOn(launcherPort);
30
+ if (launcherPid === null) {
31
+ return null;
32
+ }
33
+ const childPids = await findChildPids(launcherPid);
34
+ if (childPids.length === 0) {
35
+ return null;
36
+ }
37
+ const results = await Promise.all(childPids.map((pid) => findCdpPort(pid, launcherPort)));
38
+ return results.find((port) => port !== null) ?? null;
39
+ }
40
+ /**
41
+ * Find the PID of a process listening on the given TCP port.
42
+ */
43
+ async function findPidListeningOn(port) {
44
+ try {
45
+ const pid = await portToPid({ port, host: "*" });
46
+ return pid ?? null;
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ /**
53
+ * Find PIDs of child processes for the given parent PID.
54
+ */
55
+ async function findChildPids(parentPid) {
56
+ try {
57
+ const processes = await psList();
58
+ return processes
59
+ .filter((p) => p.ppid === parentPid)
60
+ .map((p) => p.pid);
61
+ }
62
+ catch {
63
+ return [];
64
+ }
65
+ }
66
+ /**
67
+ * Find the CDP debugging port for the given PID.
68
+ *
69
+ * Tries each listening port (excluding `excludePort`) with an HTTP fetch
70
+ * to `/json/list`. Returns the first port that responds successfully.
71
+ */
72
+ async function findCdpPort(pid, excludePort) {
73
+ let ports;
74
+ try {
75
+ ports = await pidToPorts(pid);
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ const candidates = [...ports].filter((p) => p !== excludePort);
81
+ if (candidates.length === 0) {
82
+ return null;
83
+ }
84
+ try {
85
+ return await Promise.any(candidates.map(async (port) => {
86
+ if (await isCdpPort(port)) {
87
+ return port;
88
+ }
89
+ throw new Error("not CDP");
90
+ }));
91
+ }
92
+ catch {
93
+ // AggregateError — no candidate port responded to CDP
94
+ return null;
95
+ }
96
+ }
97
+ /**
98
+ * Find and forcefully kill all instance child processes of the launcher.
99
+ *
100
+ * Use as a last resort when graceful `stopInstance()` fails and
101
+ * the instance process needs to be terminated at the OS level.
102
+ */
103
+ export async function killInstanceProcesses(launcherPort) {
104
+ const launcherPid = await findPidListeningOn(launcherPort);
105
+ if (launcherPid === null) {
106
+ return;
107
+ }
108
+ const childPids = await findChildPids(launcherPid);
109
+ for (const pid of childPids) {
110
+ try {
111
+ process.kill(pid, "SIGKILL");
112
+ }
113
+ catch {
114
+ // Process may already be dead
115
+ }
116
+ }
117
+ }
118
+ /**
119
+ * Check whether a port exposes a CDP `/json/list` endpoint.
120
+ */
121
+ async function isCdpPort(port) {
122
+ try {
123
+ const response = await fetch(`http://127.0.0.1:${String(port)}/json/list`);
124
+ return response.ok;
125
+ }
126
+ catch {
127
+ return false;
128
+ }
129
+ }
130
+ //# sourceMappingURL=instance-discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance-discovery.js","sourceRoot":"","sources":["../../src/cdp/instance-discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=instance-discovery.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance-discovery.test.d.ts","sourceRoot":"","sources":["../../src/cdp/instance-discovery.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,110 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { discoverInstancePort } from "./instance-discovery.js";
3
+ vi.mock("pid-port", () => ({
4
+ portToPid: vi.fn(),
5
+ pidToPorts: vi.fn(),
6
+ }));
7
+ vi.mock("ps-list", () => ({
8
+ default: vi.fn(),
9
+ }));
10
+ import { pidToPorts, portToPid } from "pid-port";
11
+ import psList from "ps-list";
12
+ describe("discoverInstancePort", () => {
13
+ beforeEach(() => {
14
+ // Mock fetch to simulate a responding CDP endpoint
15
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("[]", { status: 200 }));
16
+ });
17
+ afterEach(() => {
18
+ vi.restoreAllMocks();
19
+ });
20
+ it("should return null when launcher is not running", async () => {
21
+ vi.mocked(portToPid).mockRejectedValue(new Error("Could not find a process that uses port `9222`"));
22
+ const port = await discoverInstancePort(9222);
23
+ expect(port).toBeNull();
24
+ });
25
+ it("should return null when portToPid returns undefined", async () => {
26
+ vi.mocked(portToPid).mockResolvedValue(undefined);
27
+ const port = await discoverInstancePort(9222);
28
+ expect(port).toBeNull();
29
+ });
30
+ it("should return null when launcher has no children", async () => {
31
+ vi.mocked(portToPid).mockResolvedValue(12345);
32
+ vi.mocked(psList).mockResolvedValue([
33
+ { pid: 99999, name: "other", ppid: 1 },
34
+ ]);
35
+ const port = await discoverInstancePort(9222);
36
+ expect(port).toBeNull();
37
+ });
38
+ it("should discover instance port from child process", async () => {
39
+ vi.mocked(portToPid).mockResolvedValue(12345);
40
+ vi.mocked(psList).mockResolvedValue([
41
+ { pid: 12346, name: "electron", ppid: 12345 },
42
+ { pid: 99999, name: "other", ppid: 1 },
43
+ ]);
44
+ vi.mocked(pidToPorts).mockResolvedValue(new Set([55123]));
45
+ const port = await discoverInstancePort(9222);
46
+ expect(port).toBe(55123);
47
+ });
48
+ it("should skip ports matching the launcher port", async () => {
49
+ vi.mocked(portToPid).mockResolvedValue(12345);
50
+ vi.mocked(psList).mockResolvedValue([
51
+ { pid: 12346, name: "electron", ppid: 12345 },
52
+ ]);
53
+ vi.mocked(pidToPorts).mockResolvedValue(new Set([9222, 55999]));
54
+ const port = await discoverInstancePort(9222);
55
+ expect(port).toBe(55999);
56
+ });
57
+ it("should use default launcher port 9222", async () => {
58
+ vi.mocked(portToPid).mockResolvedValue(12345);
59
+ vi.mocked(psList).mockResolvedValue([
60
+ { pid: 12346, name: "electron", ppid: 12345 },
61
+ ]);
62
+ vi.mocked(pidToPorts).mockResolvedValue(new Set([44444]));
63
+ const port = await discoverInstancePort();
64
+ expect(port).toBe(44444);
65
+ expect(portToPid).toHaveBeenCalledWith({ port: 9222, host: "*" });
66
+ });
67
+ it("should return null when pidToPorts throws", async () => {
68
+ vi.mocked(portToPid).mockResolvedValue(12345);
69
+ vi.mocked(psList).mockResolvedValue([
70
+ { pid: 12346, name: "electron", ppid: 12345 },
71
+ ]);
72
+ vi.mocked(pidToPorts).mockRejectedValue(new Error("failed"));
73
+ const port = await discoverInstancePort(9222);
74
+ expect(port).toBeNull();
75
+ });
76
+ it("should return null when psList throws", async () => {
77
+ vi.mocked(portToPid).mockResolvedValue(12345);
78
+ vi.mocked(psList).mockRejectedValue(new Error("failed"));
79
+ const port = await discoverInstancePort(9222);
80
+ expect(port).toBeNull();
81
+ });
82
+ it("should skip ports that do not respond to CDP", async () => {
83
+ vi.mocked(portToPid).mockResolvedValue(12345);
84
+ vi.mocked(psList).mockResolvedValue([
85
+ { pid: 12346, name: "electron", ppid: 12345 },
86
+ ]);
87
+ vi.mocked(pidToPorts).mockResolvedValue(new Set([50000, 50001]));
88
+ // Port 50000 rejects (not CDP), port 50001 responds
89
+ vi.mocked(globalThis.fetch).mockImplementation(async (input) => {
90
+ const url = String(input);
91
+ if (url.includes(":50000/")) {
92
+ throw new Error("ECONNREFUSED");
93
+ }
94
+ return new Response("[]", { status: 200 });
95
+ });
96
+ const port = await discoverInstancePort(9222);
97
+ expect(port).toBe(50001);
98
+ });
99
+ it("should return null when no port responds to CDP", async () => {
100
+ vi.mocked(portToPid).mockResolvedValue(12345);
101
+ vi.mocked(psList).mockResolvedValue([
102
+ { pid: 12346, name: "electron", ppid: 12345 },
103
+ ]);
104
+ vi.mocked(pidToPorts).mockResolvedValue(new Set([50000]));
105
+ vi.mocked(globalThis.fetch).mockRejectedValue(new Error("ECONNREFUSED"));
106
+ const port = await discoverInstancePort(9222);
107
+ expect(port).toBeNull();
108
+ });
109
+ });
110
+ //# sourceMappingURL=instance-discovery.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance-discovery.test.js","sourceRoot":"","sources":["../../src/cdp/instance-discovery.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ /** Result of launching a test Chromium instance. */
3
+ export interface ChromiumInstance {
4
+ /** CDP debugging port. */
5
+ port: number;
6
+ /** The spawned Chromium process. */
7
+ process: ChildProcess;
8
+ /** Gracefully shut down the Chromium instance. */
9
+ close: () => Promise<void>;
10
+ }
11
+ /**
12
+ * Launch a headless Chromium instance with CDP enabled on a random free port.
13
+ *
14
+ * Uses the Chromium binary installed by `playwright-core`. The instance
15
+ * is configured for test isolation (temp profile, no sandbox).
16
+ *
17
+ * @param options.timeout - Maximum time (ms) to wait for CDP to become
18
+ * available (default 10 000).
19
+ * @returns A {@link ChromiumInstance} handle.
20
+ */
21
+ export declare function launchChromium(options?: {
22
+ timeout?: number;
23
+ }): Promise<ChromiumInstance>;
24
+ //# sourceMappingURL=launch-chromium.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-chromium.d.ts","sourceRoot":"","sources":["../../../src/cdp/testing/launch-chromium.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAS,MAAM,oBAAoB,CAAC;AAO9D,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC/B,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,OAAO,EAAE,YAAY,CAAC;IACtB,kDAAkD;IAClD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAwBD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,OAAO,CAAC,EAAE;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgE5B"}
@@ -0,0 +1,90 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createServer } from "node:net";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { chromium } from "playwright-core";
6
+ import { discoverTargets } from "../discovery.js";
7
+ /**
8
+ * Find a free TCP port by briefly binding to port 0.
9
+ */
10
+ async function findFreePort() {
11
+ return new Promise((resolve, reject) => {
12
+ const server = createServer();
13
+ server.listen(0, "127.0.0.1", () => {
14
+ const addr = server.address();
15
+ if (addr === null || typeof addr === "string") {
16
+ server.close();
17
+ reject(new Error("Failed to get port from server address"));
18
+ return;
19
+ }
20
+ const { port } = addr;
21
+ server.close(() => {
22
+ resolve(port);
23
+ });
24
+ });
25
+ server.on("error", reject);
26
+ });
27
+ }
28
+ /**
29
+ * Launch a headless Chromium instance with CDP enabled on a random free port.
30
+ *
31
+ * Uses the Chromium binary installed by `playwright-core`. The instance
32
+ * is configured for test isolation (temp profile, no sandbox).
33
+ *
34
+ * @param options.timeout - Maximum time (ms) to wait for CDP to become
35
+ * available (default 10 000).
36
+ * @returns A {@link ChromiumInstance} handle.
37
+ */
38
+ export async function launchChromium(options) {
39
+ const timeout = options?.timeout ?? 10_000;
40
+ const port = await findFreePort();
41
+ const userDataDir = join(tmpdir(), `lhremote-cdp-test-${port.toString()}-${Date.now().toString(36)}`);
42
+ const executablePath = chromium.executablePath();
43
+ const child = spawn(executablePath, [
44
+ `--remote-debugging-port=${port.toString()}`,
45
+ `--user-data-dir=${userDataDir}`,
46
+ "--headless",
47
+ "--no-sandbox",
48
+ "--disable-gpu",
49
+ "--disable-extensions",
50
+ "--use-mock-keychain",
51
+ ], { stdio: "ignore" });
52
+ // Wait for CDP endpoint to become available
53
+ const deadline = Date.now() + timeout;
54
+ let ready = false;
55
+ while (Date.now() < deadline) {
56
+ try {
57
+ const targets = await discoverTargets(port);
58
+ if (targets.length > 0) {
59
+ ready = true;
60
+ break;
61
+ }
62
+ }
63
+ catch {
64
+ // Not ready yet
65
+ }
66
+ await new Promise((r) => setTimeout(r, 100));
67
+ }
68
+ if (!ready) {
69
+ child.kill("SIGKILL");
70
+ throw new Error(`Chromium CDP endpoint did not become available on port ${port.toString()} within ${timeout.toString()}ms`);
71
+ }
72
+ const close = async () => {
73
+ if (child.exitCode !== null) {
74
+ return;
75
+ }
76
+ child.kill("SIGTERM");
77
+ await new Promise((resolve) => {
78
+ const timer = setTimeout(() => {
79
+ child.kill("SIGKILL");
80
+ resolve();
81
+ }, 5_000);
82
+ child.on("exit", () => {
83
+ clearTimeout(timer);
84
+ resolve();
85
+ });
86
+ });
87
+ };
88
+ return { port, process: child, close };
89
+ }
90
+ //# sourceMappingURL=launch-chromium.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-chromium.js","sourceRoot":"","sources":["../../../src/cdp/testing/launch-chromium.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAYlD;;GAEG;AACH,KAAK,UAAU,YAAY;IACzB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;YACjC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC9C,MAAM,CAAC,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC,CAAC;gBAC5D,OAAO;YACT,CAAC;YACD,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;YACtB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;gBAChB,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAEpC;IACC,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,MAAM,CAAC;IAC3C,MAAM,IAAI,GAAG,MAAM,YAAY,EAAE,CAAC;IAClC,MAAM,WAAW,GAAG,IAAI,CACtB,MAAM,EAAE,EACR,qBAAqB,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAClE,CAAC;IAEF,MAAM,cAAc,GAAG,QAAQ,CAAC,cAAc,EAAE,CAAC;IACjD,MAAM,KAAK,GAAG,KAAK,CACjB,cAAc,EACd;QACE,2BAA2B,IAAI,CAAC,QAAQ,EAAE,EAAE;QAC5C,mBAAmB,WAAW,EAAE;QAChC,YAAY;QACZ,cAAc;QACd,eAAe;QACf,sBAAsB;QACtB,qBAAqB;KACtB,EACD,EAAE,KAAK,EAAE,QAAQ,EAAE,CACpB,CAAC;IAEF,4CAA4C;IAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;IACtC,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,KAAK,GAAG,IAAI,CAAC;gBACb,MAAM;YACR,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,gBAAgB;QAClB,CAAC;QACD,MAAM,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,0DAA0D,IAAI,CAAC,QAAQ,EAAE,WAAW,OAAO,CAAC,QAAQ,EAAE,IAAI,CAC3G,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,IAAmB,EAAE;QACtC,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAClC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACtB,OAAO,EAAE,CAAC;YACZ,CAAC,EAAE,KAAK,CAAC,CAAC;YACV,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;gBACpB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACzC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import Database from "better-sqlite3";
2
+ /**
3
+ * Read-only SQLite client for querying a LinkedHelper database.
4
+ *
5
+ * Opens the database in read-only mode. LinkedHelper uses WAL
6
+ * journaling, so concurrent reads do not block writes.
7
+ */
8
+ export declare class DatabaseClient {
9
+ readonly db: Database.Database;
10
+ constructor(dbPath: string);
11
+ close(): void;
12
+ }
13
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC;;;;;GAKG;AACH,qBAAa,cAAc;IACzB,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;gBAEnB,MAAM,EAAE,MAAM;IAI1B,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=client.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.integration.test.d.ts","sourceRoot":"","sources":["../../src/db/client.integration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,59 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { DatabaseClient } from "./client.js";
3
+ import { FIXTURE_PATH } from "./testing/open-fixture.js";
4
+ describe("DatabaseClient (integration)", () => {
5
+ let client;
6
+ afterEach(() => {
7
+ client?.close();
8
+ client = undefined;
9
+ });
10
+ it("opens the fixture database in read-only mode", () => {
11
+ client = new DatabaseClient(FIXTURE_PATH);
12
+ expect(client.db.readonly).toBe(true);
13
+ });
14
+ it("reads data from the real schema", () => {
15
+ client = new DatabaseClient(FIXTURE_PATH);
16
+ const row = client.db
17
+ .prepare("SELECT first_name FROM person_mini_profile WHERE person_id = ?")
18
+ .get(1);
19
+ expect(row?.first_name).toBe("Ada");
20
+ });
21
+ it("rejects write operations", () => {
22
+ client = new DatabaseClient(FIXTURE_PATH);
23
+ const { db } = client;
24
+ expect(() => db.exec("INSERT INTO people (id) VALUES (9999)")).toThrow(/readonly/i);
25
+ });
26
+ it("throws when the database file does not exist", () => {
27
+ expect(() => new DatabaseClient("/nonexistent/path/to/lh.db")).toThrow();
28
+ });
29
+ it("can query all profile-related tables", () => {
30
+ client = new DatabaseClient(FIXTURE_PATH);
31
+ const tables = [
32
+ "people",
33
+ "person_mini_profile",
34
+ "person_external_ids",
35
+ "person_current_position",
36
+ "person_positions",
37
+ "person_education",
38
+ "skills",
39
+ "person_skill",
40
+ "person_email",
41
+ ];
42
+ for (const table of tables) {
43
+ const row = client.db
44
+ .prepare(`SELECT COUNT(*) AS cnt FROM ${table}`)
45
+ .get();
46
+ expect(row.cnt).toBeGreaterThanOrEqual(0);
47
+ }
48
+ });
49
+ it("close() releases the file handle", () => {
50
+ client = new DatabaseClient(FIXTURE_PATH);
51
+ const { db } = client;
52
+ client.close();
53
+ // After close, queries should throw
54
+ expect(() => db.prepare("SELECT 1").get()).toThrow();
55
+ // Prevent afterEach from double-closing
56
+ client = undefined;
57
+ });
58
+ });
59
+ //# sourceMappingURL=client.integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.integration.test.js","sourceRoot":"","sources":["../../src/db/client.integration.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,IAAI,MAAkC,CAAC;IAEvC,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,MAAM,GAAG,SAAS,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,GAAG,IAAI,cAAc,CAAC,YAAY,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,GAAG,IAAI,cAAc,CAAC,YAAY,CAAC,CAAC;QAE1C,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE;aAClB,OAAO,CAAC,gEAAgE,CAAC;aACzE,GAAG,CAAC,CAAC,CAAuC,CAAC;QAEhD,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,GAAG,IAAI,cAAc,CAAC,YAAY,CAAC,CAAC;QAC1C,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;QAEtB,MAAM,CAAC,GAAG,EAAE,CACV,EAAE,CAAC,IAAI,CAAC,uCAAuC,CAAC,CACjD,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CACJ,GAAG,EAAE,CAAC,IAAI,cAAc,CAAC,4BAA4B,CAAC,CACvD,CAAC,OAAO,EAAE,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,GAAG,IAAI,cAAc,CAAC,YAAY,CAAC,CAAC;QAE1C,MAAM,MAAM,GAAG;YACb,QAAQ;YACR,qBAAqB;YACrB,qBAAqB;YACrB,yBAAyB;YACzB,kBAAkB;YAClB,kBAAkB;YAClB,QAAQ;YACR,cAAc;YACd,cAAc;SACf,CAAC;QAEF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE;iBAClB,OAAO,CAAC,+BAA+B,KAAK,EAAE,CAAC;iBAC/C,GAAG,EAAqB,CAAC;YAC5B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,GAAG,IAAI,cAAc,CAAC,YAAY,CAAC,CAAC;QAC1C,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;QACtB,MAAM,CAAC,KAAK,EAAE,CAAC;QAEf,oCAAoC;QACpC,MAAM,CAAC,GAAG,EAAE,CACV,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,CAC7B,CAAC,OAAO,EAAE,CAAC;QAEZ,wCAAwC;QACxC,MAAM,GAAG,SAAS,CAAC;IACrB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import Database from "better-sqlite3";
2
+ /**
3
+ * Read-only SQLite client for querying a LinkedHelper database.
4
+ *
5
+ * Opens the database in read-only mode. LinkedHelper uses WAL
6
+ * journaling, so concurrent reads do not block writes.
7
+ */
8
+ export class DatabaseClient {
9
+ db;
10
+ constructor(dbPath) {
11
+ this.db = new Database(dbPath, { readonly: true });
12
+ }
13
+ close() {
14
+ this.db.close();
15
+ }
16
+ }
17
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC;;;;;GAKG;AACH,MAAM,OAAO,cAAc;IAChB,EAAE,CAAoB;IAE/B,YAAY,MAAc;QACxB,IAAI,CAAC,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.test.d.ts","sourceRoot":"","sources":["../../src/db/client.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,46 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { DatabaseClient } from "./client.js";
3
+ import { openFixture } from "./testing/open-fixture.js";
4
+ describe("DatabaseClient", () => {
5
+ describe("constructor", () => {
6
+ it("throws when the database file does not exist", () => {
7
+ expect(() => new DatabaseClient("/nonexistent/path/to/lh.db")).toThrow();
8
+ });
9
+ });
10
+ describe("with in-memory fixture", () => {
11
+ let client;
12
+ beforeEach(() => {
13
+ const db = openFixture();
14
+ client = { db };
15
+ });
16
+ afterEach(() => {
17
+ client.db.close();
18
+ });
19
+ it("can read data from the real schema", () => {
20
+ const row = client.db
21
+ .prepare("SELECT first_name FROM person_mini_profile WHERE person_id = ?")
22
+ .get(1);
23
+ expect(row?.first_name).toBe("Ada");
24
+ });
25
+ it("fixture schema includes all profile-related tables", () => {
26
+ const tables = [
27
+ "people",
28
+ "person_mini_profile",
29
+ "person_external_ids",
30
+ "person_current_position",
31
+ "person_positions",
32
+ "person_education",
33
+ "skills",
34
+ "person_skill",
35
+ "person_email",
36
+ ];
37
+ for (const table of tables) {
38
+ const row = client.db
39
+ .prepare(`SELECT COUNT(*) AS cnt FROM ${table}`)
40
+ .get();
41
+ expect(row.cnt).toBeGreaterThanOrEqual(0);
42
+ }
43
+ });
44
+ });
45
+ });
46
+ //# sourceMappingURL=client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.test.js","sourceRoot":"","sources":["../../src/db/client.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAErE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAExD,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,CACJ,GAAG,EAAE,CAAC,IAAI,cAAc,CAAC,4BAA4B,CAAC,CACvD,CAAC,OAAO,EAAE,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;QACtC,IAAI,MAA8C,CAAC;QAEnD,UAAU,CAAC,GAAG,EAAE;YACd,MAAM,EAAE,GAAG,WAAW,EAAE,CAAC;YACzB,MAAM,GAAG,EAAE,EAAE,EAAE,CAAC;QAClB,CAAC,CAAC,CAAC;QAEH,SAAS,CAAC,GAAG,EAAE;YACb,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;YAC5C,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE;iBAClB,OAAO,CAAC,gEAAgE,CAAC;iBACzE,GAAG,CAAC,CAAC,CAAuC,CAAC;YAChD,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;YAC5D,MAAM,MAAM,GAAG;gBACb,QAAQ;gBACR,qBAAqB;gBACrB,qBAAqB;gBACrB,yBAAyB;gBACzB,kBAAkB;gBAClB,kBAAkB;gBAClB,QAAQ;gBACR,cAAc;gBACd,cAAc;aACf,CAAC;YAEF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE;qBAClB,OAAO,CAAC,+BAA+B,KAAK,EAAE,CAAC;qBAC/C,GAAG,EAAqB,CAAC;gBAC5B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Resolves the database file path for a LinkedHelper account.
3
+ *
4
+ * @throws {DatabaseNotFoundError} if the database file does not exist.
5
+ */
6
+ export declare function discoverDatabase(accountId: number): string;
7
+ /**
8
+ * Scans the LinkedHelper partitions directory and returns a map of
9
+ * every account ID to its database file path.
10
+ *
11
+ * Only accounts whose database file actually exists are included.
12
+ */
13
+ export declare function discoverAllDatabases(): Map<number, string>;
14
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/db/discovery.ts"],"names":[],"mappings":"AAqCA;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CA8B1D"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=discovery.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.integration.test.d.ts","sourceRoot":"","sources":["../../src/db/discovery.integration.test.ts"],"names":[],"mappings":""}