@graphty/graph-io 0.0.0 → 0.2.1

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 (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +250 -28
  3. package/dist/chunks/children-CL3Cy0ez.js +238 -0
  4. package/dist/chunks/children-CL3Cy0ez.js.map +1 -0
  5. package/dist/chunks/escape-DyI8JofU.js +938 -0
  6. package/dist/chunks/escape-DyI8JofU.js.map +1 -0
  7. package/dist/chunks/importer-CQnJuWJw.js +2987 -0
  8. package/dist/chunks/importer-CQnJuWJw.js.map +1 -0
  9. package/dist/chunks/importer-CpCpfbxr.js +2015 -0
  10. package/dist/chunks/importer-CpCpfbxr.js.map +1 -0
  11. package/dist/chunks/importer-DbnGYr3_.js +2342 -0
  12. package/dist/chunks/importer-DbnGYr3_.js.map +1 -0
  13. package/dist/chunks/importer-GozH8DkN.js +3050 -0
  14. package/dist/chunks/importer-GozH8DkN.js.map +1 -0
  15. package/dist/chunks/records-CGpxszm1.js +605 -0
  16. package/dist/chunks/records-CGpxszm1.js.map +1 -0
  17. package/dist/chunks/text-CajMdVFy.js +189 -0
  18. package/dist/chunks/text-CajMdVFy.js.map +1 -0
  19. package/dist/chunks/writer-DxSKC7TL.js +2842 -0
  20. package/dist/chunks/writer-DxSKC7TL.js.map +1 -0
  21. package/dist/csv.d.ts +1 -0
  22. package/dist/csv.js +1702 -0
  23. package/dist/csv.js.map +1 -0
  24. package/dist/dot.d.ts +1 -0
  25. package/dist/dot.js +8 -0
  26. package/dist/dot.js.map +1 -0
  27. package/dist/gexf.d.ts +1 -0
  28. package/dist/gexf.js +3466 -0
  29. package/dist/gexf.js.map +1 -0
  30. package/dist/gml.d.ts +1 -0
  31. package/dist/gml.js +2647 -0
  32. package/dist/gml.js.map +1 -0
  33. package/dist/graph-io.d.ts +1 -0
  34. package/dist/graph-io.js +790 -0
  35. package/dist/graph-io.js.map +1 -0
  36. package/dist/graphml.d.ts +1 -0
  37. package/dist/graphml.js +8 -0
  38. package/dist/graphml.js.map +1 -0
  39. package/dist/json.d.ts +1 -0
  40. package/dist/json.js +11 -0
  41. package/dist/json.js.map +1 -0
  42. package/dist/neo4j.d.ts +1 -0
  43. package/dist/neo4j.js +2046 -0
  44. package/dist/neo4j.js.map +1 -0
  45. package/dist/pajek.d.ts +1 -0
  46. package/dist/pajek.js +8 -0
  47. package/dist/pajek.js.map +1 -0
  48. package/dist/src/children.d.ts +134 -0
  49. package/dist/src/children.d.ts.map +1 -0
  50. package/dist/src/children.js +274 -0
  51. package/dist/src/children.js.map +1 -0
  52. package/dist/src/common/attributes.d.ts +229 -0
  53. package/dist/src/common/attributes.d.ts.map +1 -0
  54. package/dist/src/common/attributes.js +368 -0
  55. package/dist/src/common/attributes.js.map +1 -0
  56. package/dist/src/common/codes.d.ts +105 -0
  57. package/dist/src/common/codes.d.ts.map +1 -0
  58. package/dist/src/common/codes.js +107 -0
  59. package/dist/src/common/codes.js.map +1 -0
  60. package/dist/src/common/declared-types.d.ts +84 -0
  61. package/dist/src/common/declared-types.d.ts.map +1 -0
  62. package/dist/src/common/declared-types.js +326 -0
  63. package/dist/src/common/declared-types.js.map +1 -0
  64. package/dist/src/common/direction.d.ts +206 -0
  65. package/dist/src/common/direction.d.ts.map +1 -0
  66. package/dist/src/common/direction.js +370 -0
  67. package/dist/src/common/direction.js.map +1 -0
  68. package/dist/src/common/escape.d.ts +92 -0
  69. package/dist/src/common/escape.d.ts.map +1 -0
  70. package/dist/src/common/escape.js +212 -0
  71. package/dist/src/common/escape.js.map +1 -0
  72. package/dist/src/common/export.d.ts +249 -0
  73. package/dist/src/common/export.d.ts.map +1 -0
  74. package/dist/src/common/export.js +594 -0
  75. package/dist/src/common/export.js.map +1 -0
  76. package/dist/src/common/format.d.ts +59 -0
  77. package/dist/src/common/format.d.ts.map +1 -0
  78. package/dist/src/common/format.js +106 -0
  79. package/dist/src/common/format.js.map +1 -0
  80. package/dist/src/common/ids.d.ts +83 -0
  81. package/dist/src/common/ids.d.ts.map +1 -0
  82. package/dist/src/common/ids.js +158 -0
  83. package/dist/src/common/ids.js.map +1 -0
  84. package/dist/src/common/input.d.ts +100 -0
  85. package/dist/src/common/input.d.ts.map +1 -0
  86. package/dist/src/common/input.js +335 -0
  87. package/dist/src/common/input.js.map +1 -0
  88. package/dist/src/common/lists.d.ts +34 -0
  89. package/dist/src/common/lists.d.ts.map +1 -0
  90. package/dist/src/common/lists.js +185 -0
  91. package/dist/src/common/lists.js.map +1 -0
  92. package/dist/src/common/options.d.ts +108 -0
  93. package/dist/src/common/options.d.ts.map +1 -0
  94. package/dist/src/common/options.js +265 -0
  95. package/dist/src/common/options.js.map +1 -0
  96. package/dist/src/common/report.d.ts +187 -0
  97. package/dist/src/common/report.d.ts.map +1 -0
  98. package/dist/src/common/report.js +274 -0
  99. package/dist/src/common/report.js.map +1 -0
  100. package/dist/src/common/temporal.d.ts +71 -0
  101. package/dist/src/common/temporal.d.ts.map +1 -0
  102. package/dist/src/common/temporal.js +266 -0
  103. package/dist/src/common/temporal.js.map +1 -0
  104. package/dist/src/common/text.d.ts +104 -0
  105. package/dist/src/common/text.d.ts.map +1 -0
  106. package/dist/src/common/text.js +255 -0
  107. package/dist/src/common/text.js.map +1 -0
  108. package/dist/src/common/weights.d.ts +77 -0
  109. package/dist/src/common/weights.d.ts.map +1 -0
  110. package/dist/src/common/weights.js +156 -0
  111. package/dist/src/common/weights.js.map +1 -0
  112. package/dist/src/common/writer.d.ts +51 -0
  113. package/dist/src/common/writer.d.ts.map +1 -0
  114. package/dist/src/common/writer.js +108 -0
  115. package/dist/src/common/writer.js.map +1 -0
  116. package/dist/src/common/xml.d.ts +245 -0
  117. package/dist/src/common/xml.d.ts.map +1 -0
  118. package/dist/src/common/xml.js +942 -0
  119. package/dist/src/common/xml.js.map +1 -0
  120. package/dist/src/formats/csv/exporter.d.ts +70 -0
  121. package/dist/src/formats/csv/exporter.d.ts.map +1 -0
  122. package/dist/src/formats/csv/exporter.js +682 -0
  123. package/dist/src/formats/csv/exporter.js.map +1 -0
  124. package/dist/src/formats/csv/header.d.ts +66 -0
  125. package/dist/src/formats/csv/header.d.ts.map +1 -0
  126. package/dist/src/formats/csv/header.js +152 -0
  127. package/dist/src/formats/csv/header.js.map +1 -0
  128. package/dist/src/formats/csv/importer.d.ts +82 -0
  129. package/dist/src/formats/csv/importer.d.ts.map +1 -0
  130. package/dist/src/formats/csv/importer.js +849 -0
  131. package/dist/src/formats/csv/importer.js.map +1 -0
  132. package/dist/src/formats/csv/index.d.ts +60 -0
  133. package/dist/src/formats/csv/index.d.ts.map +1 -0
  134. package/dist/src/formats/csv/index.js +63 -0
  135. package/dist/src/formats/csv/index.js.map +1 -0
  136. package/dist/src/formats/csv/records.d.ts +188 -0
  137. package/dist/src/formats/csv/records.d.ts.map +1 -0
  138. package/dist/src/formats/csv/records.js +702 -0
  139. package/dist/src/formats/csv/records.js.map +1 -0
  140. package/dist/src/formats/csv/values.d.ts +105 -0
  141. package/dist/src/formats/csv/values.d.ts.map +1 -0
  142. package/dist/src/formats/csv/values.js +192 -0
  143. package/dist/src/formats/csv/values.js.map +1 -0
  144. package/dist/src/formats/dot/exporter.d.ts +52 -0
  145. package/dist/src/formats/dot/exporter.d.ts.map +1 -0
  146. package/dist/src/formats/dot/exporter.js +836 -0
  147. package/dist/src/formats/dot/exporter.js.map +1 -0
  148. package/dist/src/formats/dot/importer.d.ts +102 -0
  149. package/dist/src/formats/dot/importer.d.ts.map +1 -0
  150. package/dist/src/formats/dot/importer.js +1291 -0
  151. package/dist/src/formats/dot/importer.js.map +1 -0
  152. package/dist/src/formats/dot/index.d.ts +7 -0
  153. package/dist/src/formats/dot/index.d.ts.map +1 -0
  154. package/dist/src/formats/dot/index.js +7 -0
  155. package/dist/src/formats/dot/index.js.map +1 -0
  156. package/dist/src/formats/dot/names.d.ts +29 -0
  157. package/dist/src/formats/dot/names.d.ts.map +1 -0
  158. package/dist/src/formats/dot/names.js +28 -0
  159. package/dist/src/formats/dot/names.js.map +1 -0
  160. package/dist/src/formats/dot/tokenizer.d.ts +114 -0
  161. package/dist/src/formats/dot/tokenizer.d.ts.map +1 -0
  162. package/dist/src/formats/dot/tokenizer.js +341 -0
  163. package/dist/src/formats/dot/tokenizer.js.map +1 -0
  164. package/dist/src/formats/gexf/exporter.d.ts +56 -0
  165. package/dist/src/formats/gexf/exporter.d.ts.map +1 -0
  166. package/dist/src/formats/gexf/exporter.js +1395 -0
  167. package/dist/src/formats/gexf/exporter.js.map +1 -0
  168. package/dist/src/formats/gexf/importer.d.ts +73 -0
  169. package/dist/src/formats/gexf/importer.d.ts.map +1 -0
  170. package/dist/src/formats/gexf/importer.js +1880 -0
  171. package/dist/src/formats/gexf/importer.js.map +1 -0
  172. package/dist/src/formats/gexf/index.d.ts +96 -0
  173. package/dist/src/formats/gexf/index.d.ts.map +1 -0
  174. package/dist/src/formats/gexf/index.js +97 -0
  175. package/dist/src/formats/gexf/index.js.map +1 -0
  176. package/dist/src/formats/gexf/schema.d.ts +135 -0
  177. package/dist/src/formats/gexf/schema.d.ts.map +1 -0
  178. package/dist/src/formats/gexf/schema.js +323 -0
  179. package/dist/src/formats/gexf/schema.js.map +1 -0
  180. package/dist/src/formats/gml/exporter.d.ts +69 -0
  181. package/dist/src/formats/gml/exporter.d.ts.map +1 -0
  182. package/dist/src/formats/gml/exporter.js +1093 -0
  183. package/dist/src/formats/gml/exporter.js.map +1 -0
  184. package/dist/src/formats/gml/importer.d.ts +66 -0
  185. package/dist/src/formats/gml/importer.d.ts.map +1 -0
  186. package/dist/src/formats/gml/importer.js +1331 -0
  187. package/dist/src/formats/gml/importer.js.map +1 -0
  188. package/dist/src/formats/gml/index.d.ts +85 -0
  189. package/dist/src/formats/gml/index.d.ts.map +1 -0
  190. package/dist/src/formats/gml/index.js +88 -0
  191. package/dist/src/formats/gml/index.js.map +1 -0
  192. package/dist/src/formats/gml/syntax.d.ts +186 -0
  193. package/dist/src/formats/gml/syntax.d.ts.map +1 -0
  194. package/dist/src/formats/gml/syntax.js +467 -0
  195. package/dist/src/formats/gml/syntax.js.map +1 -0
  196. package/dist/src/formats/graphml/constants.d.ts +169 -0
  197. package/dist/src/formats/graphml/constants.d.ts.map +1 -0
  198. package/dist/src/formats/graphml/constants.js +165 -0
  199. package/dist/src/formats/graphml/constants.js.map +1 -0
  200. package/dist/src/formats/graphml/exporter.d.ts +34 -0
  201. package/dist/src/formats/graphml/exporter.d.ts.map +1 -0
  202. package/dist/src/formats/graphml/exporter.js +1176 -0
  203. package/dist/src/formats/graphml/exporter.js.map +1 -0
  204. package/dist/src/formats/graphml/importer.d.ts +31 -0
  205. package/dist/src/formats/graphml/importer.d.ts.map +1 -0
  206. package/dist/src/formats/graphml/importer.js +1607 -0
  207. package/dist/src/formats/graphml/importer.js.map +1 -0
  208. package/dist/src/formats/graphml/index.d.ts +8 -0
  209. package/dist/src/formats/graphml/index.d.ts.map +1 -0
  210. package/dist/src/formats/graphml/index.js +8 -0
  211. package/dist/src/formats/graphml/index.js.map +1 -0
  212. package/dist/src/formats/graphml/tree.d.ts +72 -0
  213. package/dist/src/formats/graphml/tree.d.ts.map +1 -0
  214. package/dist/src/formats/graphml/tree.js +290 -0
  215. package/dist/src/formats/graphml/tree.js.map +1 -0
  216. package/dist/src/formats/json/dialect.d.ts +125 -0
  217. package/dist/src/formats/json/dialect.d.ts.map +1 -0
  218. package/dist/src/formats/json/dialect.js +262 -0
  219. package/dist/src/formats/json/dialect.js.map +1 -0
  220. package/dist/src/formats/json/exporter.d.ts +89 -0
  221. package/dist/src/formats/json/exporter.d.ts.map +1 -0
  222. package/dist/src/formats/json/exporter.js +1358 -0
  223. package/dist/src/formats/json/exporter.js.map +1 -0
  224. package/dist/src/formats/json/importer.d.ts +108 -0
  225. package/dist/src/formats/json/importer.d.ts.map +1 -0
  226. package/dist/src/formats/json/importer.js +1838 -0
  227. package/dist/src/formats/json/importer.js.map +1 -0
  228. package/dist/src/formats/json/index.d.ts +8 -0
  229. package/dist/src/formats/json/index.d.ts.map +1 -0
  230. package/dist/src/formats/json/index.js +8 -0
  231. package/dist/src/formats/json/index.js.map +1 -0
  232. package/dist/src/formats/neo4j/exporter.d.ts +68 -0
  233. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -0
  234. package/dist/src/formats/neo4j/exporter.js +1055 -0
  235. package/dist/src/formats/neo4j/exporter.js.map +1 -0
  236. package/dist/src/formats/neo4j/header.d.ts +52 -0
  237. package/dist/src/formats/neo4j/header.d.ts.map +1 -0
  238. package/dist/src/formats/neo4j/header.js +131 -0
  239. package/dist/src/formats/neo4j/header.js.map +1 -0
  240. package/dist/src/formats/neo4j/importer.d.ts +73 -0
  241. package/dist/src/formats/neo4j/importer.d.ts.map +1 -0
  242. package/dist/src/formats/neo4j/importer.js +932 -0
  243. package/dist/src/formats/neo4j/importer.js.map +1 -0
  244. package/dist/src/formats/neo4j/index.d.ts +79 -0
  245. package/dist/src/formats/neo4j/index.d.ts.map +1 -0
  246. package/dist/src/formats/neo4j/index.js +83 -0
  247. package/dist/src/formats/neo4j/index.js.map +1 -0
  248. package/dist/src/formats/pajek/exporter.d.ts +58 -0
  249. package/dist/src/formats/pajek/exporter.d.ts.map +1 -0
  250. package/dist/src/formats/pajek/exporter.js +825 -0
  251. package/dist/src/formats/pajek/exporter.js.map +1 -0
  252. package/dist/src/formats/pajek/importer.d.ts +88 -0
  253. package/dist/src/formats/pajek/importer.d.ts.map +1 -0
  254. package/dist/src/formats/pajek/importer.js +1047 -0
  255. package/dist/src/formats/pajek/importer.js.map +1 -0
  256. package/dist/src/formats/pajek/index.d.ts +7 -0
  257. package/dist/src/formats/pajek/index.d.ts.map +1 -0
  258. package/dist/src/formats/pajek/index.js +7 -0
  259. package/dist/src/formats/pajek/index.js.map +1 -0
  260. package/dist/src/formats/pajek/syntax.d.ts +112 -0
  261. package/dist/src/formats/pajek/syntax.d.ts.map +1 -0
  262. package/dist/src/formats/pajek/syntax.js +269 -0
  263. package/dist/src/formats/pajek/syntax.js.map +1 -0
  264. package/dist/src/index.d.ts +35 -0
  265. package/dist/src/index.d.ts.map +1 -0
  266. package/dist/src/index.js +39 -0
  267. package/dist/src/index.js.map +1 -0
  268. package/dist/src/registry.d.ts +207 -0
  269. package/dist/src/registry.d.ts.map +1 -0
  270. package/dist/src/registry.js +481 -0
  271. package/dist/src/registry.js.map +1 -0
  272. package/dist/src/sniff.d.ts +104 -0
  273. package/dist/src/sniff.d.ts.map +1 -0
  274. package/dist/src/sniff.js +357 -0
  275. package/dist/src/sniff.js.map +1 -0
  276. package/dist/src/types.d.ts +238 -0
  277. package/dist/src/types.d.ts.map +1 -0
  278. package/dist/src/types.js +29 -0
  279. package/dist/src/types.js.map +1 -0
  280. package/dist/tsconfig.build.tsbuildinfo +1 -0
  281. package/package.json +122 -7
  282. package/src/children.ts +335 -0
  283. package/src/common/attributes.ts +520 -0
  284. package/src/common/codes.ts +153 -0
  285. package/src/common/declared-types.ts +374 -0
  286. package/src/common/direction.ts +518 -0
  287. package/src/common/escape.ts +231 -0
  288. package/src/common/export.ts +817 -0
  289. package/src/common/format.ts +111 -0
  290. package/src/common/ids.ts +176 -0
  291. package/src/common/input.ts +378 -0
  292. package/src/common/lists.ts +196 -0
  293. package/src/common/options.ts +377 -0
  294. package/src/common/report.ts +352 -0
  295. package/src/common/temporal.ts +302 -0
  296. package/src/common/text.ts +294 -0
  297. package/src/common/weights.ts +202 -0
  298. package/src/common/writer.ts +123 -0
  299. package/src/common/xml.ts +1053 -0
  300. package/src/formats/csv/exporter.ts +894 -0
  301. package/src/formats/csv/header.ts +172 -0
  302. package/src/formats/csv/importer.ts +1104 -0
  303. package/src/formats/csv/index.ts +88 -0
  304. package/src/formats/csv/records.ts +813 -0
  305. package/src/formats/csv/values.ts +224 -0
  306. package/src/formats/dot/exporter.ts +1014 -0
  307. package/src/formats/dot/importer.ts +1549 -0
  308. package/src/formats/dot/index.ts +7 -0
  309. package/src/formats/dot/names.ts +40 -0
  310. package/src/formats/dot/tokenizer.ts +384 -0
  311. package/src/formats/gexf/exporter.ts +1696 -0
  312. package/src/formats/gexf/importer.ts +2333 -0
  313. package/src/formats/gexf/index.ts +142 -0
  314. package/src/formats/gexf/schema.ts +361 -0
  315. package/src/formats/gml/exporter.ts +1404 -0
  316. package/src/formats/gml/importer.ts +1591 -0
  317. package/src/formats/gml/index.ts +128 -0
  318. package/src/formats/gml/syntax.ts +545 -0
  319. package/src/formats/graphml/constants.ts +225 -0
  320. package/src/formats/graphml/exporter.ts +1458 -0
  321. package/src/formats/graphml/importer.ts +2027 -0
  322. package/src/formats/graphml/index.ts +8 -0
  323. package/src/formats/graphml/tree.ts +318 -0
  324. package/src/formats/json/dialect.ts +317 -0
  325. package/src/formats/json/exporter.ts +1616 -0
  326. package/src/formats/json/importer.ts +2271 -0
  327. package/src/formats/json/index.ts +8 -0
  328. package/src/formats/neo4j/exporter.ts +1287 -0
  329. package/src/formats/neo4j/header.ts +156 -0
  330. package/src/formats/neo4j/importer.ts +1220 -0
  331. package/src/formats/neo4j/index.ts +116 -0
  332. package/src/formats/pajek/exporter.ts +1000 -0
  333. package/src/formats/pajek/importer.ts +1311 -0
  334. package/src/formats/pajek/index.ts +7 -0
  335. package/src/formats/pajek/syntax.ts +307 -0
  336. package/src/index.ts +244 -0
  337. package/src/registry.ts +617 -0
  338. package/src/sniff.ts +397 -0
  339. package/src/types.ts +262 -0
@@ -0,0 +1,617 @@
1
+ /**
2
+ * The importer / exporter registry (design sections 8.2 and 8.4): the eight built-in formats
3
+ * registered by name, `sniff()` over them, and the two conveniences for callers who do not own a
4
+ * sink: `importGraph()` sniffs the format, creates a builder seeded from the common options
5
+ * (`directed: true` as a placeholder; the importer sets the real value), imports and freezes;
6
+ * `exportGraph()` looks the exporter up by name. A caller who owns a builder uses the importer
7
+ * objects directly (the subpath exports) and this module never touches their sink.
8
+ */
9
+
10
+ import {
11
+ type FreezeOptions,
12
+ type FreezeReport,
13
+ GraphBuilder,
14
+ type GraphBuilderOptions,
15
+ GraphFormatError,
16
+ type GraphSnapshot,
17
+ } from "@graphty/graph-format";
18
+
19
+ import { throwIfAborted } from "./common/input.js";
20
+ import { ImportReportBuilder } from "./common/report.js";
21
+ import { csvExporter, csvImporter } from "./formats/csv/index.js";
22
+ import { dotExporter, dotImporter } from "./formats/dot/index.js";
23
+ import { gexfExporter, gexfImporter } from "./formats/gexf/index.js";
24
+ import { gmlExporter, gmlImporter } from "./formats/gml/index.js";
25
+ import { graphmlExporter, graphmlImporter } from "./formats/graphml/index.js";
26
+ import { jsonExporter, jsonImporter } from "./formats/json/index.js";
27
+ import { neo4jExporter, neo4jImporter } from "./formats/neo4j/index.js";
28
+ import { pajekExporter, pajekImporter } from "./formats/pajek/index.js";
29
+ import { rankFormats, SNIFF_HEAD_BYTES, type SniffHints, type SniffResult } from "./sniff.js";
30
+ import {
31
+ type CommonExportOptions,
32
+ type CommonImportOptions,
33
+ type GraphExporter,
34
+ type GraphImporter,
35
+ type ImportInput,
36
+ type ImportReport,
37
+ type LossNote,
38
+ } from "./types.js";
39
+
40
+ /** The issue code of an input whose format no registered importer recognises. */
41
+ export const UNKNOWN_FORMAT_CODE = "E_UNKNOWN_FORMAT";
42
+
43
+ /** The builder options importGraph() accepts beyond the ones the common import options seed. */
44
+ export type BuilderSeed = Omit<
45
+ GraphBuilderOptions,
46
+ "directed" | "addMissingNodes" | "duplicateEdges" | "selfLoops" | "weightDtype"
47
+ >;
48
+
49
+ /**
50
+ * The options of importGraph(): the common import options (which also seed the registry's
51
+ * builder, design section 8.4), the format choice and the hints sniffing uses, the builder and
52
+ * freeze options, and any format-specific option (`delimiter`, `dialect`, ...) passed through to
53
+ * the importer unchanged.
54
+ */
55
+ export interface ImportGraphOptions extends CommonImportOptions {
56
+ /** The format name, or "auto" (default) to sniff it from the filename, MIME type and content. */
57
+ readonly format?: string | undefined;
58
+ /** The file name or path the input came from, a hint for sniffing. */
59
+ readonly filename?: string | null | undefined;
60
+ /** The MIME type the input was served as, a hint for sniffing. */
61
+ readonly mimeType?: string | null | undefined;
62
+ /** Builder options the common options do not cover (`weighted`, `expectedNodes`, ...). */
63
+ readonly builder?: BuilderSeed | undefined;
64
+ /** Options of the freeze that follows the import. */
65
+ readonly freeze?: FreezeOptions | undefined;
66
+ /** Format-specific options, passed to the importer as they are. */
67
+ readonly [formatOption: string]: unknown;
68
+ }
69
+
70
+ /** What importGraph() returns (design section 8.4). */
71
+ export interface ImportGraphResult {
72
+ /** The format the input was read as. */
73
+ readonly format: string;
74
+ /** The sniff that chose the importer, or null when the caller named the format. */
75
+ readonly sniff: SniffResult | null;
76
+ /** The frozen snapshot. */
77
+ readonly snapshot: GraphSnapshot;
78
+ /** The importer's report. */
79
+ readonly report: ImportReport;
80
+ /** The freeze report (design section 6.6). */
81
+ readonly freeze: FreezeReport;
82
+ }
83
+
84
+ /** The options of exportGraph(): the common export options plus any format-specific option, passed through. */
85
+ export interface ExportGraphOptions extends CommonExportOptions {
86
+ /** Format-specific options, passed to the exporter as they are. */
87
+ readonly [formatOption: string]: unknown;
88
+ }
89
+
90
+ /** The keys of ImportGraphOptions that belong to the registry, never to an importer. */
91
+ const REGISTRY_KEYS: ReadonlySet<string> = new Set(["format", "filename", "mimeType", "builder", "freeze"]);
92
+
93
+ /**
94
+ * A registry of importers and exporters by format name. Registration order is the tie-break
95
+ * order of sniffing (design section 8.2); the default registry lists the built-in formats in the
96
+ * order of GRAPH_FORMATS.
97
+ */
98
+ export class FormatRegistry {
99
+ private readonly importerMap = new Map<string, GraphImporter>();
100
+
101
+ private readonly exporterMap = new Map<string, GraphExporter>();
102
+
103
+ /**
104
+ * Register an importer under its format name, replacing one of the same name in place (the
105
+ * original registration order is kept).
106
+ * @param importer - the importer
107
+ * @returns this registry, for chaining
108
+ */
109
+ registerImporter(importer: GraphImporter): this {
110
+ this.importerMap.set(importer.format, importer);
111
+ return this;
112
+ }
113
+
114
+ /**
115
+ * Register an exporter under its format name, replacing one of the same name.
116
+ * @param exporter - the exporter
117
+ * @returns this registry, for chaining
118
+ */
119
+ registerExporter(exporter: GraphExporter): this {
120
+ this.exporterMap.set(exporter.format, exporter);
121
+ return this;
122
+ }
123
+
124
+ /**
125
+ * The importer of a format.
126
+ * @param format - the format name
127
+ * @returns the importer; E_UNSUPPORTED when none is registered
128
+ */
129
+ importer(format: string): GraphImporter {
130
+ const importer = this.importerMap.get(format);
131
+ if (importer === undefined) {
132
+ throw unknownFormat("importer", format, this.importerMap.keys());
133
+ }
134
+ return importer;
135
+ }
136
+
137
+ /**
138
+ * The exporter of a format.
139
+ * @param format - the format name
140
+ * @returns the exporter; E_UNSUPPORTED when none is registered
141
+ */
142
+ exporter(format: string): GraphExporter {
143
+ const exporter = this.exporterMap.get(format);
144
+ if (exporter === undefined) {
145
+ throw unknownFormat("exporter", format, this.exporterMap.keys());
146
+ }
147
+ return exporter;
148
+ }
149
+
150
+ /**
151
+ * Whether an importer is registered for a format.
152
+ * @param format - the format name
153
+ * @returns true when importer(format) would succeed
154
+ */
155
+ hasImporter(format: string): boolean {
156
+ return this.importerMap.has(format);
157
+ }
158
+
159
+ /**
160
+ * Whether an exporter is registered for a format.
161
+ * @param format - the format name
162
+ * @returns true when exporter(format) would succeed
163
+ */
164
+ hasExporter(format: string): boolean {
165
+ return this.exporterMap.has(format);
166
+ }
167
+
168
+ /**
169
+ * Every registered importer, in registration order.
170
+ * @returns the importers
171
+ */
172
+ importers(): readonly GraphImporter[] {
173
+ return [...this.importerMap.values()];
174
+ }
175
+
176
+ /**
177
+ * Every registered exporter, in registration order.
178
+ * @returns the exporters
179
+ */
180
+ exporters(): readonly GraphExporter[] {
181
+ return [...this.exporterMap.values()];
182
+ }
183
+
184
+ /**
185
+ * The names of every format with an importer or an exporter, importers' order first.
186
+ * @returns the format names, each once
187
+ */
188
+ formats(): readonly string[] {
189
+ return [...new Set([...this.importerMap.keys(), ...this.exporterMap.keys()])];
190
+ }
191
+
192
+ /**
193
+ * Rank the registered importers for an input (design section 8.2; the successor of
194
+ * graphty-element's detectFormat()).
195
+ * @param hints - the filename, MIME type and / or head of the input
196
+ * @returns the candidates, best first; empty when nothing matches
197
+ */
198
+ sniffAll(hints: SniffHints): readonly SniffResult[] {
199
+ return rankFormats(hints, this.importerMap.values());
200
+ }
201
+
202
+ /**
203
+ * The best importer for an input, or null when no registered importer claims it.
204
+ * @param hints - the filename, MIME type and / or head of the input
205
+ * @returns the best candidate, or null
206
+ */
207
+ sniff(hints: SniffHints): SniffResult | null {
208
+ const ranked = this.sniffAll(hints);
209
+ return ranked.length > 0 ? ranked[0] : null;
210
+ }
211
+
212
+ /**
213
+ * Read an input into a fresh builder and freeze it (design section 8.4: for callers who do not
214
+ * own a sink). The format is the one named in the options, else sniffed from the filename,
215
+ * the MIME type and the first bytes of the content; the builder is seeded from the common
216
+ * options with `directed: true` as a placeholder that the importer overrides from the file.
217
+ * @param input - the text, bytes, stream or chunks to read
218
+ * @param options - the format, hints, common and format-specific import options
219
+ * @returns the snapshot, the import report and the freeze report
220
+ */
221
+ async importGraph(input: ImportInput, options: ImportGraphOptions = {}): Promise<ImportGraphResult> {
222
+ const requested = options.format ?? "auto";
223
+ let importer: GraphImporter;
224
+ let sniff: SniffResult | null = null;
225
+ let source = input;
226
+ let peeked: PeekedInput | null = null;
227
+ if (requested === "auto") {
228
+ peeked = await peekHead(input, SNIFF_HEAD_BYTES, options.signal ?? null);
229
+ source = peeked.input;
230
+ sniff = this.sniff({ filename: options.filename, mimeType: options.mimeType, head: peeked.head });
231
+ if (sniff === null) {
232
+ const report = new ImportReportBuilder("unknown", 0);
233
+ return report.fail(
234
+ UNKNOWN_FORMAT_CODE,
235
+ `no registered importer recognises the input${describeHints(options)}; pass the format explicitly`,
236
+ undefined,
237
+ { formats: this.formats() },
238
+ );
239
+ }
240
+ importer = this.importer(sniff.format);
241
+ } else {
242
+ importer = this.importer(requested);
243
+ }
244
+ const builder = new GraphBuilder({
245
+ weightDtype: options.weightDtype ?? "f64",
246
+ ...options.builder,
247
+ directed: true,
248
+ addMissingNodes: options.addMissingNodes ?? true,
249
+ duplicateEdges: options.duplicateEdges ?? "keep",
250
+ selfLoops: options.selfLoops ?? "keep",
251
+ });
252
+ let report: ImportReport;
253
+ try {
254
+ report = await importer.import(source, builder, importerOptions(options));
255
+ } catch (err) {
256
+ await peeked?.close();
257
+ throw err;
258
+ }
259
+ const frozen = builder.freezeWithReport(options.freeze);
260
+ return Object.freeze({
261
+ format: importer.format,
262
+ sniff,
263
+ snapshot: frozen.snapshot,
264
+ report,
265
+ freeze: frozen.report,
266
+ });
267
+ }
268
+
269
+ /**
270
+ * Write a snapshot in a format, as UTF-8 chunks (design section 8.5).
271
+ * @param snapshot - the snapshot
272
+ * @param format - the format name
273
+ * @param options - the exporter's common and format-specific options
274
+ * @returns the encoded chunks
275
+ */
276
+ exportGraph(snapshot: GraphSnapshot, format: string, options?: ExportGraphOptions): AsyncIterable<Uint8Array> {
277
+ return this.exporter(format).export(snapshot, options);
278
+ }
279
+
280
+ /**
281
+ * Write a snapshot in a format, as one string.
282
+ * @param snapshot - the snapshot
283
+ * @param format - the format name
284
+ * @param options - the exporter's common and format-specific options
285
+ * @returns the whole document
286
+ */
287
+ async exportGraphToString(snapshot: GraphSnapshot, format: string, options?: ExportGraphOptions): Promise<string> {
288
+ return this.exporter(format).exportToString(snapshot, options);
289
+ }
290
+
291
+ /**
292
+ * What exporting a snapshot in a format would lose, without writing anything.
293
+ * @param snapshot - the snapshot
294
+ * @param format - the format name
295
+ * @param options - the exporter's common and format-specific options
296
+ * @returns the loss notes, empty when the export is exact
297
+ */
298
+ checkExport(snapshot: GraphSnapshot, format: string, options?: ExportGraphOptions): readonly LossNote[] {
299
+ return this.exporter(format).check(snapshot, options);
300
+ }
301
+ }
302
+
303
+ /**
304
+ * A registry holding the eight built-in importers and exporters in the order of GRAPH_FORMATS.
305
+ * @returns a new registry
306
+ */
307
+ export function createRegistry(): FormatRegistry {
308
+ return new FormatRegistry()
309
+ .registerImporter(jsonImporter)
310
+ .registerExporter(jsonExporter)
311
+ .registerImporter(graphmlImporter)
312
+ .registerExporter(graphmlExporter)
313
+ .registerImporter(gexfImporter)
314
+ .registerExporter(gexfExporter)
315
+ .registerImporter(csvImporter)
316
+ .registerExporter(csvExporter)
317
+ .registerImporter(gmlImporter)
318
+ .registerExporter(gmlExporter)
319
+ .registerImporter(dotImporter)
320
+ .registerExporter(dotExporter)
321
+ .registerImporter(pajekImporter)
322
+ .registerExporter(pajekExporter)
323
+ .registerImporter(neo4jImporter)
324
+ .registerExporter(neo4jExporter);
325
+ }
326
+
327
+ /** The default registry: every built-in format. */
328
+ export const registry: FormatRegistry = createRegistry();
329
+
330
+ /**
331
+ * Read an input into a fresh builder and freeze it, through the default registry (design section
332
+ * 8.4: `importGraph(input, { format?, ...options })`).
333
+ * @param input - the text, bytes, stream or chunks to read
334
+ * @param options - the format, hints, common and format-specific import options
335
+ * @returns the snapshot, the import report and the freeze report
336
+ */
337
+ export function importGraph(input: ImportInput, options?: ImportGraphOptions): Promise<ImportGraphResult> {
338
+ return registry.importGraph(input, options);
339
+ }
340
+
341
+ /**
342
+ * Write a snapshot in a format through the default registry, as UTF-8 chunks.
343
+ * @param snapshot - the snapshot
344
+ * @param format - the format name
345
+ * @param options - the exporter's common and format-specific options
346
+ * @returns the encoded chunks
347
+ */
348
+ export function exportGraph(
349
+ snapshot: GraphSnapshot,
350
+ format: string,
351
+ options?: ExportGraphOptions,
352
+ ): AsyncIterable<Uint8Array> {
353
+ return registry.exportGraph(snapshot, format, options);
354
+ }
355
+
356
+ /**
357
+ * Write a snapshot in a format through the default registry, as one string.
358
+ * @param snapshot - the snapshot
359
+ * @param format - the format name
360
+ * @param options - the exporter's common and format-specific options
361
+ * @returns the whole document
362
+ */
363
+ export async function exportGraphToString(
364
+ snapshot: GraphSnapshot,
365
+ format: string,
366
+ options?: ExportGraphOptions,
367
+ ): Promise<string> {
368
+ return registry.exportGraphToString(snapshot, format, options);
369
+ }
370
+
371
+ /**
372
+ * What exporting a snapshot in a format would lose, through the default registry.
373
+ * @param snapshot - the snapshot
374
+ * @param format - the format name
375
+ * @param options - the exporter's common and format-specific options
376
+ * @returns the loss notes, empty when the export is exact
377
+ */
378
+ export function checkExport(
379
+ snapshot: GraphSnapshot,
380
+ format: string,
381
+ options?: ExportGraphOptions,
382
+ ): readonly LossNote[] {
383
+ return registry.checkExport(snapshot, format, options);
384
+ }
385
+
386
+ /**
387
+ * Sniff an input's format through the default registry.
388
+ * @param hints - the filename, MIME type and / or head of the input
389
+ * @returns the best candidate, or null when no built-in importer claims it
390
+ */
391
+ export function sniff(hints: SniffHints): SniffResult | null {
392
+ return registry.sniff(hints);
393
+ }
394
+
395
+ /**
396
+ * The options handed to the importer: everything but the registry's own keys.
397
+ * @param options - the importGraph options
398
+ * @returns the importer's options
399
+ */
400
+ function importerOptions(options: ImportGraphOptions): CommonImportOptions {
401
+ const out: Record<string, unknown> = {};
402
+ for (const [key, value] of Object.entries(options)) {
403
+ if (!REGISTRY_KEYS.has(key)) {
404
+ out[key] = value;
405
+ }
406
+ }
407
+ return out;
408
+ }
409
+
410
+ /**
411
+ * The E_UNSUPPORTED error of a format name nothing is registered under.
412
+ * @param kind - importer or exporter
413
+ * @param format - the requested name
414
+ * @param known - the registered names
415
+ * @returns the error
416
+ */
417
+ function unknownFormat(kind: "importer" | "exporter", format: string, known: Iterable<string>): GraphFormatError {
418
+ const supported = [...known];
419
+ return new GraphFormatError(
420
+ "E_UNSUPPORTED",
421
+ `no ${kind} is registered for format ${JSON.stringify(format)}; known: ${supported.join(", ")}`,
422
+ { option: "format", found: format, supported },
423
+ );
424
+ }
425
+
426
+ /**
427
+ * The hints of an import, for the unknown-format message.
428
+ * @param options - the importGraph options
429
+ * @returns " (filename ..., MIME type ...)" or an empty string
430
+ */
431
+ function describeHints(options: ImportGraphOptions): string {
432
+ const parts: string[] = [];
433
+ if (typeof options.filename === "string" && options.filename.length > 0) {
434
+ parts.push(`filename ${JSON.stringify(options.filename)}`);
435
+ }
436
+ if (typeof options.mimeType === "string" && options.mimeType.length > 0) {
437
+ parts.push(`MIME type ${JSON.stringify(options.mimeType)}`);
438
+ }
439
+ return parts.length === 0 ? "" : ` (${parts.join(", ")})`;
440
+ }
441
+
442
+ /** A head read from an input, and the input to hand the importer (the same bytes, replayed for a stream). */
443
+ interface PeekedInput {
444
+ /** The first bytes (or, for a text input, characters) of the content. */
445
+ readonly head: Uint8Array | string;
446
+ /** The input to read: the original for in-memory input, a replaying iterable for a stream. */
447
+ readonly input: ImportInput;
448
+ /**
449
+ * Close the source when the importer never iterated the replaying input (it threw first, an
450
+ * abort for instance): a stream's reader is cancelled and released, an async generator finalised.
451
+ * @returns when the source is closed
452
+ */
453
+ close(): Promise<void>;
454
+ }
455
+
456
+ /**
457
+ * The first `bytes` of an input without consuming it: in-memory input is sliced; a stream or an
458
+ * async iterable is read until enough is buffered and then replayed (the buffered chunks first,
459
+ * the rest as it arrives) through a new async iterable that cancels the source when the importer
460
+ * stops early.
461
+ * @param input - the input
462
+ * @param bytes - how much to peek
463
+ * @param signal - the cancellation signal, or null
464
+ * @returns the head and the input to read
465
+ */
466
+ async function peekHead(input: ImportInput, bytes: number, signal: AbortSignal | null): Promise<PeekedInput> {
467
+ if (typeof input === "string") {
468
+ return { head: input.slice(0, bytes), input, close: (): Promise<void> => Promise.resolve() };
469
+ }
470
+ if (input instanceof Uint8Array) {
471
+ return { head: input.subarray(0, bytes), input, close: (): Promise<void> => Promise.resolve() };
472
+ }
473
+ throwIfAborted(signal);
474
+ const source: AsyncIterator<string | Uint8Array> =
475
+ typeof (input as { getReader?: unknown }).getReader === "function"
476
+ ? readerIterator(input as ReadableStream<Uint8Array>)
477
+ : (input as AsyncIterable<string | Uint8Array>)[Symbol.asyncIterator]();
478
+ const buffered: (string | Uint8Array)[] = [];
479
+ let size = 0;
480
+ let finished = false;
481
+ while (size < bytes) {
482
+ if (signal !== null && signal.aborted) {
483
+ await source.return?.();
484
+ throwIfAborted(signal);
485
+ }
486
+ const next = await source.next();
487
+ if (next.done === true) {
488
+ finished = true;
489
+ break;
490
+ }
491
+ buffered.push(next.value);
492
+ size += typeof next.value === "string" ? next.value.length : next.value.byteLength;
493
+ }
494
+ if (!finished && signal !== null && signal.aborted) {
495
+ // an abort that landed on the read completing the head: close the source before rethrowing
496
+ await source.return?.();
497
+ throwIfAborted(signal);
498
+ }
499
+ const replayed = replay(buffered, source, finished);
500
+ return { head: joinHead(buffered, bytes), input: replayed, close: (): Promise<void> => replayed.close() };
501
+ }
502
+
503
+ /**
504
+ * The head text or bytes of the buffered chunks: bytes when every chunk is bytes, text otherwise
505
+ * (byte chunks decoded leniently; the importer decodes the real input strictly).
506
+ * @param chunks - the buffered chunks
507
+ * @param bytes - the head size
508
+ * @returns the head
509
+ */
510
+ function joinHead(chunks: readonly (string | Uint8Array)[], bytes: number): Uint8Array | string {
511
+ if (chunks.every((c) => c instanceof Uint8Array)) {
512
+ const total = Math.min(
513
+ bytes,
514
+ chunks.reduce((sum, c) => sum + c.byteLength, 0),
515
+ );
516
+ const head = new Uint8Array(total);
517
+ let offset = 0;
518
+ for (const chunk of chunks) {
519
+ if (offset >= total) {
520
+ break;
521
+ }
522
+ const part = chunk.subarray(0, Math.min(chunk.byteLength, total - offset));
523
+ head.set(part, offset);
524
+ offset += part.byteLength;
525
+ }
526
+ return head;
527
+ }
528
+ const decoder = new TextDecoder("utf-8", { fatal: false });
529
+ let text = "";
530
+ for (const chunk of chunks) {
531
+ text += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
532
+ if (text.length >= bytes) {
533
+ break;
534
+ }
535
+ }
536
+ return text.slice(0, bytes);
537
+ }
538
+
539
+ /**
540
+ * An async iterable that yields the buffered chunks, then the rest of the source; the source is
541
+ * closed when the consumer stops early.
542
+ * @param buffered - the chunks already read
543
+ * @param source - the iterator positioned after them
544
+ * @param finished - whether the source is already exhausted
545
+ * @returns the replaying iterable
546
+ */
547
+ function replay(
548
+ buffered: readonly (string | Uint8Array)[],
549
+ source: AsyncIterator<string | Uint8Array>,
550
+ finished: boolean,
551
+ ): AsyncIterable<string | Uint8Array> & { close(): Promise<void> } {
552
+ let exhausted = finished;
553
+ const close = async (): Promise<void> => {
554
+ if (!exhausted) {
555
+ exhausted = true;
556
+ await source.return?.();
557
+ }
558
+ };
559
+ return {
560
+ close,
561
+ [Symbol.asyncIterator](): AsyncIterator<string | Uint8Array> {
562
+ let position = 0;
563
+ return {
564
+ async next(): Promise<IteratorResult<string | Uint8Array>> {
565
+ if (position < buffered.length) {
566
+ return { done: false, value: buffered[position++] };
567
+ }
568
+ if (exhausted) {
569
+ return { done: true, value: undefined };
570
+ }
571
+ const next = await source.next();
572
+ if (next.done === true) {
573
+ exhausted = true;
574
+ return { done: true, value: undefined };
575
+ }
576
+ return next;
577
+ },
578
+ async return(): Promise<IteratorResult<string | Uint8Array>> {
579
+ await close();
580
+ return { done: true, value: undefined };
581
+ },
582
+ };
583
+ },
584
+ };
585
+ }
586
+
587
+ /**
588
+ * An async iterator over a ReadableStream's chunks that cancels the stream when closed early.
589
+ * @param stream - the stream
590
+ * @returns the iterator
591
+ */
592
+ function readerIterator(stream: ReadableStream<Uint8Array>): AsyncIterator<Uint8Array> {
593
+ const reader = stream.getReader();
594
+ let done = false;
595
+ return {
596
+ async next(): Promise<IteratorResult<Uint8Array>> {
597
+ if (done) {
598
+ return { done: true, value: undefined };
599
+ }
600
+ const result = await reader.read();
601
+ if (result.done) {
602
+ done = true;
603
+ reader.releaseLock();
604
+ return { done: true, value: undefined };
605
+ }
606
+ return { done: false, value: result.value };
607
+ },
608
+ async return(): Promise<IteratorResult<Uint8Array>> {
609
+ if (!done) {
610
+ done = true;
611
+ await reader.cancel().catch(() => undefined);
612
+ reader.releaseLock();
613
+ }
614
+ return { done: true, value: undefined };
615
+ },
616
+ };
617
+ }