@cavelang/cli 0.35.0 → 0.36.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 (327) hide show
  1. package/README.md +224 -12
  2. package/dist/internal/act/action.js +2 -1
  3. package/dist/internal/act/action.js.map +1 -1
  4. package/dist/internal/act/declare.d.ts.map +1 -1
  5. package/dist/internal/act/declare.js +82 -53
  6. package/dist/internal/act/declare.js.map +1 -1
  7. package/dist/internal/act/engine.d.ts +4 -3
  8. package/dist/internal/act/engine.d.ts.map +1 -1
  9. package/dist/internal/act/engine.js +121 -32
  10. package/dist/internal/act/engine.js.map +1 -1
  11. package/dist/internal/automate/cancellation.d.ts +3 -0
  12. package/dist/internal/automate/cancellation.d.ts.map +1 -0
  13. package/dist/internal/automate/cancellation.js +38 -0
  14. package/dist/internal/automate/cancellation.js.map +1 -0
  15. package/dist/internal/automate/declare.d.ts.map +1 -1
  16. package/dist/internal/automate/declare.js +49 -24
  17. package/dist/internal/automate/declare.js.map +1 -1
  18. package/dist/internal/automate/engine.d.ts +7 -3
  19. package/dist/internal/automate/engine.d.ts.map +1 -1
  20. package/dist/internal/automate/engine.js +196 -93
  21. package/dist/internal/automate/engine.js.map +1 -1
  22. package/dist/internal/automate/error-message.d.ts +3 -0
  23. package/dist/internal/automate/error-message.d.ts.map +1 -0
  24. package/dist/internal/automate/error-message.js +10 -0
  25. package/dist/internal/automate/error-message.js.map +1 -0
  26. package/dist/internal/automate/main.d.ts.map +1 -1
  27. package/dist/internal/automate/main.js +238 -35
  28. package/dist/internal/automate/main.js.map +1 -1
  29. package/dist/internal/automate/options.d.ts +4 -0
  30. package/dist/internal/automate/options.d.ts.map +1 -0
  31. package/dist/internal/automate/options.js +19 -0
  32. package/dist/internal/automate/options.js.map +1 -0
  33. package/dist/internal/connect/declared.d.ts +6 -27
  34. package/dist/internal/connect/declared.d.ts.map +1 -1
  35. package/dist/internal/connect/declared.js +165 -76
  36. package/dist/internal/connect/declared.js.map +1 -1
  37. package/dist/internal/connect/error-message.d.ts +3 -0
  38. package/dist/internal/connect/error-message.d.ts.map +1 -0
  39. package/dist/internal/connect/error-message.js +10 -0
  40. package/dist/internal/connect/error-message.js.map +1 -0
  41. package/dist/internal/connect/federation.d.ts +9 -0
  42. package/dist/internal/connect/federation.d.ts.map +1 -0
  43. package/dist/internal/connect/federation.js +19 -0
  44. package/dist/internal/connect/federation.js.map +1 -0
  45. package/dist/internal/connect/fetch-failure.d.ts +3 -0
  46. package/dist/internal/connect/fetch-failure.d.ts.map +1 -0
  47. package/dist/internal/connect/fetch-failure.js +36 -0
  48. package/dist/internal/connect/fetch-failure.js.map +1 -0
  49. package/dist/internal/connect/main.d.ts +1 -0
  50. package/dist/internal/connect/main.d.ts.map +1 -1
  51. package/dist/internal/connect/main.js +288 -114
  52. package/dist/internal/connect/main.js.map +1 -1
  53. package/dist/internal/connect/options.d.ts +3 -0
  54. package/dist/internal/connect/options.d.ts.map +1 -0
  55. package/dist/internal/connect/options.js +9 -0
  56. package/dist/internal/connect/options.js.map +1 -0
  57. package/dist/internal/connect/records.d.ts +3 -0
  58. package/dist/internal/connect/records.d.ts.map +1 -0
  59. package/dist/internal/connect/records.js +21 -0
  60. package/dist/internal/connect/records.js.map +1 -0
  61. package/dist/internal/connect/run.d.ts +1 -18
  62. package/dist/internal/connect/run.d.ts.map +1 -1
  63. package/dist/internal/connect/run.js +115 -55
  64. package/dist/internal/connect/run.js.map +1 -1
  65. package/dist/internal/connect/source.d.ts +3 -1
  66. package/dist/internal/connect/source.d.ts.map +1 -1
  67. package/dist/internal/connect/source.js +226 -47
  68. package/dist/internal/connect/source.js.map +1 -1
  69. package/dist/internal/connect/template.d.ts +2 -2
  70. package/dist/internal/connect/template.d.ts.map +1 -1
  71. package/dist/internal/connect/template.js +25 -9
  72. package/dist/internal/connect/template.js.map +1 -1
  73. package/dist/internal/connect/utf8.d.ts +3 -0
  74. package/dist/internal/connect/utf8.d.ts.map +1 -0
  75. package/dist/internal/connect/utf8.js +12 -0
  76. package/dist/internal/connect/utf8.js.map +1 -0
  77. package/dist/internal/eval/cleanup.d.ts +5 -0
  78. package/dist/internal/eval/cleanup.d.ts.map +1 -0
  79. package/dist/internal/eval/cleanup.js +50 -0
  80. package/dist/internal/eval/cleanup.js.map +1 -0
  81. package/dist/internal/eval/error-message.d.ts +3 -0
  82. package/dist/internal/eval/error-message.d.ts.map +1 -0
  83. package/dist/internal/eval/error-message.js +10 -0
  84. package/dist/internal/eval/error-message.js.map +1 -0
  85. package/dist/internal/eval/judge.d.ts.map +1 -1
  86. package/dist/internal/eval/judge.js +3 -31
  87. package/dist/internal/eval/judge.js.map +1 -1
  88. package/dist/internal/eval/loop.d.ts.map +1 -1
  89. package/dist/internal/eval/loop.js +18 -4
  90. package/dist/internal/eval/loop.js.map +1 -1
  91. package/dist/internal/eval/main.d.ts.map +1 -1
  92. package/dist/internal/eval/main.js +22 -10
  93. package/dist/internal/eval/main.js.map +1 -1
  94. package/dist/internal/eval/options.d.ts +6 -0
  95. package/dist/internal/eval/options.d.ts.map +1 -0
  96. package/dist/internal/eval/options.js +31 -0
  97. package/dist/internal/eval/options.js.map +1 -0
  98. package/dist/internal/eval/queries.d.ts.map +1 -1
  99. package/dist/internal/eval/queries.js +9 -6
  100. package/dist/internal/eval/queries.js.map +1 -1
  101. package/dist/internal/eval/run.d.ts.map +1 -1
  102. package/dist/internal/eval/run.js +112 -24
  103. package/dist/internal/eval/run.js.map +1 -1
  104. package/dist/internal/eval/score.d.ts.map +1 -1
  105. package/dist/internal/eval/score.js +9 -2
  106. package/dist/internal/eval/score.js.map +1 -1
  107. package/dist/internal/eval/suite.d.ts.map +1 -1
  108. package/dist/internal/eval/suite.js +14 -2
  109. package/dist/internal/eval/suite.js.map +1 -1
  110. package/dist/internal/ingest/cleanup.d.ts +5 -0
  111. package/dist/internal/ingest/cleanup.d.ts.map +1 -0
  112. package/dist/internal/ingest/cleanup.js +50 -0
  113. package/dist/internal/ingest/cleanup.js.map +1 -0
  114. package/dist/internal/ingest/content.d.ts +6 -0
  115. package/dist/internal/ingest/content.d.ts.map +1 -0
  116. package/dist/internal/ingest/content.js +25 -0
  117. package/dist/internal/ingest/content.js.map +1 -0
  118. package/dist/internal/ingest/context.d.ts.map +1 -1
  119. package/dist/internal/ingest/context.js +25 -7
  120. package/dist/internal/ingest/context.js.map +1 -1
  121. package/dist/internal/ingest/error-message.d.ts +3 -0
  122. package/dist/internal/ingest/error-message.d.ts.map +1 -0
  123. package/dist/internal/ingest/error-message.js +10 -0
  124. package/dist/internal/ingest/error-message.js.map +1 -0
  125. package/dist/internal/ingest/files.d.ts +4 -12
  126. package/dist/internal/ingest/files.d.ts.map +1 -1
  127. package/dist/internal/ingest/files.js +22 -10
  128. package/dist/internal/ingest/files.js.map +1 -1
  129. package/dist/internal/ingest/main.d.ts.map +1 -1
  130. package/dist/internal/ingest/main.js +46 -16
  131. package/dist/internal/ingest/main.js.map +1 -1
  132. package/dist/internal/ingest/options.d.ts +6 -0
  133. package/dist/internal/ingest/options.d.ts.map +1 -0
  134. package/dist/internal/ingest/options.js +27 -0
  135. package/dist/internal/ingest/options.js.map +1 -0
  136. package/dist/internal/ingest/prompt.d.ts.map +1 -1
  137. package/dist/internal/ingest/prompt.js +14 -10
  138. package/dist/internal/ingest/prompt.js.map +1 -1
  139. package/dist/internal/ingest/run.d.ts +9 -2
  140. package/dist/internal/ingest/run.d.ts.map +1 -1
  141. package/dist/internal/ingest/run.js +206 -62
  142. package/dist/internal/ingest/run.js.map +1 -1
  143. package/dist/internal/ingest/timeout.d.ts +3 -0
  144. package/dist/internal/ingest/timeout.d.ts.map +1 -0
  145. package/dist/internal/ingest/timeout.js +14 -0
  146. package/dist/internal/ingest/timeout.js.map +1 -0
  147. package/dist/internal/ingest/web.d.ts +2 -11
  148. package/dist/internal/ingest/web.d.ts.map +1 -1
  149. package/dist/internal/ingest/web.js +131 -25
  150. package/dist/internal/ingest/web.js.map +1 -1
  151. package/dist/internal/loop/budgets.d.ts +3 -0
  152. package/dist/internal/loop/budgets.d.ts.map +1 -0
  153. package/dist/internal/loop/budgets.js +10 -0
  154. package/dist/internal/loop/budgets.js.map +1 -0
  155. package/dist/internal/loop/index.d.ts +2 -0
  156. package/dist/internal/loop/index.d.ts.map +1 -1
  157. package/dist/internal/loop/index.js +2 -0
  158. package/dist/internal/loop/index.js.map +1 -1
  159. package/dist/internal/loop/json-answer.d.ts +7 -0
  160. package/dist/internal/loop/json-answer.d.ts.map +1 -0
  161. package/dist/internal/loop/json-answer.js +79 -0
  162. package/dist/internal/loop/json-answer.js.map +1 -0
  163. package/dist/internal/loop/json-spans.d.ts +3 -0
  164. package/dist/internal/loop/json-spans.d.ts.map +1 -0
  165. package/dist/internal/loop/json-spans.js +88 -0
  166. package/dist/internal/loop/json-spans.js.map +1 -0
  167. package/dist/internal/loop/llm.d.ts +3 -2
  168. package/dist/internal/loop/llm.d.ts.map +1 -1
  169. package/dist/internal/loop/llm.js +115 -32
  170. package/dist/internal/loop/llm.js.map +1 -1
  171. package/dist/internal/loop/process.d.ts +3 -1
  172. package/dist/internal/loop/process.d.ts.map +1 -1
  173. package/dist/internal/loop/process.js +159 -59
  174. package/dist/internal/loop/process.js.map +1 -1
  175. package/dist/internal/loop/read-snapshot.d.ts +4 -0
  176. package/dist/internal/loop/read-snapshot.d.ts.map +1 -0
  177. package/dist/internal/loop/read-snapshot.js +29 -0
  178. package/dist/internal/loop/read-snapshot.js.map +1 -0
  179. package/dist/internal/loop/reconstruct.d.ts +6 -5
  180. package/dist/internal/loop/reconstruct.d.ts.map +1 -1
  181. package/dist/internal/loop/reconstruct.js +19 -8
  182. package/dist/internal/loop/reconstruct.js.map +1 -1
  183. package/dist/internal/loop/score-settings.d.ts +5 -0
  184. package/dist/internal/loop/score-settings.d.ts.map +1 -0
  185. package/dist/internal/loop/score-settings.js +14 -0
  186. package/dist/internal/loop/score-settings.js.map +1 -0
  187. package/dist/internal/loop/sqlite.d.ts.map +1 -1
  188. package/dist/internal/loop/sqlite.js +15 -7
  189. package/dist/internal/loop/sqlite.js.map +1 -1
  190. package/dist/internal/loop/store.d.ts.map +1 -1
  191. package/dist/internal/loop/store.js +11 -7
  192. package/dist/internal/loop/store.js.map +1 -1
  193. package/dist/internal/loop/timeout.d.ts +3 -0
  194. package/dist/internal/loop/timeout.d.ts.map +1 -0
  195. package/dist/internal/loop/timeout.js +13 -0
  196. package/dist/internal/loop/timeout.js.map +1 -0
  197. package/dist/internal/mcp/error-message.d.ts +3 -0
  198. package/dist/internal/mcp/error-message.d.ts.map +1 -0
  199. package/dist/internal/mcp/error-message.js +10 -0
  200. package/dist/internal/mcp/error-message.js.map +1 -0
  201. package/dist/internal/mcp/main.d.ts.map +1 -1
  202. package/dist/internal/mcp/main.js +42 -27
  203. package/dist/internal/mcp/main.js.map +1 -1
  204. package/dist/internal/mcp/read-snapshot.d.ts +4 -0
  205. package/dist/internal/mcp/read-snapshot.d.ts.map +1 -0
  206. package/dist/internal/mcp/read-snapshot.js +22 -0
  207. package/dist/internal/mcp/read-snapshot.js.map +1 -0
  208. package/dist/internal/mcp/server.d.ts.map +1 -1
  209. package/dist/internal/mcp/server.js +181 -21
  210. package/dist/internal/mcp/server.js.map +1 -1
  211. package/dist/internal/mcp/source.d.ts +3 -0
  212. package/dist/internal/mcp/source.d.ts.map +1 -0
  213. package/dist/internal/mcp/source.js +13 -0
  214. package/dist/internal/mcp/source.js.map +1 -0
  215. package/dist/internal/mcp/tools.d.ts.map +1 -1
  216. package/dist/internal/mcp/tools.js +135 -72
  217. package/dist/internal/mcp/tools.js.map +1 -1
  218. package/dist/internal/rules/declarations.d.ts +6 -0
  219. package/dist/internal/rules/declarations.d.ts.map +1 -0
  220. package/dist/internal/rules/declarations.js +14 -0
  221. package/dist/internal/rules/declarations.js.map +1 -0
  222. package/dist/internal/rules/declare.d.ts.map +1 -1
  223. package/dist/internal/rules/declare.js +30 -16
  224. package/dist/internal/rules/declare.js.map +1 -1
  225. package/dist/internal/rules/engine.d.ts +8 -7
  226. package/dist/internal/rules/engine.d.ts.map +1 -1
  227. package/dist/internal/rules/engine.js +150 -44
  228. package/dist/internal/rules/engine.js.map +1 -1
  229. package/dist/internal/shape/alias-root.d.ts +3 -0
  230. package/dist/internal/shape/alias-root.d.ts.map +1 -0
  231. package/dist/internal/shape/alias-root.js +13 -0
  232. package/dist/internal/shape/alias-root.js.map +1 -0
  233. package/dist/internal/shape/check.d.ts +15 -3
  234. package/dist/internal/shape/check.d.ts.map +1 -1
  235. package/dist/internal/shape/check.js +230 -100
  236. package/dist/internal/shape/check.js.map +1 -1
  237. package/dist/internal/shape/client.d.ts.map +1 -1
  238. package/dist/internal/shape/client.js +42 -20
  239. package/dist/internal/shape/client.js.map +1 -1
  240. package/dist/internal/shape/constraints.d.ts +8 -0
  241. package/dist/internal/shape/constraints.d.ts.map +1 -0
  242. package/dist/internal/shape/constraints.js +17 -0
  243. package/dist/internal/shape/constraints.js.map +1 -0
  244. package/dist/internal/shape/edit-distance.d.ts +3 -0
  245. package/dist/internal/shape/edit-distance.d.ts.map +1 -0
  246. package/dist/internal/shape/edit-distance.js +33 -0
  247. package/dist/internal/shape/edit-distance.js.map +1 -0
  248. package/dist/internal/shape/gate.d.ts.map +1 -1
  249. package/dist/internal/shape/gate.js +0 -0
  250. package/dist/internal/shape/gate.js.map +1 -1
  251. package/dist/internal/shape/snapshot.d.ts +4 -0
  252. package/dist/internal/shape/snapshot.d.ts.map +1 -0
  253. package/dist/internal/shape/snapshot.js +20 -0
  254. package/dist/internal/shape/snapshot.js.map +1 -0
  255. package/dist/internal/shape/suggest.d.ts +8 -6
  256. package/dist/internal/shape/suggest.d.ts.map +1 -1
  257. package/dist/internal/shape/suggest.js +208 -92
  258. package/dist/internal/shape/suggest.js.map +1 -1
  259. package/dist/internal/sync/error-message.d.ts +3 -0
  260. package/dist/internal/sync/error-message.d.ts.map +1 -0
  261. package/dist/internal/sync/error-message.js +10 -0
  262. package/dist/internal/sync/error-message.js.map +1 -0
  263. package/dist/internal/sync/index.d.ts +3 -2
  264. package/dist/internal/sync/index.d.ts.map +1 -1
  265. package/dist/internal/sync/index.js +3 -2
  266. package/dist/internal/sync/index.js.map +1 -1
  267. package/dist/internal/sync/sync.d.ts +8 -5
  268. package/dist/internal/sync/sync.d.ts.map +1 -1
  269. package/dist/internal/sync/sync.js +322 -65
  270. package/dist/internal/sync/sync.js.map +1 -1
  271. package/dist/internal/view/api.d.ts +3 -1
  272. package/dist/internal/view/api.d.ts.map +1 -1
  273. package/dist/internal/view/api.js +154 -90
  274. package/dist/internal/view/api.js.map +1 -1
  275. package/dist/internal/view/error-message.d.ts +3 -0
  276. package/dist/internal/view/error-message.d.ts.map +1 -0
  277. package/dist/internal/view/error-message.js +10 -0
  278. package/dist/internal/view/error-message.js.map +1 -0
  279. package/dist/internal/view/main.d.ts.map +1 -1
  280. package/dist/internal/view/main.js +61 -8
  281. package/dist/internal/view/main.js.map +1 -1
  282. package/dist/internal/view/page.d.ts.map +1 -1
  283. package/dist/internal/view/page.js +119 -20
  284. package/dist/internal/view/page.js.map +1 -1
  285. package/dist/internal/view/report.d.ts.map +1 -1
  286. package/dist/internal/view/report.js +180 -145
  287. package/dist/internal/view/report.js.map +1 -1
  288. package/dist/internal/view/scope.d.ts +6 -2
  289. package/dist/internal/view/scope.d.ts.map +1 -1
  290. package/dist/internal/view/scope.js +107 -24
  291. package/dist/internal/view/scope.js.map +1 -1
  292. package/dist/internal/view/sensitivity.d.ts +4 -0
  293. package/dist/internal/view/sensitivity.d.ts.map +1 -0
  294. package/dist/internal/view/sensitivity.js +11 -0
  295. package/dist/internal/view/sensitivity.js.map +1 -0
  296. package/dist/internal/view/server.d.ts.map +1 -1
  297. package/dist/internal/view/server.js +57 -20
  298. package/dist/internal/view/server.js.map +1 -1
  299. package/dist/src/atomic-output.d.ts +3 -0
  300. package/dist/src/atomic-output.d.ts.map +1 -0
  301. package/dist/src/atomic-output.js +67 -0
  302. package/dist/src/atomic-output.js.map +1 -0
  303. package/dist/src/cli.d.ts +3 -2
  304. package/dist/src/cli.d.ts.map +1 -1
  305. package/dist/src/cli.js +188 -173
  306. package/dist/src/cli.js.map +1 -1
  307. package/dist/src/dispatch.d.ts.map +1 -1
  308. package/dist/src/dispatch.js +3 -2
  309. package/dist/src/dispatch.js.map +1 -1
  310. package/dist/src/doctor.d.ts.map +1 -1
  311. package/dist/src/doctor.js +178 -102
  312. package/dist/src/doctor.js.map +1 -1
  313. package/dist/src/error-message.d.ts +3 -0
  314. package/dist/src/error-message.d.ts.map +1 -0
  315. package/dist/src/error-message.js +20 -0
  316. package/dist/src/error-message.js.map +1 -0
  317. package/dist/src/hooks-config.d.ts +3 -0
  318. package/dist/src/hooks-config.d.ts.map +1 -0
  319. package/dist/src/hooks-config.js +23 -0
  320. package/dist/src/hooks-config.js.map +1 -0
  321. package/package.json +23 -20
  322. package/src/atomic-output.ts +47 -0
  323. package/src/cli.ts +170 -147
  324. package/src/dispatch.ts +3 -2
  325. package/src/doctor.ts +192 -111
  326. package/src/error-message.ts +13 -0
  327. package/src/hooks-config.ts +22 -0
@@ -2,7 +2,10 @@
2
2
  * `cave connect` entry — argument parsing, pass orchestration (single,
3
3
  * `--watch`, `--query`), and report rendering around `run.ts`.
4
4
  */
5
- import { existsSync, readFileSync, watch as watchFs } from 'node:fs';
5
+ import { rethrowFetchFailure } from "./fetch-failure.js";
6
+ import { errorMessage } from "./error-message.js";
7
+ import { existsSync, watch as watchFs } from 'node:fs';
8
+ import { readText } from "./utf8.js";
6
9
  import { basename, dirname, resolve } from 'node:path';
7
10
  import { parseArgs } from 'node:util';
8
11
  import { Registry } from '@cavelang/canonical';
@@ -11,7 +14,8 @@ import { Record as QueryRecord, query as caveQuery } from '@cavelang/query';
11
14
  import * as Declared from "./declared.js";
12
15
  import * as Source from "./source.js";
13
16
  import * as Template from "./template.js";
14
- import { connect, federatedQuery } from "./run.js";
17
+ import { connect } from "./run.js";
18
+ import { withFederatedSource } from "./federation.js";
15
19
  const usage = `cave connect — deterministic structured ingestion through a mapping template (spec §23)
16
20
 
17
21
  Usage:
@@ -21,7 +25,7 @@ Usage:
21
25
  cave connect [--db <path>] --query '<pattern>'
22
26
 
23
27
  The source is a .csv/.tsv/.json/.jsonl/.ndjson file, a SQLite database
24
- (with --table or --sql), or an http(s) URL serving JSON or CSV. The
28
+ (with --table or --sql), or an http(s) URL serving JSON, JSONL, CSV or TSV. The
25
29
  mapping is an ordinary CAVE document whose ?field variables stand for
26
30
  record fields; variable-free blocks append once per run, variable blocks
27
31
  instantiate once per record — no LLM in the loop, same input, same claims.
@@ -83,7 +87,21 @@ Examples:
83
87
  cave connect https://api.example.com/deps.json --records data.items --map deps.map.cave
84
88
  cave connect events.jsonl --map events.map.cave --watch
85
89
  cave connect people.csv --map people.map.cave --query '?who WORKS-AT acme'`;
86
- const waitForAbort = (signal) => signal?.aborted === true ? Promise.resolve() : new Promise(resolve => signal?.addEventListener('abort', () => resolve(), { once: true }));
90
+ /** Abort and fatal callbacks share one owned completion boundary. */
91
+ const watchEnd = (signal) => {
92
+ let failure;
93
+ let wake;
94
+ const done = new Promise(resolve => { wake = resolve; });
95
+ signal?.addEventListener('abort', wake, { once: true });
96
+ if (signal?.aborted)
97
+ wake();
98
+ return {
99
+ fail(error) { failure ??= { error }; wake(); },
100
+ async wait() { await done; if (failure !== undefined)
101
+ throw failure.error; },
102
+ close() { signal?.removeEventListener('abort', wake); }
103
+ };
104
+ };
87
105
  const renderReport = (report) => {
88
106
  const lines = [
89
107
  `connect: ${report.records} record(s): ${report.mapped} mapped, ${report.skipped} skipped (unchanged)` +
@@ -98,28 +116,35 @@ const renderReport = (report) => {
98
116
  }
99
117
  for (const failure of report.failures) {
100
118
  lines.push(` ${failure.record}: FAILED`);
101
- lines.push(...failure.problems.map(problem => ` ${problem}`));
119
+ for (const problem of failure.problems)
120
+ lines.push(` ${problem}`);
102
121
  }
103
122
  return lines.join('\n');
104
123
  };
105
124
  /** `--map` is a template file, or the template itself written inline (spec §23.1). */
106
125
  const loadMapping = (spec) => Template.isInline(spec) && !existsSync(spec) ?
107
126
  Template.parse(Template.inlineDocument(spec)) :
108
- Template.parse(readFileSync(spec, 'utf8'));
127
+ Template.parse(readText(spec));
109
128
  const sourceOptions = (values, context) => ({
110
129
  ...values.format === undefined ? {} : { format: values.format },
111
130
  ...values.delimiter === undefined ? {} : { delimiter: values.delimiter },
112
131
  ...values.table === undefined ? {} : { table: values.table },
113
132
  ...values.sql === undefined ? {} : { sql: values.sql },
114
133
  ...values.records === undefined ? {} : { records: values.records },
134
+ ...context.signal === undefined ? {} : { signal: context.signal },
115
135
  ...context.fetchImpl === undefined ? {} : { fetchImpl: context.fetchImpl }
116
136
  });
117
137
  const loadSource = async (source, values, context) => {
118
138
  try {
119
- return await Source.load(source, sourceOptions(values, context));
139
+ context.signal?.throwIfAborted();
140
+ const loaded = await Source.load(source, sourceOptions(values, context));
141
+ context.signal?.throwIfAborted();
142
+ return loaded;
120
143
  }
121
144
  catch (error) {
122
- throw new Error(`load ${source}: ${error instanceof Error ? error.message : String(error)}`);
145
+ if (context.signal?.aborted)
146
+ rethrowFetchFailure(context.signal, error);
147
+ throw new Error(`load ${source}: ${errorMessage(error)}`, { cause: error });
123
148
  }
124
149
  };
125
150
  const runPass = async (store, source, values, name, io, context) => {
@@ -167,6 +192,25 @@ const runDry = async (source, values, io, context) => {
167
192
  io.stdout.write(`${sections.join('\n\n')}\n`);
168
193
  return failures > 0 ? 1 : 0;
169
194
  };
195
+ /** Own the command store until its work settles, preserving cleanup failures. */
196
+ const withCommandStore = async (store, run) => {
197
+ let result;
198
+ try {
199
+ result = await run();
200
+ }
201
+ catch (error) {
202
+ try {
203
+ store.close();
204
+ }
205
+ catch (closeError) {
206
+ const messages = [error, closeError].map(errorMessage);
207
+ throw new AggregateError([error, closeError], `command failed: ${messages[0]}; store close also failed: ${messages[1]}`, { cause: error });
208
+ }
209
+ throw error;
210
+ }
211
+ store.close();
212
+ return result;
213
+ };
170
214
  const runQuery = async (source, values, name, io, context) => {
171
215
  const { mapping, problems } = loadMapping(values.map);
172
216
  if (mapping === undefined) {
@@ -178,13 +222,16 @@ const runQuery = async (source, values, name, io, context) => {
178
222
  const registry = values['no-prelude'] === true ? { registry: Registry.empty } : {};
179
223
  // Federation over a store that does not exist yet queries the source alone.
180
224
  const store = kindOf(db) === 'missing' ? open(':memory:', registry) : openAt(db, { intent: 'scratch', assemble: Declared.assemble, ...registry });
181
- try {
182
- const { matches, report } = federatedQuery(store, mapping, loaded.records, {
225
+ return await withCommandStore(store, () => {
226
+ const { result: { matches, records }, report } = withFederatedSource(store, mapping, loaded.records, {
183
227
  name,
184
228
  source,
185
229
  ...loaded.spans === undefined ? {} : { spans: loaded.spans },
186
230
  ...values.key === undefined ? {} : { key: values.key }
187
- }, values.query, { all: values.all === true, aliases: values.aliases === true });
231
+ }, () => {
232
+ const matches = caveQuery(store, values.query, { all: values.all === true, aliases: values.aliases === true });
233
+ return { matches, records: values.json === true ? matches.map(match => QueryRecord.of(store, match)) : undefined };
234
+ });
188
235
  // A failed record means the union was incomplete — matches still print
189
236
  // as partial results, but the exit code must not read as success.
190
237
  const code = report.failures.length > 0 ? 1 : 0;
@@ -192,7 +239,7 @@ const runQuery = async (source, values, name, io, context) => {
192
239
  io.stderr.write(`${renderReport(report)}\n`);
193
240
  }
194
241
  if (values.json === true) {
195
- io.stdout.write(`${JSON.stringify(matches.map(match => QueryRecord.of(store, match)), undefined, 2)}\n`);
242
+ io.stdout.write(`${JSON.stringify(records, undefined, 2)}\n`);
196
243
  return code;
197
244
  }
198
245
  if (matches.length === 0) {
@@ -207,18 +254,55 @@ const runQuery = async (source, values, name, io, context) => {
207
254
  });
208
255
  io.stdout.write(`${lines.join('\n')}\n`);
209
256
  return code;
257
+ });
258
+ };
259
+ /** Finish every owned watch resource before returning or propagating failures. */
260
+ const watchSession = async (run, stop, watchers, active) => {
261
+ const errors = [];
262
+ let result;
263
+ try {
264
+ result = await run();
265
+ }
266
+ catch (error) {
267
+ errors.push(error);
268
+ }
269
+ try {
270
+ stop();
271
+ }
272
+ catch (error) {
273
+ errors.push(error);
274
+ }
275
+ for (const watcher of watchers()) {
276
+ try {
277
+ watcher.close();
278
+ }
279
+ catch (error) {
280
+ errors.push(error);
281
+ }
282
+ }
283
+ try {
284
+ await active();
210
285
  }
211
- finally {
212
- store.close();
286
+ catch (error) {
287
+ errors.push(error);
213
288
  }
289
+ if (errors.length === 1)
290
+ throw errors[0];
291
+ if (errors.length > 1) {
292
+ throw new AggregateError(errors, `watch session failed: ${errors.map(errorMessage).join('; ')}`, { cause: errors[0] });
293
+ }
294
+ return result;
214
295
  };
215
296
  const runWatch = async (store, source, values, name, io, context) => {
297
+ let stopped = false;
298
+ const end = watchEnd(io.signal);
299
+ const fail = (error) => { stopped = true; end.fail(error); };
216
300
  const passOnce = async () => {
217
301
  try {
218
302
  await runPass(store, source, values, name, io, context);
219
303
  }
220
304
  catch (error) {
221
- io.stderr.write(`cave connect watch pass: ${error instanceof Error ? error.message : String(error)}\n`);
305
+ io.stderr.write(`cave connect watch pass: ${errorMessage(error)}\n`);
222
306
  }
223
307
  };
224
308
  let running = false;
@@ -231,60 +315,84 @@ const runWatch = async (store, source, values, name, io, context) => {
231
315
  return;
232
316
  }
233
317
  running = true;
234
- do {
235
- queued = false;
236
- await passOnce();
237
- } while (queued);
238
- running = false;
318
+ try {
319
+ do {
320
+ queued = false;
321
+ await passOnce();
322
+ } while (queued && !stopped && !io.signal?.aborted);
323
+ }
324
+ finally {
325
+ running = false;
326
+ }
239
327
  };
240
328
  const schedule = context.schedule ?? ((callback, delayMs) => setTimeout(() => { void callback(); }, delayMs));
241
329
  const cancelScheduled = context.cancelScheduled ?? (handle => clearTimeout(handle));
242
330
  const trigger = () => {
331
+ if (stopped || io.signal?.aborted)
332
+ return;
243
333
  if (running) {
244
334
  queued = true;
245
335
  return;
246
336
  }
247
- if (timer !== undefined)
248
- cancelScheduled(timer);
249
- timer = schedule(async () => {
250
- timer = undefined;
251
- const pass = fire();
252
- active = pass;
253
- await pass;
254
- if (active === pass)
255
- active = undefined;
256
- }, 200);
337
+ try {
338
+ if (timer !== undefined)
339
+ cancelScheduled(timer);
340
+ timer = schedule(async () => {
341
+ timer = undefined;
342
+ if (stopped)
343
+ return;
344
+ const pass = fire();
345
+ active = pass;
346
+ try {
347
+ await pass;
348
+ }
349
+ catch (error) {
350
+ fail(error);
351
+ }
352
+ if (active === pass)
353
+ active = undefined;
354
+ }, 200);
355
+ }
356
+ catch (error) {
357
+ fail(error);
358
+ }
257
359
  };
258
360
  // Watch the parent directories — editors replace files on save, and a
259
361
  // watcher on the file itself dies with the old inode.
260
362
  const targets = [...new Set([resolve(source), ...Template.isInline(values.map) && !existsSync(values.map) ? [] : [resolve(values.map)]])];
261
363
  const watch = context.watch ?? ((path, listener) => watchFs(path, listener));
262
- const watchers = targets.map(target => watch(dirname(target), (_event, filename) => {
263
- // Some platforms and watcher backends cannot identify the changed
264
- // directory entry. A filename-less event is therefore a rescan signal,
265
- // while Buffer filenames retain the same exact target filtering.
266
- if (filename === null || filename.toString() === basename(target)) {
267
- trigger();
268
- }
269
- }));
270
- try {
364
+ const watchers = [];
365
+ return watchSession(async () => {
366
+ for (const target of targets) {
367
+ const subscription = watch(dirname(target), (_event, filename) => {
368
+ // Filename-less events rescan; editors may replace the watched file.
369
+ if (filename === null || filename.toString() === basename(target))
370
+ trigger();
371
+ });
372
+ watchers.push(subscription);
373
+ subscription.on?.('error', error => { if (!stopped)
374
+ fail(error); });
375
+ }
271
376
  // Install watchers before the initial pass. A save during startup is
272
377
  // therefore either read by this pass or queued for the next one.
273
- await passOnce();
378
+ await fire();
379
+ if (stopped || io.signal?.aborted) {
380
+ await end.wait();
381
+ return 0;
382
+ }
274
383
  io.stdout.write('watching (ctrl-c to stop)\n');
275
- await waitForAbort(io.signal);
384
+ await end.wait();
276
385
  return 0;
277
- }
278
- finally {
386
+ }, () => {
387
+ stopped = true;
388
+ end.close();
279
389
  if (timer !== undefined)
280
390
  cancelScheduled(timer);
281
- watchers.forEach(watcher => watcher.close());
282
- await active;
283
- }
391
+ }, () => watchers, () => active);
284
392
  };
285
- const printMatches = (store, matches, pattern, values, io) => {
286
- if (values.json === true) {
287
- io.stdout.write(`${JSON.stringify(matches.map(match => QueryRecord.of(store, match)), undefined, 2)}\n`);
393
+ const printMatches = (matches, pattern, io, records) => {
394
+ if (records !== undefined) {
395
+ io.stdout.write(`${JSON.stringify(records, undefined, 2)}\n`);
288
396
  return;
289
397
  }
290
398
  if (matches.length === 0) {
@@ -327,6 +435,7 @@ const declaredPass = async (store, root, values, io, context) => {
327
435
  new Set(Declared.closure(store, selectDeclared(store, values).map(declared => declared.name)).map(declared => declared.name));
328
436
  const version = Declared.versionCounter();
329
437
  for (;;) {
438
+ io.signal?.throwIfAborted();
330
439
  const next = Declared.declaredSources(store)
331
440
  .find(declared => (allowed === undefined || allowed.has(declared.name)) && done.get(declared.name) !== Declared.signature(declared));
332
441
  if (next === undefined) {
@@ -335,7 +444,8 @@ const declaredPass = async (store, root, values, io, context) => {
335
444
  version(next.name);
336
445
  done.set(next.name, Declared.signature(next));
337
446
  try {
338
- const ready = await Declared.prepare(next, dir, context.fetchImpl);
447
+ const ready = await Declared.prepare(next, dir, context.fetchImpl, io.signal);
448
+ io.signal?.throwIfAborted();
339
449
  const before = Declared.signatures(Declared.declaredSources(store));
340
450
  const report = Declared.run(store, ready, { force: values.force === true, prune: values.prune === true });
341
451
  if (allowed !== undefined) {
@@ -352,7 +462,8 @@ const declaredPass = async (store, root, values, io, context) => {
352
462
  failed += 1;
353
463
  }
354
464
  catch (error) {
355
- io.stderr.write(`source/${next.name} (${next.path}): ${error instanceof Error ? error.message : String(error)}\n`);
465
+ io.signal?.throwIfAborted();
466
+ io.stderr.write(`source/${next.name} (${next.path}): ${errorMessage(error)}\n`);
356
467
  failed += 1;
357
468
  }
358
469
  }
@@ -360,6 +471,7 @@ const declaredPass = async (store, root, values, io, context) => {
360
471
  const discoverOptions = (values, context, force = values.force === true) => ({
361
472
  ...values.name === undefined ? {} : { only: values.name },
362
473
  ...context.fetchImpl === undefined ? {} : { fetchImpl: context.fetchImpl },
474
+ ...context.signal === undefined ? {} : { signal: context.signal },
363
475
  force,
364
476
  prune: values.prune === true
365
477
  });
@@ -367,7 +479,9 @@ const discoverOptions = (values, context, force = values.force === true) => ({
367
479
  const declaredDry = async (store, root, values, io, context) => {
368
480
  const sections = [];
369
481
  let failures = 0;
370
- for (const ready of await Declared.discover(store, root, discoverOptions(values, context))) {
482
+ const sequence = await Declared.discover(store, root, discoverOptions(values, context));
483
+ io.signal?.throwIfAborted();
484
+ for (const ready of sequence) {
371
485
  sections.push(`; === source/${ready.declared.name} (${ready.declared.path})`);
372
486
  if (ready.mapping.prelude !== '') {
373
487
  sections.push(`; --- prelude\n\n${ready.mapping.prelude.trimEnd()}`);
@@ -398,7 +512,9 @@ const declaredQuery = async (store, root, values, io, context) => {
398
512
  // the declarations since, so the replay checks and rediscovers if so.
399
513
  for (let attempt = 0; attempt < Declared.overlayAttempts; attempt += 1) {
400
514
  const { sequence, baseline } = await Declared.discovery(store, root, discoverOptions(values, context, true));
515
+ io.signal?.throwIfAborted();
401
516
  let matches;
517
+ let records;
402
518
  let failed = 0;
403
519
  try {
404
520
  store.transaction(() => {
@@ -413,6 +529,8 @@ const declaredQuery = async (store, root, values, io, context) => {
413
529
  }
414
530
  }
415
531
  matches = caveQuery(store, values.query, { all: values.all === true, aliases: values.aliases === true });
532
+ if (values.json === true)
533
+ records = matches.map(match => QueryRecord.of(store, match));
416
534
  throw rollback;
417
535
  });
418
536
  }
@@ -424,21 +542,31 @@ const declaredQuery = async (store, root, values, io, context) => {
424
542
  throw error;
425
543
  }
426
544
  }
427
- printMatches(store, matches, values.query, values, io);
545
+ printMatches(matches, values.query, io, records);
428
546
  return failed > 0 ? 1 : 0;
429
547
  }
430
548
  throw Declared.staleOverlay();
431
549
  };
432
550
  const declaredWatch = async (store, root, values, io, context) => {
551
+ let stopped = false;
552
+ const end = watchEnd(io.signal);
553
+ const fail = (error) => { stopped = true; end.fail(error); };
433
554
  const dir = Declared.directoryOf(root);
434
555
  const passOnce = async () => {
435
556
  try {
436
557
  await declaredPass(store, root, values, io, context);
437
558
  }
438
559
  catch (error) {
439
- io.stderr.write(`cave connect watch pass: ${error instanceof Error ? error.message : String(error)}\n`);
560
+ io.stderr.write(`cave connect watch pass: ${errorMessage(error)}\n`);
561
+ }
562
+ try {
563
+ if (!stopped && !io.signal?.aborted)
564
+ refreshWatchers();
565
+ }
566
+ catch (error) {
567
+ // Keep current subscriptions available to retry on the next save.
568
+ io.stderr.write(`cave connect watch subscriptions: ${errorMessage(error)}\n`);
440
569
  }
441
- refreshWatchers();
442
570
  };
443
571
  let running = false;
444
572
  let queued = false;
@@ -450,31 +578,49 @@ const declaredWatch = async (store, root, values, io, context) => {
450
578
  return;
451
579
  }
452
580
  running = true;
453
- do {
454
- queued = false;
455
- await passOnce();
456
- } while (queued);
457
- running = false;
581
+ try {
582
+ do {
583
+ queued = false;
584
+ await passOnce();
585
+ } while (queued && !stopped && !io.signal?.aborted);
586
+ }
587
+ finally {
588
+ running = false;
589
+ }
458
590
  };
459
591
  const schedule = context.schedule ?? ((callback, delayMs) => setTimeout(() => { void callback(); }, delayMs));
460
592
  const cancelScheduled = context.cancelScheduled ?? (handle => clearTimeout(handle));
461
593
  const trigger = () => {
594
+ if (stopped || io.signal?.aborted)
595
+ return;
462
596
  if (running) {
463
597
  queued = true;
464
598
  return;
465
599
  }
466
- if (timer !== undefined)
467
- cancelScheduled(timer);
468
- timer = schedule(async () => {
469
- timer = undefined;
470
- // Retained so shutdown waits for a pass in flight (a URL fetch, say)
471
- // instead of closing the store under it.
472
- const pass = fire();
473
- active = pass;
474
- await pass;
475
- if (active === pass)
476
- active = undefined;
477
- }, 200);
600
+ try {
601
+ if (timer !== undefined)
602
+ cancelScheduled(timer);
603
+ timer = schedule(async () => {
604
+ timer = undefined;
605
+ if (stopped)
606
+ return;
607
+ // Retained so shutdown waits for a pass in flight (a URL fetch, say)
608
+ // instead of closing the store under it.
609
+ const pass = fire();
610
+ active = pass;
611
+ try {
612
+ await pass;
613
+ }
614
+ catch (error) {
615
+ fail(error);
616
+ }
617
+ if (active === pass)
618
+ active = undefined;
619
+ }, 200);
620
+ }
621
+ catch (error) {
622
+ fail(error);
623
+ }
478
624
  };
479
625
  // Every local declared file and mapping; URL sources only run on file
480
626
  // changes. A followed .cave source may declare more, so the set is
@@ -492,42 +638,68 @@ const declaredWatch = async (store, root, values, io, context) => {
492
638
  catch {
493
639
  return;
494
640
  }
495
- for (const target of new Set(declared.flatMap(source => [
641
+ const targets = new Set(declared.flatMap(source => [
496
642
  ...Source.isUrl(source.path) ? [] : [Declared.resolvePath(source.path, dir)],
497
643
  // A map is a file when it resolves to one, as the loader decides; only
498
644
  // otherwise is it inline and nothing to watch.
499
645
  ...source.map === undefined || (Template.isInline(source.map) && !existsSync(Declared.resolvePath(source.map, dir))) ?
500
646
  [] : [Declared.resolvePath(source.map, dir)]
501
- ]))) {
647
+ ]));
648
+ for (const target of targets) {
502
649
  if (watched.has(target))
503
650
  continue;
504
- watched.set(target, watch(dirname(target), (_event, filename) => {
651
+ let active = true;
652
+ const subscription = watch(dirname(target), (_event, filename) => {
653
+ if (!active)
654
+ return;
505
655
  if (filename === null || filename.toString() === basename(target)) {
506
656
  trigger();
507
657
  }
508
- }));
658
+ });
659
+ watched.set(target, { close: () => { active = false; subscription.close(); } });
660
+ subscription.on?.('error', error => { if (active && !stopped)
661
+ fail(error); });
662
+ }
663
+ const errors = [];
664
+ for (const [target, subscription] of watched) {
665
+ if (targets.has(target))
666
+ continue;
667
+ // Retire ownership before closing: a failed close must not suppress a
668
+ // fresh subscription if this path is declared again on a later pass.
669
+ watched.delete(target);
670
+ try {
671
+ subscription.close();
672
+ }
673
+ catch (error) {
674
+ errors.push(error);
675
+ }
676
+ }
677
+ if (errors.length === 1)
678
+ throw errors[0];
679
+ if (errors.length > 1) {
680
+ throw new AggregateError(errors, `retiring watch subscriptions failed: ${errors.map(errorMessage).join('; ')}`, { cause: errors[0] });
509
681
  }
510
682
  };
511
- refreshWatchers();
512
- if (watched.size === 0) {
513
- io.stderr.write('cave connect: nothing to watch — no declared local source\n');
514
- return 1;
515
- }
516
- try {
517
- await passOnce();
683
+ return watchSession(async () => {
518
684
  refreshWatchers();
685
+ if (watched.size === 0) {
686
+ io.stderr.write('cave connect: nothing to watch — no declared local source\n');
687
+ return 1;
688
+ }
689
+ await fire();
690
+ if (stopped || io.signal?.aborted) {
691
+ await end.wait();
692
+ return 0;
693
+ }
519
694
  io.stdout.write('watching (ctrl-c to stop)\n');
520
- await waitForAbort(io.signal);
695
+ await end.wait();
521
696
  return 0;
522
- }
523
- finally {
697
+ }, () => {
698
+ stopped = true;
699
+ end.close();
524
700
  if (timer !== undefined)
525
701
  cancelScheduled(timer);
526
- for (const watcher of watched.values())
527
- watcher.close();
528
- if (active !== undefined)
529
- await active;
530
- }
702
+ }, () => watched.values(), () => active);
531
703
  };
532
704
  /**
533
705
  * `cave connect` without a source (spec §23.4): the store's declared
@@ -547,49 +719,54 @@ const runDeclared = async (values, io, context) => {
547
719
  try {
548
720
  if (values.list === true) {
549
721
  const store = openAt(db, { intent: 'read', assemble: Declared.assemble, ...registryOf(values) });
550
- try {
722
+ return await withCommandStore(store, () => {
551
723
  const declared = selectDeclared(store, values);
552
724
  io.stdout.write(declared.length === 0 ? 'no declared sources\n' : `${declared.map(Declared.describe).join('\n')}\n`);
553
725
  return 0;
554
- }
555
- finally {
556
- store.close();
557
- }
726
+ });
558
727
  }
559
728
  if (values['dry-run'] === true || values.query !== undefined) {
560
729
  // Discovery works on a private snapshot, so the dry run only reads;
561
730
  // the overlay appends into the store's own rolled-back transaction.
562
731
  const store = openAt(db, { intent: values.query === undefined ? 'read' : 'scratch', assemble: Declared.assemble, ...registryOf(values) });
563
- try {
732
+ return await withCommandStore(store, async () => {
564
733
  return values.query === undefined ?
565
734
  await declaredDry(store, db, values, io, context) :
566
735
  await declaredQuery(store, db, values, io, context);
567
- }
568
- finally {
569
- store.close();
570
- }
736
+ });
571
737
  }
572
738
  const store = openAt(db, { intent: 'write', ...registryOf(values) });
573
- try {
739
+ return await withCommandStore(store, async () => {
574
740
  return values.watch === true ?
575
741
  await declaredWatch(store, db, values, io, context) :
576
742
  await declaredPass(store, db, values, io, context);
577
- }
578
- finally {
579
- store.close();
580
- }
743
+ });
581
744
  }
582
745
  catch (error) {
583
- io.stderr.write(`cave connect: ${error instanceof Error ? error.message : String(error)}\n`);
746
+ io.stderr.write(`cave connect: ${errorMessage(error)}\n`);
584
747
  return error instanceof LocateError ? 1 : 1;
585
748
  }
586
749
  };
587
750
  export const runConnect = async (argv, context = {}) => {
751
+ const signal = context.signal;
752
+ if (signal?.aborted === true)
753
+ return 0;
754
+ context = {
755
+ signal,
756
+ stdout: context.stdout,
757
+ stderr: context.stderr,
758
+ fetchImpl: context.fetchImpl,
759
+ watch: context.watch,
760
+ schedule: context.schedule,
761
+ cancelScheduled: context.cancelScheduled
762
+ };
588
763
  const io = {
589
764
  stdout: context.stdout ?? process.stdout,
590
765
  stderr: context.stderr ?? process.stderr,
591
- ...context.signal === undefined ? {} : { signal: context.signal }
766
+ ...signal === undefined ? {} : { signal }
592
767
  };
768
+ if (Boolean(io.signal?.aborted))
769
+ return 0;
593
770
  const { values, positionals } = parseArgs({
594
771
  args: [...argv],
595
772
  options: {
@@ -660,18 +837,15 @@ export const runConnect = async (argv, context = {}) => {
660
837
  intent: 'write',
661
838
  ...values['no-prelude'] === true ? { registry: Registry.empty } : {}
662
839
  });
663
- try {
840
+ return await withCommandStore(store, async () => {
664
841
  if (values.watch === true) {
665
842
  return await runWatch(store, source, values, name, io, context);
666
843
  }
667
844
  return await runPass(store, source, values, name, io, context);
668
- }
669
- finally {
670
- store.close();
671
- }
845
+ });
672
846
  }
673
847
  catch (error) {
674
- io.stderr.write(`cave connect: ${error instanceof Error ? error.message : String(error)}\n`);
848
+ io.stderr.write(`cave connect: ${errorMessage(error)}\n`);
675
849
  return 1;
676
850
  }
677
851
  };