@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,2842 @@
1
+ import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
2
+ const MISSING_ID_CODE = "E_MISSING_ID";
3
+ const MISSING_ENDPOINT_CODE = "E_MISSING_ENDPOINT";
4
+ const DUPLICATE_NODE_CODE = "W_DUPLICATE_NODE";
5
+ const NO_GRAPH_CODE = "E_NO_GRAPH";
6
+ const MULTIPLE_GRAPHS_CODE = "W_MULTIPLE_GRAPHS";
7
+ const EMPTY_INPUT_CODE = "E_EMPTY_INPUT";
8
+ const SYNTAX_CODE = "E_SYNTAX";
9
+ const XML_SYNTAX_CODE = "E_XML_SYNTAX";
10
+ const UNKNOWN_ELEMENT_CODE = "W_UNKNOWN_ELEMENT";
11
+ const STRAY_TEXT_CODE = "W_STRAY_TEXT";
12
+ const UNKNOWN_PARENT_CODE = "E_UNKNOWN_PARENT";
13
+ const HYPEREDGE_CODE = "E_HYPEREDGE";
14
+ const DUPLICATE_KEY_CODE = "E_DUPLICATE_KEY";
15
+ const COLUMN_RENAMED_CODE = "W_COLUMN_RENAMED";
16
+ const ROLE_TAKEN_CODE = "W_ROLE_TAKEN";
17
+ const PRECISION_CODE = "W_PRECISION";
18
+ const ID_MERGED_CODE = "W_ID_MERGED";
19
+ const SINK_OPTION_CODE = "W_SINK_OPTION";
20
+ const OPTION_IGNORED_CODE = "W_OPTION_IGNORED";
21
+ const INVALID_UTF8_CODE = "E_INVALID_UTF8";
22
+ const DIRECTION_REFUSED_CODE = "W_DIRECTION_REFUSED";
23
+ const DIRECTION_FORCED_CODE = "W_DIRECTION_FORCED";
24
+ const MIXED_DIRECTION_CODE = "E_MIXED_DIRECTION";
25
+ const COUNT_HINT_CODE = "W_COUNT_HINT";
26
+ const UNKNOWN_ATTR_TYPE_CODE = "W_UNKNOWN_ATTR_TYPE";
27
+ const BAD_DEFAULT_CODE = "W_BAD_DEFAULT";
28
+ const BAD_OPTIONS_CODE = "W_BAD_OPTIONS";
29
+ const DUPLICATE_EDGE_ID_CODE = "E_DUPLICATE_EDGE_ID";
30
+ const ROLE_DROPPED_CODE = "W_ROLE_DROPPED";
31
+ const PARENTS_DROPPED_CODE = "W_PARENTS_DROPPED";
32
+ const TEMPORAL_DROPPED_CODE = "W_TEMPORAL_DROPPED";
33
+ const MUTUAL_EXPANDED_CODE = "W_MUTUAL_EXPANDED";
34
+ const MUTUAL_AS_UNDIRECTED_CODE = "W_MUTUAL_AS_UNDIRECTED";
35
+ const ID_TEXT_TYPE_CODE = "W_ID_TEXT_TYPE";
36
+ const ID_TEXT_COLLISION_CODE = "E_ID_TEXT_COLLISION";
37
+ const TEMPORAL_TEXT_DROPPED_CODE = "W_TEMPORAL_TEXT_DROPPED";
38
+ const WEIGHT_KEY_CLASH_CODE = "W_WEIGHT_KEY_CLASH";
39
+ const ROLE_ASSUMED_CODE = "W_ROLE_ASSUMED";
40
+ const COLUMN_RENAMED_LOSS_CODE = "W_COLUMN_NAME_CHANGED";
41
+ const EMPTY_COLUMN_DROPPED_CODE = "W_EMPTY_COLUMN_DROPPED";
42
+ const STORAGE_CLASS_CODE = "W_STORAGE_CLASS_CHANGED";
43
+ const INTEGRAL_F64_CODE = "W_INTEGRAL_F64_AS_I32";
44
+ const TEXT_INFERRED_CODE = "W_TEXT_INFERRED";
45
+ const XML_ILLEGAL_CHAR_CODE = "E_XML_ILLEGAL_CHAR";
46
+ const OPTIONS_GAINED_CODE = "W_OPTIONS_GAINED";
47
+ const TIME_TEXT_ROLE = "timeText";
48
+ const TIME_TEXT_SUFFIX = ".text";
49
+ const DATE_TIME$1 = /^(-?[0-9]{4,})-([0-9]{2})-([0-9]{2})(?:[T ]([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]{1,9}))?)?)?(Z|z|[+-][0-9]{2}(?::?[0-9]{2})?)?$/;
50
+ const TIME$1 = /^([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]{1,9}))?)?(Z|z|[+-][0-9]{2}(?::?[0-9]{2})?)?$/;
51
+ const NUMERIC = /^[+-]?([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)?$/;
52
+ const MS_PER_MINUTE = 6e4;
53
+ const MS_PER_DAY = 864e5;
54
+ function parseTemporal(text, kind) {
55
+ let value;
56
+ switch (kind) {
57
+ case "date":
58
+ case "dateTime":
59
+ case "localDateTime":
60
+ value = parseDateTime(text, kind);
61
+ break;
62
+ case "time":
63
+ case "localTime":
64
+ value = parseTime(text, kind);
65
+ break;
66
+ default: {
67
+ const name = kind;
68
+ throw new GraphFormatError("E_COLUMN_TYPE", `unknown temporal kind ${name}`, { kind: name });
69
+ }
70
+ }
71
+ const canonical = formatTemporal(value, kind);
72
+ return { value, text: canonical === text ? null : text };
73
+ }
74
+ function parseTimeText(text, timeFormat) {
75
+ const trimmed = text.trim();
76
+ switch (timeFormat) {
77
+ case "integer":
78
+ case "double":
79
+ return parseNumericTime(trimmed);
80
+ case "date":
81
+ return parseTemporal(trimmed, "date");
82
+ case "dateTime":
83
+ return parseTemporal(trimmed, "dateTime");
84
+ case null:
85
+ return NUMERIC.test(trimmed) ? parseNumericTime(trimmed) : parseTemporal(trimmed, "dateTime");
86
+ default: {
87
+ const name = timeFormat;
88
+ throw new GraphFormatError("E_COLUMN_TYPE", `unknown timeformat ${name}`, { timeFormat: name });
89
+ }
90
+ }
91
+ }
92
+ function formatTemporal(value, kind) {
93
+ switch (kind) {
94
+ case "date": {
95
+ const d = new Date(value);
96
+ return `${year(d)}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())}`;
97
+ }
98
+ case "dateTime":
99
+ case "localDateTime": {
100
+ const d = new Date(value);
101
+ const date = `${year(d)}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())}`;
102
+ const time = clock(d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds());
103
+ return kind === "dateTime" ? `${date}T${time}Z` : `${date}T${time}`;
104
+ }
105
+ case "time":
106
+ case "localTime": {
107
+ const ms = (value % MS_PER_DAY + MS_PER_DAY) % MS_PER_DAY;
108
+ const h = Math.floor(ms / 36e5);
109
+ const m = Math.floor(ms % 36e5 / MS_PER_MINUTE);
110
+ const s = Math.floor(ms % MS_PER_MINUTE / 1e3);
111
+ const text = clock(h, m, s, ms % 1e3);
112
+ return kind === "time" ? `${text}Z` : text;
113
+ }
114
+ default: {
115
+ const name = kind;
116
+ throw new GraphFormatError("E_COLUMN_TYPE", `unknown temporal kind ${name}`, { kind: name });
117
+ }
118
+ }
119
+ }
120
+ function formatTimeValue(value, timeFormat) {
121
+ switch (timeFormat) {
122
+ case "date":
123
+ return formatTemporal(value, "date");
124
+ case "dateTime":
125
+ return formatTemporal(value, "dateTime");
126
+ case "integer":
127
+ case "double":
128
+ case null:
129
+ return String(value);
130
+ default: {
131
+ const name = timeFormat;
132
+ throw new GraphFormatError("E_COLUMN_TYPE", `unknown timeformat ${name}`, { timeFormat: name });
133
+ }
134
+ }
135
+ }
136
+ function timeTextCompanion(column) {
137
+ return {
138
+ name: column + TIME_TEXT_SUFFIX,
139
+ dtype: "string",
140
+ role: TIME_TEXT_ROLE,
141
+ nullable: true,
142
+ extra: { for: column }
143
+ };
144
+ }
145
+ function parseNumericTime(text) {
146
+ if (!NUMERIC.test(text)) {
147
+ throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is not a numeric time`, { value: text });
148
+ }
149
+ const value = Number(text);
150
+ return { value, text: String(value) === text ? null : text };
151
+ }
152
+ function parseDateTime(text, kind) {
153
+ const m = DATE_TIME$1.exec(text);
154
+ if (m === null) {
155
+ throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is not an ISO-8601 ${kind}`, { value: text, kind });
156
+ }
157
+ const y = Number(m[1]);
158
+ const month = Number(m[2]);
159
+ const day = Number(m[3]);
160
+ const hour = m[4] === void 0 ? 0 : Number(m[4]);
161
+ const minute = m[5] === void 0 ? 0 : Number(m[5]);
162
+ const second = m[6] === void 0 ? 0 : Number(m[6]);
163
+ const ms = m[7] === void 0 ? 0 : fractionMs(m[7]);
164
+ if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 24 || minute > 59 || second > 60) {
165
+ throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is out of range for a ${kind}`, { value: text, kind });
166
+ }
167
+ const d = /* @__PURE__ */ new Date(0);
168
+ d.setUTCFullYear(y, month - 1, day);
169
+ d.setUTCHours(hour, minute, second, ms);
170
+ if (d.getUTCDate() !== day && hour !== 24) {
171
+ throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is not a calendar date`, { value: text, kind });
172
+ }
173
+ const offset = m[8] === void 0 ? 0 : offsetMinutes(m[8]);
174
+ return d.getTime() - offset * MS_PER_MINUTE;
175
+ }
176
+ function parseTime(text, kind) {
177
+ const m = TIME$1.exec(text);
178
+ if (m === null) {
179
+ throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is not an ISO-8601 ${kind}`, { value: text, kind });
180
+ }
181
+ const hour = Number(m[1]);
182
+ const minute = Number(m[2]);
183
+ const second = m[3] === void 0 ? 0 : Number(m[3]);
184
+ const ms = m[4] === void 0 ? 0 : fractionMs(m[4]);
185
+ if (hour > 24 || minute > 59 || second > 60) {
186
+ throw new GraphFormatError("E_COLUMN_TYPE", `"${text}" is out of range for a ${kind}`, { value: text, kind });
187
+ }
188
+ const local = ((hour * 60 + minute) * 60 + second) * 1e3 + ms;
189
+ const offset = m[5] === void 0 ? 0 : offsetMinutes(m[5]);
190
+ const utc = local - offset * MS_PER_MINUTE;
191
+ return (utc % MS_PER_DAY + MS_PER_DAY) % MS_PER_DAY;
192
+ }
193
+ function fractionMs(digits) {
194
+ return Math.floor(Number(`0.${digits}`) * 1e3);
195
+ }
196
+ function offsetMinutes(zone) {
197
+ if (zone === "Z" || zone === "z") {
198
+ return 0;
199
+ }
200
+ const sign = zone.startsWith("-") ? -1 : 1;
201
+ const digits = zone.slice(1).replace(":", "");
202
+ const hours = Number(digits.slice(0, 2));
203
+ const minutes = digits.length > 2 ? Number(digits.slice(2, 4)) : 0;
204
+ return sign * (hours * 60 + minutes);
205
+ }
206
+ function pad2(n) {
207
+ return n < 10 ? `0${n}` : String(n);
208
+ }
209
+ function year(d) {
210
+ const y = d.getUTCFullYear();
211
+ const abs = String(Math.abs(y)).padStart(4, "0");
212
+ return y < 0 ? `-${abs}` : abs;
213
+ }
214
+ function clock(h, m, s, ms) {
215
+ const base = `${pad2(h)}:${pad2(m)}:${pad2(s)}`;
216
+ return ms === 0 ? base : `${base}.${String(ms).padStart(3, "0")}`;
217
+ }
218
+ const BOOLEAN = { dtype: "bool", kind: "boolean" };
219
+ const INTEGER = { dtype: "i32", kind: "integer" };
220
+ const LONG = { dtype: "f64", kind: "long", precision: true };
221
+ const FLOAT = { dtype: "f32", kind: "float" };
222
+ const DOUBLE = { dtype: "f64", kind: "double" };
223
+ const STRING = { dtype: "string", kind: "string" };
224
+ const DATE = { dtype: "f64", kind: "temporal", temporal: "date" };
225
+ const DATE_TIME = { dtype: "f64", kind: "temporal", temporal: "dateTime" };
226
+ const LOCAL_DATE_TIME = { dtype: "f64", kind: "temporal", temporal: "localDateTime" };
227
+ const TIME = { dtype: "f64", kind: "temporal", temporal: "time" };
228
+ const LOCAL_TIME = { dtype: "f64", kind: "temporal", temporal: "localTime" };
229
+ const DURATION = { dtype: "string", kind: "duration" };
230
+ const POINT = { dtype: "json", kind: "point" };
231
+ const RECORD = { dtype: "json", kind: "json" };
232
+ const GEXF_SCALARS = {
233
+ integer: INTEGER,
234
+ long: LONG,
235
+ double: DOUBLE,
236
+ float: FLOAT,
237
+ boolean: BOOLEAN,
238
+ string: STRING,
239
+ anyuri: STRING,
240
+ char: STRING,
241
+ byte: INTEGER,
242
+ short: INTEGER,
243
+ bigdecimal: STRING,
244
+ biginteger: STRING,
245
+ date: DATE,
246
+ datetime: DATE_TIME
247
+ };
248
+ const GRAPHML_SCALARS = {
249
+ boolean: BOOLEAN,
250
+ int: INTEGER,
251
+ long: LONG,
252
+ float: FLOAT,
253
+ double: DOUBLE,
254
+ string: STRING
255
+ };
256
+ const GML_SCALARS = {
257
+ int: INTEGER,
258
+ real: DOUBLE,
259
+ string: STRING,
260
+ record: RECORD
261
+ };
262
+ const NEO4J_SCALARS = {
263
+ int: INTEGER,
264
+ integer: LONG,
265
+ long: LONG,
266
+ float: FLOAT,
267
+ double: DOUBLE,
268
+ boolean: BOOLEAN,
269
+ byte: INTEGER,
270
+ short: INTEGER,
271
+ char: STRING,
272
+ string: STRING,
273
+ point: POINT,
274
+ date: DATE,
275
+ localtime: LOCAL_TIME,
276
+ time: TIME,
277
+ localdatetime: LOCAL_DATE_TIME,
278
+ datetime: DATE_TIME,
279
+ duration: DURATION
280
+ };
281
+ const TABLES = {
282
+ gexf: GEXF_SCALARS,
283
+ graphml: GRAPHML_SCALARS,
284
+ gml: GML_SCALARS,
285
+ neo4j: NEO4J_SCALARS
286
+ };
287
+ const INTEGER_TEXT$1 = /^[+-]?[0-9]+$/;
288
+ const DECIMAL_TEXT = /^[+-]?([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)?$/;
289
+ const I32_MIN = -2147483648;
290
+ const I32_MAX = 2147483647;
291
+ function mapDeclaredType(format, type, long) {
292
+ const table = TABLES[format];
293
+ let key = type.trim().toLowerCase();
294
+ let list2 = false;
295
+ if (format === "gexf" && key.startsWith("list") && key.length > 4) {
296
+ list2 = true;
297
+ key = key.slice(4);
298
+ } else if (format === "neo4j" && key.endsWith("[]")) {
299
+ list2 = true;
300
+ key = key.slice(0, -2);
301
+ } else if (format === "gml" && key === "list") {
302
+ return {
303
+ declared: type,
304
+ dtype: "list",
305
+ itemDtype: "json",
306
+ kind: "json",
307
+ list: true,
308
+ temporal: null,
309
+ precision: false
310
+ };
311
+ }
312
+ let entry = table[key];
313
+ if (entry === void 0) {
314
+ return null;
315
+ }
316
+ if (entry.kind === "long" && long === "string") {
317
+ entry = STRING;
318
+ }
319
+ return {
320
+ declared: type,
321
+ dtype: list2 ? "list" : entry.dtype,
322
+ itemDtype: list2 ? entry.dtype : null,
323
+ kind: entry.kind,
324
+ list: list2,
325
+ temporal: entry.temporal ?? null,
326
+ precision: entry.precision ?? false
327
+ };
328
+ }
329
+ function stringSpec(declared) {
330
+ return {
331
+ declared: declared ?? "",
332
+ dtype: "string",
333
+ itemDtype: null,
334
+ kind: "string",
335
+ list: false,
336
+ temporal: null,
337
+ precision: false
338
+ };
339
+ }
340
+ function parseScalarText(text, kind, temporal = null) {
341
+ switch (kind) {
342
+ case "boolean": {
343
+ const value = parseBooleanText(text);
344
+ if (value === null) {
345
+ throw typeError(text, kind);
346
+ }
347
+ return value;
348
+ }
349
+ case "integer": {
350
+ const trimmed = text.trim();
351
+ if (!INTEGER_TEXT$1.test(trimmed)) {
352
+ throw typeError(text, kind);
353
+ }
354
+ const value = Number(trimmed);
355
+ if (value < I32_MIN || value > I32_MAX) {
356
+ throw new GraphFormatError("E_COLUMN_TYPE", `"${trimmed}" is outside the 32-bit integer range`, {
357
+ value: trimmed,
358
+ kind
359
+ });
360
+ }
361
+ return value;
362
+ }
363
+ case "long": {
364
+ const trimmed = text.trim();
365
+ if (!INTEGER_TEXT$1.test(trimmed)) {
366
+ throw typeError(text, kind);
367
+ }
368
+ return Number(trimmed);
369
+ }
370
+ case "float":
371
+ case "double":
372
+ return parseDecimalText(text, kind);
373
+ case "string":
374
+ case "duration":
375
+ return text;
376
+ case "temporal": {
377
+ if (temporal === null) {
378
+ throw new GraphFormatError("E_COLUMN_TYPE", "a temporal value needs its temporal kind", { kind });
379
+ }
380
+ return parseTemporal(text.trim(), temporal).value;
381
+ }
382
+ case "point":
383
+ return parsePointText(text);
384
+ case "json":
385
+ return parseJsonText(text);
386
+ default: {
387
+ const name = kind;
388
+ throw new GraphFormatError("E_COLUMN_TYPE", `unknown value kind ${name}`, { kind: name });
389
+ }
390
+ }
391
+ }
392
+ function parseBooleanText(text) {
393
+ switch (text.trim().toLowerCase()) {
394
+ case "true":
395
+ case "1":
396
+ return true;
397
+ case "false":
398
+ case "0":
399
+ return false;
400
+ default:
401
+ return null;
402
+ }
403
+ }
404
+ function parseDecimalText(text, kind = "double") {
405
+ const trimmed = text.trim();
406
+ if (DECIMAL_TEXT.test(trimmed)) {
407
+ return Number(trimmed);
408
+ }
409
+ switch (trimmed) {
410
+ case "INF":
411
+ case "+INF":
412
+ case "Infinity":
413
+ case "+Infinity":
414
+ return Infinity;
415
+ case "-INF":
416
+ case "-Infinity":
417
+ return -Infinity;
418
+ case "NaN":
419
+ return NaN;
420
+ default:
421
+ throw typeError(text, kind);
422
+ }
423
+ }
424
+ function parsePointText(text) {
425
+ const trimmed = text.trim();
426
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
427
+ throw typeError(text, "point");
428
+ }
429
+ const out = {};
430
+ const inner = trimmed.slice(1, -1).trim();
431
+ if (inner.length === 0) {
432
+ return out;
433
+ }
434
+ for (const part of inner.split(",")) {
435
+ const colon = part.indexOf(":");
436
+ if (colon < 0) {
437
+ throw typeError(text, "point");
438
+ }
439
+ const key = part.slice(0, colon).trim().replace(/^["']|["']$/g, "");
440
+ const raw = part.slice(colon + 1).trim();
441
+ if (key.length === 0) {
442
+ throw typeError(text, "point");
443
+ }
444
+ if (DECIMAL_TEXT.test(raw)) {
445
+ out[key] = Number(raw);
446
+ } else if (raw.startsWith("'") && raw.endsWith("'") || raw.startsWith('"') && raw.endsWith('"')) {
447
+ out[key] = raw.slice(1, -1);
448
+ } else {
449
+ throw typeError(text, "point");
450
+ }
451
+ }
452
+ return out;
453
+ }
454
+ function parseJsonText(text) {
455
+ try {
456
+ return JSON.parse(text);
457
+ } catch {
458
+ throw typeError(text, "json");
459
+ }
460
+ }
461
+ function typeError(text, kind) {
462
+ const shown = text.length > 40 ? `${text.slice(0, 40)}...` : text;
463
+ return new GraphFormatError("E_COLUMN_TYPE", `"${shown}" is not a ${kind}`, { value: text, kind });
464
+ }
465
+ function splitListText(text, syntax) {
466
+ const trimmed = text.trim();
467
+ if (trimmed.length === 0) {
468
+ return [];
469
+ }
470
+ switch (syntax) {
471
+ case "gexf": {
472
+ if (isBracketed(trimmed)) {
473
+ return splitBracketed(trimmed);
474
+ }
475
+ const separator = firstSeparator(trimmed, ["|", ",", ";"]);
476
+ return separator === null ? [trimmed] : splitOn(trimmed, separator);
477
+ }
478
+ case "brackets":
479
+ return isBracketed(trimmed) ? splitBracketed(trimmed) : [trimmed];
480
+ case "pipe":
481
+ return splitOn(trimmed, "|");
482
+ case "comma":
483
+ return splitOn(trimmed, ",");
484
+ case "semicolon":
485
+ return splitOn(trimmed, ";");
486
+ default: {
487
+ const name = syntax;
488
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown list syntax ${name}`, {
489
+ option: "listSyntax",
490
+ found: name
491
+ });
492
+ }
493
+ }
494
+ }
495
+ function joinListText(items, syntax) {
496
+ switch (syntax) {
497
+ case "gexf":
498
+ case "brackets":
499
+ return `[${items.map(quoteBracketItem).join(", ")}]`;
500
+ case "pipe":
501
+ return items.join("|");
502
+ case "comma":
503
+ return items.join(",");
504
+ case "semicolon":
505
+ return items.join(";");
506
+ default: {
507
+ const name = syntax;
508
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown list syntax ${name}`, {
509
+ option: "listSyntax",
510
+ found: name
511
+ });
512
+ }
513
+ }
514
+ }
515
+ function isBracketed(text) {
516
+ return text.length >= 2 && text.startsWith("[") && text.endsWith("]");
517
+ }
518
+ function firstSeparator(text, candidates) {
519
+ for (const candidate of candidates) {
520
+ if (text.includes(candidate)) {
521
+ return candidate;
522
+ }
523
+ }
524
+ return null;
525
+ }
526
+ function splitOn(text, separator) {
527
+ return text.split(separator).map((item) => item.trim());
528
+ }
529
+ function splitBracketed(text) {
530
+ const inner = text.slice(1, -1);
531
+ const items = [];
532
+ const n = inner.length;
533
+ if (inner.trim().length === 0) {
534
+ return items;
535
+ }
536
+ let i = 0;
537
+ for (; ; ) {
538
+ while (i < n && isSpace(inner.charCodeAt(i))) {
539
+ i++;
540
+ }
541
+ let item = "";
542
+ if (i < n && (inner[i] === '"' || inner[i] === "'")) {
543
+ const quote = inner[i];
544
+ i++;
545
+ while (i < n) {
546
+ if (inner[i] === quote) {
547
+ if (inner[i + 1] === quote) {
548
+ item += quote;
549
+ i += 2;
550
+ continue;
551
+ }
552
+ i++;
553
+ break;
554
+ }
555
+ item += inner[i];
556
+ i++;
557
+ }
558
+ }
559
+ let end = inner.indexOf(",", i);
560
+ if (end < 0) {
561
+ end = n;
562
+ }
563
+ item += inner.slice(i, end).trim();
564
+ items.push(item);
565
+ i = end;
566
+ if (i >= n) {
567
+ return items;
568
+ }
569
+ i++;
570
+ if (inner.slice(i).trim().length === 0) {
571
+ items.push("");
572
+ return items;
573
+ }
574
+ }
575
+ }
576
+ function isSpace(c) {
577
+ return c === 32 || c === 9 || c === 13 || c === 10;
578
+ }
579
+ function quoteBracketItem(item) {
580
+ if (item.length === 0 || /[,[\]"']/.test(item) || item !== item.trim()) {
581
+ return `"${item.replace(/"/g, '""')}"`;
582
+ }
583
+ return item;
584
+ }
585
+ const UNKNOWN_TYPE_CODE = UNKNOWN_ATTR_TYPE_CODE;
586
+ const RENAMED_CODE = COLUMN_RENAMED_CODE;
587
+ const DICT_SAMPLE_ROWS = 1024;
588
+ function declareAttribute(input) {
589
+ const issues = [];
590
+ const listSyntax = input.listSyntax ?? "gexf";
591
+ let spec;
592
+ if (input.type === null) {
593
+ spec = stringSpec(null);
594
+ } else {
595
+ const mapped = mapDeclaredType(input.format, input.type, input.long);
596
+ if (mapped === null) {
597
+ spec = stringSpec(input.type);
598
+ issues.push({
599
+ category: "unsupported",
600
+ code: UNKNOWN_TYPE_CODE,
601
+ message: `${input.format} type "${input.type}" is not supported; values kept as text`
602
+ });
603
+ } else {
604
+ spec = mapped;
605
+ }
606
+ }
607
+ const { name, renamed } = resolveName(input);
608
+ if (renamed) {
609
+ issues.push({
610
+ category: "coercion",
611
+ code: RENAMED_CODE,
612
+ message: `attribute "${input.title ?? input.id ?? ""}" renamed to "${name}": the name was taken`
613
+ });
614
+ }
615
+ const decl = {
616
+ name,
617
+ dtype: spec.dtype,
618
+ nullable: true,
619
+ origin: {
620
+ format: input.format,
621
+ id: input.id,
622
+ title: input.title !== null && input.title !== name ? input.title : null,
623
+ type: input.type,
624
+ namespace: input.namespace ?? null
625
+ }
626
+ };
627
+ if (spec.list) {
628
+ decl.itemDtype = spec.itemDtype ?? "string";
629
+ }
630
+ if (input.role !== void 0 && input.role !== null) {
631
+ decl.role = input.role;
632
+ }
633
+ if (input.dynamic === true) {
634
+ decl.dynamic = true;
635
+ }
636
+ const optionsText = input.optionsText ?? null;
637
+ if (optionsText !== null) {
638
+ try {
639
+ const options = splitListText(optionsText, listSyntax).map((item) => parseItem(item, spec));
640
+ decl.options = options;
641
+ if (spec.dtype === "string" && !spec.list && options.every((o) => typeof o === "string")) {
642
+ decl.dtype = "dict";
643
+ spec = { ...spec, dtype: "dict" };
644
+ }
645
+ } catch (err) {
646
+ issues.push({
647
+ category: "validation-error",
648
+ code: BAD_OPTIONS_CODE,
649
+ message: `options "${optionsText}" of attribute "${name}" do not parse as ${spec.declared}: ${messageOf(err)}`
650
+ });
651
+ }
652
+ }
653
+ const defaultText = input.defaultText ?? null;
654
+ if (defaultText !== null) {
655
+ try {
656
+ decl.default = parseDeclaredValue(defaultText, spec, listSyntax);
657
+ } catch (err) {
658
+ issues.push({
659
+ category: "validation-error",
660
+ code: BAD_DEFAULT_CODE,
661
+ message: `default "${defaultText}" of attribute "${name}" does not parse as ${spec.declared}: ${messageOf(err)}`
662
+ });
663
+ }
664
+ }
665
+ const companion = spec.temporal !== null && !spec.list ? timeTextCompanion(name) : null;
666
+ return { decl, spec, listSyntax, companion, renamed, issues };
667
+ }
668
+ function parseDeclaredValue(text, spec, listSyntax) {
669
+ if (spec.list) {
670
+ return splitListText(text, listSyntax).map((item) => parseItem(item, spec));
671
+ }
672
+ return parseItem(text, spec);
673
+ }
674
+ function parseDeclaredTemporal(text, spec) {
675
+ if (spec.temporal === null || spec.list) {
676
+ throw new GraphFormatError("E_COLUMN_TYPE", `"${spec.declared}" is not a scalar temporal type`, {
677
+ declared: spec.declared
678
+ });
679
+ }
680
+ return parseTemporal(text.trim(), spec.temporal);
681
+ }
682
+ const INTEGER_TEXT = /^[+-]?[0-9]+$/;
683
+ function losesPrecision(spec, text) {
684
+ if (!spec.precision) {
685
+ return false;
686
+ }
687
+ const trimmed = text.trim();
688
+ if (!INTEGER_TEXT.test(trimmed)) {
689
+ return false;
690
+ }
691
+ const n = Number(trimmed);
692
+ return !Number.isFinite(n) || BigInt(trimmed) !== BigInt(n);
693
+ }
694
+ function declareOn(sink, domain, decl) {
695
+ return domain === "node" ? sink.declareNodeColumn(decl) : sink.declareEdgeColumn(decl);
696
+ }
697
+ function declareResolved(sink, domain, decl, report, where) {
698
+ let current = decl;
699
+ let renamed = false;
700
+ let roleDropped = false;
701
+ for (; ; ) {
702
+ try {
703
+ return { handle: declareOn(sink, domain, current), decl: current, renamed, roleDropped };
704
+ } catch (err) {
705
+ if (!(err instanceof GraphFormatError)) {
706
+ throw err;
707
+ }
708
+ if (err.code === "E_COLUMN_EXISTS" && !renamed) {
709
+ const taken = takenIn(sink, domain);
710
+ const id = current.origin?.id ?? null;
711
+ const name = uniqueColumnName(current.name, id, taken);
712
+ report.warning(
713
+ "coercion",
714
+ COLUMN_RENAMED_CODE,
715
+ `${domain} column "${current.name}" renamed to "${name}": the sink already holds that name with another shape`,
716
+ { ...where, element: where?.element ?? current.name }
717
+ );
718
+ current = { ...current, name };
719
+ renamed = true;
720
+ continue;
721
+ }
722
+ if (err.code === "E_DUPLICATE_ROLE" && current.role !== void 0 && !roleDropped) {
723
+ const holder = typeof err.details.holder === "string" ? err.details.holder : "another column";
724
+ report.warning(
725
+ "coercion",
726
+ ROLE_TAKEN_CODE,
727
+ `${domain} column "${current.name}" loses the ${current.role} role: "${holder}" already holds it`,
728
+ { ...where, element: where?.element ?? current.name }
729
+ );
730
+ const { role: _role, ...withoutRole } = current;
731
+ current = withoutRole;
732
+ roleDropped = true;
733
+ continue;
734
+ }
735
+ throw err;
736
+ }
737
+ }
738
+ }
739
+ function declareCompanion(sink, domain, companion, report) {
740
+ try {
741
+ return declareOn(sink, domain, companion);
742
+ } catch (err) {
743
+ if (err instanceof GraphFormatError && err.code === "E_DUPLICATE_ROLE") {
744
+ report?.warnOnce(
745
+ "coercion",
746
+ ROLE_TAKEN_CODE,
747
+ `${domain} column "${companion.name}" is declared without the timeText role: another companion holds it (exporters use extra.for)`,
748
+ { element: companion.name }
749
+ );
750
+ const { role: _role, ...withoutRole } = companion;
751
+ return declareOn(sink, domain, withoutRole);
752
+ }
753
+ throw err;
754
+ }
755
+ }
756
+ function takenIn(sink, domain) {
757
+ return (name) => (domain === "node" ? sink.nodeColumn(name) : sink.edgeColumn(name)) !== INVALID_INDEX;
758
+ }
759
+ function uniqueColumnName(name, id, taken) {
760
+ if (!taken(name)) {
761
+ return name;
762
+ }
763
+ const base = id === null ? name : `${name}#${id}`;
764
+ if (id !== null && !taken(base)) {
765
+ return base;
766
+ }
767
+ for (let n = 2; ; n++) {
768
+ const candidate = `${base}#${n}`;
769
+ if (!taken(candidate)) {
770
+ return candidate;
771
+ }
772
+ }
773
+ }
774
+ class DictHeuristic {
775
+ /**
776
+ * Create a heuristic.
777
+ * @param sampleRows - rows observed before deciding; DICT_SAMPLE_ROWS by default
778
+ */
779
+ constructor(sampleRows = DICT_SAMPLE_ROWS) {
780
+ this.distinct = /* @__PURE__ */ new Set();
781
+ this.rowsSeen = 0;
782
+ this.sampleRows = sampleRows;
783
+ }
784
+ /**
785
+ * Rows observed so far.
786
+ * @returns the count
787
+ */
788
+ get rows() {
789
+ return this.rowsSeen;
790
+ }
791
+ /**
792
+ * Distinct values observed so far.
793
+ * @returns the count
794
+ */
795
+ get distinctCount() {
796
+ return this.distinct.size;
797
+ }
798
+ /**
799
+ * Whether the sample is full.
800
+ * @returns true once sampleRows values were observed
801
+ */
802
+ get decided() {
803
+ return this.rowsSeen >= this.sampleRows;
804
+ }
805
+ /**
806
+ * Observe one value.
807
+ * @param value - the value text
808
+ * @returns true when this observation completed the sample
809
+ */
810
+ observe(value) {
811
+ this.rowsSeen++;
812
+ if (this.distinct.size < this.rowsSeen) {
813
+ this.distinct.add(value);
814
+ }
815
+ return this.rowsSeen === this.sampleRows;
816
+ }
817
+ /**
818
+ * The dtype the sample suggests: dict when the distinct count is below rows / 2 (and at least
819
+ * one row was seen), string otherwise. Valid before the sample is full (end of input).
820
+ * @returns "dict" or "string"
821
+ */
822
+ decide() {
823
+ return this.rowsSeen > 0 && this.distinct.size * 2 < this.rowsSeen ? "dict" : "string";
824
+ }
825
+ }
826
+ function resolveName(input) {
827
+ const preferred = input.title !== null && input.title.length > 0 ? input.title : input.id;
828
+ if (preferred === null || preferred.length === 0) {
829
+ throw new GraphFormatError("E_COLUMN_TYPE", "an attribute declaration needs a title or an id", {
830
+ field: "name",
831
+ format: input.format
832
+ });
833
+ }
834
+ if (input.taken === void 0) {
835
+ return { name: preferred, renamed: false };
836
+ }
837
+ const name = uniqueColumnName(preferred, input.id, input.taken);
838
+ return { name, renamed: name !== preferred };
839
+ }
840
+ function parseItem(text, spec) {
841
+ return parseScalarText(text, spec.kind, spec.temporal);
842
+ }
843
+ function messageOf(err) {
844
+ return err instanceof Error ? err.message : String(err);
845
+ }
846
+ const DIRECTED_COLUMN = "graphty.directed";
847
+ const PAIR_COLUMN = "graphty.pair";
848
+ const MUTUAL_COLUMN = "graphty.mutual";
849
+ const DIRECTED_DECL = { name: DIRECTED_COLUMN, dtype: "bool", role: "directed" };
850
+ const PAIR_DECL = { name: PAIR_COLUMN, dtype: "u32", role: "pair", refersTo: "edge" };
851
+ const MUTUAL_DECL = { name: MUTUAL_COLUMN, dtype: "bool", role: "mutual" };
852
+ class DirectionResolver {
853
+ /**
854
+ * Create a resolver.
855
+ * @param sink - the sink edges are pushed into
856
+ * @param report - the report issues are recorded in
857
+ * @param policy - the resolved onMixedDirection option
858
+ */
859
+ constructor(sink, report, policy) {
860
+ this.directedHandle = INVALID_INDEX;
861
+ this.pairHandle = INVALID_INDEX;
862
+ this.mutualHandle = INVALID_INDEX;
863
+ this.firstEdge = -1;
864
+ this.nextEdge = -1;
865
+ this.forcedCount = 0;
866
+ this.headerSet = false;
867
+ this.mirrorOfLast = INVALID_INDEX;
868
+ this.sink = sink;
869
+ this.report = report;
870
+ this.policy = policy;
871
+ }
872
+ /**
873
+ * The sink's current direction.
874
+ * @returns true when directed
875
+ */
876
+ get directed() {
877
+ return this.sink.directed;
878
+ }
879
+ /**
880
+ * Whether the reserved direction columns exist (the sink holds expanded edges).
881
+ * @returns true once graphty.pair was declared or adopted
882
+ */
883
+ get expanded() {
884
+ return this.pairHandle !== INVALID_INDEX;
885
+ }
886
+ /**
887
+ * Rule 1 of design section 8.4: set the sink's direction from the file's header (or the
888
+ * `defaultDirected` option for a file that declares none) before the first edge. Under the
889
+ * "directed" / "undirected" policies the policy's direction is used instead and the difference
890
+ * is reported. A refusal (E_DIRECTED: locked, or a non-empty directed sink for an undirected
891
+ * header) is recorded as a coercion issue and the sink's direction stands; a non-empty unlocked
892
+ * undirected sink with a directed header is expanded in place.
893
+ * @param headerDirected - the direction the file declares (or the default)
894
+ * @param where - the line of the header, when known
895
+ */
896
+ setHeader(headerDirected, where) {
897
+ this.headerSet = true;
898
+ let wanted = headerDirected;
899
+ if (this.policy === "directed" || this.policy === "undirected") {
900
+ wanted = this.policy === "directed";
901
+ if (wanted !== headerDirected) {
902
+ this.report.warning(
903
+ "coercion",
904
+ DIRECTION_FORCED_CODE,
905
+ `the file declares ${direction(headerDirected)} edges; read as ${direction(wanted)} per onMixedDirection`,
906
+ { line: where?.line ?? null }
907
+ );
908
+ }
909
+ }
910
+ const { sink } = this;
911
+ if (sink.directed === wanted) {
912
+ this.adoptExisting();
913
+ return;
914
+ }
915
+ try {
916
+ if (wanted && sink.edgeCount > 0 && !sink.directedLocked) {
917
+ const before = sink.edgeCount;
918
+ sink.setDirected(true, { expand: true });
919
+ this.adoptExpansionColumns();
920
+ this.report.counts.expandedMixed += before;
921
+ } else {
922
+ sink.setDirected(wanted);
923
+ }
924
+ } catch (err) {
925
+ if (!(err instanceof GraphFormatError) || err.code !== "E_DIRECTED") {
926
+ throw err;
927
+ }
928
+ this.report.warning(
929
+ "coercion",
930
+ DIRECTION_REFUSED_CODE,
931
+ `the file is ${direction(wanted)} but the sink is ${direction(sink.directed)} (${reasonOf(err)}); read as ${direction(sink.directed)}`,
932
+ { line: where?.line ?? null }
933
+ );
934
+ }
935
+ this.adoptExisting();
936
+ }
937
+ /**
938
+ * Pick up reserved columns a caller's sink already holds (an earlier expansion), so every edge
939
+ * pushed from now on gets its graphty.directed value.
940
+ */
941
+ adoptExisting() {
942
+ if (this.pairHandle === INVALID_INDEX && this.sink.edgeColumn(PAIR_COLUMN) !== INVALID_INDEX) {
943
+ this.adoptExpansionColumns();
944
+ }
945
+ }
946
+ /**
947
+ * The mirror half of the edge most recently pushed by addEdge(), when it was expanded into a
948
+ * pair; INVALID_INDEX otherwise. An importer that learns an edge's weight after pushing it
949
+ * (a GEXF attvalue) sets it on both halves.
950
+ * @returns the mirror's logical index, or INVALID_INDEX
951
+ */
952
+ get lastMirror() {
953
+ return this.mirrorOfLast;
954
+ }
955
+ /**
956
+ * Rule 2 of design section 8.4: push one source edge. An edge whose direction equals the
957
+ * sink's is one addEdge; one that differs is expanded, forced or refused per the policy.
958
+ * @param source - the source id
959
+ * @param target - the target id
960
+ * @param kind - the edge's direction in the file
961
+ * @param weight - the weight, or undefined when the file gives none
962
+ * @param where - the line and element, for issues
963
+ * @returns the logical index of the (primary) edge
964
+ */
965
+ addEdge(source, target, kind, weight, where) {
966
+ if (!this.headerSet) {
967
+ throw new GraphFormatError("E_DIRECTED", "DirectionResolver.setHeader() must run before the first edge", {
968
+ reason: "header not set"
969
+ });
970
+ }
971
+ const { sink } = this;
972
+ this.mirrorOfLast = INVALID_INDEX;
973
+ const edgeDirected = kind !== "undirected";
974
+ if (edgeDirected === sink.directed && kind !== "mutual") {
975
+ const e = sink.addEdge(source, target, weight);
976
+ this.track(e);
977
+ if (this.directedHandle !== INVALID_INDEX) {
978
+ sink.setEdgeValue(this.directedHandle, e, edgeDirected);
979
+ }
980
+ return e;
981
+ }
982
+ switch (this.policy) {
983
+ case "directed":
984
+ case "undirected": {
985
+ this.forcedCount++;
986
+ this.report.warnOnce(
987
+ "coercion",
988
+ DIRECTION_FORCED_CODE,
989
+ `${kind} edges read as ${direction(sink.directed)} per onMixedDirection`,
990
+ where
991
+ );
992
+ const e = sink.addEdge(source, target, weight);
993
+ this.track(e);
994
+ if (this.directedHandle !== INVALID_INDEX) {
995
+ sink.setEdgeValue(this.directedHandle, e, sink.directed);
996
+ }
997
+ return e;
998
+ }
999
+ case "error":
1000
+ this.report.error(
1001
+ "validation-error",
1002
+ MIXED_DIRECTION_CODE,
1003
+ `${kind} edge in a ${direction(sink.directed)} graph (onMixedDirection: "error")`,
1004
+ where
1005
+ );
1006
+ throw this.report.abort("mixed direction refused", { code: MIXED_DIRECTION_CODE });
1007
+ case "expand":
1008
+ return this.expand(source, target, kind, weight, where);
1009
+ default: {
1010
+ const name = this.policy;
1011
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown onMixedDirection ${name}`, {
1012
+ option: "onMixedDirection",
1013
+ found: name
1014
+ });
1015
+ }
1016
+ }
1017
+ }
1018
+ /**
1019
+ * How many edges were pushed with a direction other than their own under the forcing policies.
1020
+ * @returns the count
1021
+ */
1022
+ get forced() {
1023
+ return this.forcedCount;
1024
+ }
1025
+ /**
1026
+ * Expand one edge that differs from the sink's direction.
1027
+ * @param source - the source id
1028
+ * @param target - the target id
1029
+ * @param kind - the edge's direction in the file
1030
+ * @param weight - the weight, or undefined
1031
+ * @param where - the line and element, for issues
1032
+ * @returns the primary edge index
1033
+ */
1034
+ expand(source, target, kind, weight, where) {
1035
+ const { sink } = this;
1036
+ if (!sink.directed) {
1037
+ try {
1038
+ const before = sink.edgeCount;
1039
+ sink.setDirected(true, { expand: true });
1040
+ this.adoptExpansionColumns();
1041
+ this.report.counts.expandedMixed += before;
1042
+ } catch (err) {
1043
+ if (!(err instanceof GraphFormatError) || err.code !== "E_DIRECTED") {
1044
+ throw err;
1045
+ }
1046
+ this.report.error(
1047
+ "coercion",
1048
+ DIRECTION_REFUSED_CODE,
1049
+ `${direction(true)} edge in a locked ${direction(false)} sink cannot be expanded`,
1050
+ where
1051
+ );
1052
+ throw this.report.abort("the sink refused the expansion to directed", {
1053
+ code: DIRECTION_REFUSED_CODE,
1054
+ reason: err.details.reason
1055
+ });
1056
+ }
1057
+ if (kind === "directed") {
1058
+ const e = sink.addEdge(source, target, weight);
1059
+ this.track(e);
1060
+ sink.setEdgeValue(this.directedHandle, e, true);
1061
+ return e;
1062
+ }
1063
+ }
1064
+ this.ensureExpansionColumns();
1065
+ const first = sink.addEdge(source, target, weight);
1066
+ this.track(first);
1067
+ const mutual = kind === "mutual";
1068
+ sink.setEdgeValue(this.directedHandle, first, mutual);
1069
+ this.report.counts.expandedMixed++;
1070
+ if (mutual) {
1071
+ this.ensureMutualColumn();
1072
+ sink.setEdgeValue(this.mutualHandle, first, true);
1073
+ }
1074
+ const u = sink.indexOf(source);
1075
+ const v = sink.indexOf(target);
1076
+ if (u === v) {
1077
+ return first;
1078
+ }
1079
+ const mirror = sink.addEdge(target, source, weight);
1080
+ this.track(mirror);
1081
+ this.mirrorOfLast = mirror;
1082
+ sink.setEdgeValue(this.directedHandle, mirror, mutual);
1083
+ sink.setEdgeValue(this.pairHandle, first, mirror);
1084
+ sink.setEdgeValue(this.pairHandle, mirror, first);
1085
+ return first;
1086
+ }
1087
+ /**
1088
+ * Note an edge index this resolver pushed, for the backfill of graphty.directed.
1089
+ * @param e - the edge index
1090
+ */
1091
+ track(e) {
1092
+ if (this.firstEdge < 0) {
1093
+ this.firstEdge = e;
1094
+ }
1095
+ this.nextEdge = e + 1;
1096
+ }
1097
+ /**
1098
+ * Pick up the reserved columns the builder wrote during its own expansion.
1099
+ */
1100
+ adoptExpansionColumns() {
1101
+ this.directedHandle = this.sink.declareEdgeColumn(DIRECTED_DECL);
1102
+ this.pairHandle = this.sink.declareEdgeColumn(PAIR_DECL);
1103
+ }
1104
+ /**
1105
+ * Declare the reserved columns on first use in a directed sink and backfill graphty.directed
1106
+ * = true for every edge this resolver pushed so far (they were directed in the source).
1107
+ */
1108
+ ensureExpansionColumns() {
1109
+ if (this.pairHandle !== INVALID_INDEX) {
1110
+ return;
1111
+ }
1112
+ this.directedHandle = this.sink.declareEdgeColumn(DIRECTED_DECL);
1113
+ this.pairHandle = this.sink.declareEdgeColumn(PAIR_DECL);
1114
+ if (this.firstEdge >= 0) {
1115
+ for (let e = this.firstEdge; e < this.nextEdge; e++) {
1116
+ this.sink.setEdgeValue(this.directedHandle, e, true);
1117
+ }
1118
+ }
1119
+ }
1120
+ /** Declare graphty.mutual on first use. */
1121
+ ensureMutualColumn() {
1122
+ if (this.mutualHandle === INVALID_INDEX) {
1123
+ this.mutualHandle = this.sink.declareEdgeColumn(MUTUAL_DECL);
1124
+ }
1125
+ }
1126
+ }
1127
+ function reasonOf(err) {
1128
+ const { reason } = err.details;
1129
+ return typeof reason === "string" ? reason : "refused";
1130
+ }
1131
+ function direction(directed) {
1132
+ return directed ? "directed" : "undirected";
1133
+ }
1134
+ function pairFolding(snapshot, options = {}) {
1135
+ const foldMutual = options.foldMutual ?? false;
1136
+ const directedColumn = roleColumn(snapshot.edges.byRole("directed"), "bool");
1137
+ const pairColumn = roleColumn(snapshot.edges.byRole("pair"), "u32");
1138
+ const mutualColumn = roleColumn(snapshot.edges.byRole("mutual"), "bool");
1139
+ const { edgeCount } = snapshot;
1140
+ const mateOf = (e) => {
1141
+ if (pairColumn === null || !pairColumn.isSet(e)) {
1142
+ return INVALID_INDEX;
1143
+ }
1144
+ const mate = pairColumn.value(e);
1145
+ return typeof mate === "number" && mate !== INVALID_INDEX ? mate : INVALID_INDEX;
1146
+ };
1147
+ const sourceDirected = (e) => directedColumn === null || !directedColumn.isSet(e) || directedColumn.value(e) === true;
1148
+ const isMutual = (e) => {
1149
+ if (mutualColumn === null) {
1150
+ return false;
1151
+ }
1152
+ if (mutualColumn.isSet(e) && mutualColumn.value(e) === true) {
1153
+ return true;
1154
+ }
1155
+ const mate = mateOf(e);
1156
+ return mate !== INVALID_INDEX && mutualColumn.isSet(mate) && mutualColumn.value(mate) === true;
1157
+ };
1158
+ const folded = (e) => {
1159
+ const mate = mateOf(e);
1160
+ if (mate === INVALID_INDEX || mate >= e) {
1161
+ return false;
1162
+ }
1163
+ return !sourceDirected(e) || foldMutual && isMutual(e);
1164
+ };
1165
+ let mutualCount = 0;
1166
+ if (mutualColumn !== null) {
1167
+ for (let e = 0; e < edgeCount; e++) {
1168
+ if (mutualColumn.isSet(e) && mutualColumn.value(e) === true && mateOf(e) > e) {
1169
+ mutualCount++;
1170
+ }
1171
+ }
1172
+ }
1173
+ return { expanded: pairColumn !== null, mutualCount, folded, mateOf, sourceDirected, isMutual };
1174
+ }
1175
+ function roleColumn(column, dtype) {
1176
+ return column !== null && column.dtype === dtype ? column : null;
1177
+ }
1178
+ const CANONICAL_INTEGER = /^-?(0|[1-9][0-9]*)$/;
1179
+ function canonicalId(text) {
1180
+ if (text !== "-0" && CANONICAL_INTEGER.test(text)) {
1181
+ const n = Number(text);
1182
+ if (Number.isSafeInteger(n)) {
1183
+ return n;
1184
+ }
1185
+ }
1186
+ return text;
1187
+ }
1188
+ function isCanonicalIntegerText(text) {
1189
+ return typeof canonicalId(text) === "number";
1190
+ }
1191
+ function coerceIdText(text, mode) {
1192
+ switch (mode) {
1193
+ case "keep":
1194
+ case "string":
1195
+ return text;
1196
+ case "canonical":
1197
+ return canonicalId(text);
1198
+ case "number": {
1199
+ if (text.trim().length === 0) {
1200
+ throw invalidId(text, "empty text is not a number");
1201
+ }
1202
+ const n = Number(text);
1203
+ if (!Number.isFinite(n)) {
1204
+ throw invalidId(text, "not a finite number");
1205
+ }
1206
+ return n;
1207
+ }
1208
+ default: {
1209
+ const name = mode;
1210
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown id coercion ${name}`, { option: "ids", found: name });
1211
+ }
1212
+ }
1213
+ }
1214
+ function coerceId(value, mode) {
1215
+ if (typeof value === "string") {
1216
+ return coerceIdText(value, mode);
1217
+ }
1218
+ if (typeof value === "number") {
1219
+ if (!Number.isFinite(value)) {
1220
+ throw invalidId(value, "not a finite number");
1221
+ }
1222
+ return mode === "string" ? String(value) : value;
1223
+ }
1224
+ if (mode === "string" && (typeof value === "boolean" || typeof value === "bigint" || value === null)) {
1225
+ return String(value);
1226
+ }
1227
+ throw invalidId(value, `a ${value === null ? "null" : typeof value} is not an id under ids: "${mode}"`);
1228
+ }
1229
+ function invalidId(value, reason) {
1230
+ const shown = typeof value === "string" || typeof value === "number" ? value : typeof value;
1231
+ return new GraphFormatError("E_INVALID_ID", `invalid node id ${JSON.stringify(shown)}: ${reason}`, {
1232
+ reason,
1233
+ value: typeof value === "bigint" ? value.toString() : value
1234
+ });
1235
+ }
1236
+ class IdCoercer {
1237
+ /**
1238
+ * Create a coercer.
1239
+ * @param mode - the rule
1240
+ */
1241
+ constructor(mode) {
1242
+ this.mergeCount = 0;
1243
+ this.lastMerge = null;
1244
+ this.mode = mode;
1245
+ this.firstText = mode === "number" ? /* @__PURE__ */ new Map() : null;
1246
+ }
1247
+ /**
1248
+ * Coerce a text cell, recording a merge under "number" when a different text already produced
1249
+ * the same number.
1250
+ * @param text - the cell text
1251
+ * @returns the id
1252
+ */
1253
+ text(text) {
1254
+ const id = coerceIdText(text, this.mode);
1255
+ this.lastMerge = null;
1256
+ if (this.firstText !== null && typeof id === "number") {
1257
+ const previous = this.firstText.get(id);
1258
+ if (previous === void 0) {
1259
+ this.firstText.set(id, text);
1260
+ } else if (previous !== text) {
1261
+ this.mergeCount++;
1262
+ this.lastMerge = { id, text, previousText: previous };
1263
+ }
1264
+ }
1265
+ return id;
1266
+ }
1267
+ /**
1268
+ * Coerce a typed value.
1269
+ * @param value - the value
1270
+ * @returns the id
1271
+ */
1272
+ value(value) {
1273
+ if (typeof value === "string") {
1274
+ return this.text(value);
1275
+ }
1276
+ this.lastMerge = null;
1277
+ return coerceId(value, this.mode);
1278
+ }
1279
+ }
1280
+ const DECODE_SLICE = 256 * 1024;
1281
+ const BOM = String.fromCharCode(65279);
1282
+ function isImportInput(input) {
1283
+ if (typeof input === "string" || input instanceof Uint8Array) {
1284
+ return true;
1285
+ }
1286
+ if (typeof input !== "object" || input === null) {
1287
+ return false;
1288
+ }
1289
+ return typeof input.getReader === "function" || Symbol.asyncIterator in input;
1290
+ }
1291
+ function inputLength(input) {
1292
+ if (typeof input === "string") {
1293
+ return input.length;
1294
+ }
1295
+ if (input instanceof Uint8Array) {
1296
+ return input.byteLength;
1297
+ }
1298
+ return null;
1299
+ }
1300
+ function throwIfAborted(signal) {
1301
+ if (signal === null || signal === void 0 || !signal.aborted) {
1302
+ return;
1303
+ }
1304
+ const { reason } = signal;
1305
+ if (reason === void 0) {
1306
+ throw abortError();
1307
+ }
1308
+ throw reason;
1309
+ }
1310
+ function abortError() {
1311
+ const message = "The import was aborted";
1312
+ if (typeof DOMException === "function") {
1313
+ return new DOMException(message, "AbortError");
1314
+ }
1315
+ const err = new Error(message);
1316
+ err.name = "AbortError";
1317
+ return err;
1318
+ }
1319
+ async function* textChunks(input, report, options = {}) {
1320
+ const signal = options.signal ?? null;
1321
+ const onProgress = options.onProgress ?? null;
1322
+ const total = inputLength(input);
1323
+ let done = 0;
1324
+ let first = true;
1325
+ const emit = (text) => {
1326
+ if (first && text.length > 0) {
1327
+ first = false;
1328
+ return text.startsWith(BOM) ? text.slice(1) : text;
1329
+ }
1330
+ return text;
1331
+ };
1332
+ throwIfAborted(signal);
1333
+ if (typeof input === "string") {
1334
+ const text = emit(input);
1335
+ done = input.length;
1336
+ if (text.length > 0) {
1337
+ yield text;
1338
+ }
1339
+ onProgress?.(done, total ?? void 0);
1340
+ return;
1341
+ }
1342
+ const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
1343
+ const decode = (bytes, stream) => {
1344
+ try {
1345
+ return decoder.decode(bytes, { stream });
1346
+ } catch (err) {
1347
+ if (err instanceof TypeError) {
1348
+ report.fail(INVALID_UTF8_CODE, `invalid UTF-8 near byte ${done}`, void 0, { byteOffset: done });
1349
+ }
1350
+ throw err;
1351
+ }
1352
+ };
1353
+ if (input instanceof Uint8Array) {
1354
+ for (let offset = 0; offset < input.byteLength; offset += DECODE_SLICE) {
1355
+ throwIfAborted(signal);
1356
+ const slice = input.subarray(offset, Math.min(offset + DECODE_SLICE, input.byteLength));
1357
+ const text = emit(decode(slice, offset + DECODE_SLICE < input.byteLength));
1358
+ done = Math.min(offset + DECODE_SLICE, input.byteLength);
1359
+ if (text.length > 0) {
1360
+ yield text;
1361
+ }
1362
+ onProgress?.(done, total ?? void 0);
1363
+ }
1364
+ if (input.byteLength === 0) {
1365
+ onProgress?.(0, 0);
1366
+ }
1367
+ return;
1368
+ }
1369
+ const chunks = isReadableStream(input) ? streamChunks(input, signal) : input;
1370
+ for await (const chunk of chunks) {
1371
+ throwIfAborted(signal);
1372
+ let text;
1373
+ if (typeof chunk === "string") {
1374
+ const pending = decode(new Uint8Array(0), false);
1375
+ text = emit(pending + chunk);
1376
+ done += chunk.length;
1377
+ } else if (chunk instanceof Uint8Array) {
1378
+ text = emit(decode(chunk, true));
1379
+ done += chunk.byteLength;
1380
+ } else {
1381
+ throw new GraphFormatError("E_UNSUPPORTED", "an input chunk must be a string or a Uint8Array", {
1382
+ reason: "chunk type",
1383
+ found: typeof chunk
1384
+ });
1385
+ }
1386
+ if (text.length > 0) {
1387
+ yield text;
1388
+ }
1389
+ onProgress?.(done);
1390
+ }
1391
+ const tail = emit(decode(new Uint8Array(0), false));
1392
+ if (tail.length > 0) {
1393
+ yield tail;
1394
+ }
1395
+ onProgress?.(done, done);
1396
+ }
1397
+ function isReadableStream(input) {
1398
+ return typeof input.getReader === "function";
1399
+ }
1400
+ async function* streamChunks(stream, signal) {
1401
+ const reader = stream.getReader();
1402
+ let finished = false;
1403
+ try {
1404
+ for (; ; ) {
1405
+ throwIfAborted(signal);
1406
+ const { done, value } = await reader.read();
1407
+ if (done) {
1408
+ finished = true;
1409
+ return;
1410
+ }
1411
+ yield value;
1412
+ }
1413
+ } finally {
1414
+ if (!finished) {
1415
+ await reader.cancel(signal?.reason).catch(() => void 0);
1416
+ }
1417
+ reader.releaseLock();
1418
+ }
1419
+ }
1420
+ async function readText(input, report, options = {}) {
1421
+ if (typeof input === "string") {
1422
+ throwIfAborted(options.signal);
1423
+ options.onProgress?.(input.length, input.length);
1424
+ return input.startsWith(BOM) ? input.slice(1) : input;
1425
+ }
1426
+ const parts = [];
1427
+ for await (const chunk of textChunks(input, report, options)) {
1428
+ parts.push(chunk);
1429
+ }
1430
+ return parts.length === 1 ? parts[0] : parts.join("");
1431
+ }
1432
+ class LineReader {
1433
+ /**
1434
+ * Create a reader over an input; nothing is read until iteration starts.
1435
+ * @param input - the input
1436
+ * @param report - the report decode errors are recorded in
1437
+ * @param options - cancellation and progress
1438
+ */
1439
+ constructor(input, report, options = {}) {
1440
+ this.lineNumber = 0;
1441
+ this.input = input;
1442
+ this.report = report;
1443
+ this.options = options;
1444
+ }
1445
+ /**
1446
+ * The 1-based number of the line most recently yielded.
1447
+ * @returns the line number; 0 before the first line
1448
+ */
1449
+ get line() {
1450
+ return this.lineNumber;
1451
+ }
1452
+ /**
1453
+ * Iterate the lines.
1454
+ * @yields one line at a time, terminator removed
1455
+ * @returns nothing
1456
+ */
1457
+ async *[Symbol.asyncIterator]() {
1458
+ const pending = [];
1459
+ let trailingCr = false;
1460
+ for await (const chunk of textChunks(this.input, this.report, this.options)) {
1461
+ let start = 0;
1462
+ const end = chunk.length;
1463
+ if (trailingCr) {
1464
+ trailingCr = false;
1465
+ this.lineNumber++;
1466
+ const joined = pending.join("");
1467
+ pending.length = 0;
1468
+ yield joined.slice(0, -1);
1469
+ if (chunk.charCodeAt(0) === 10) {
1470
+ start = 1;
1471
+ }
1472
+ }
1473
+ let nl = chunk.indexOf("\n", start);
1474
+ let cr = chunk.indexOf("\r", start);
1475
+ while (nl >= 0 || cr >= 0) {
1476
+ let cut;
1477
+ let next;
1478
+ if (cr >= 0 && (nl < 0 || cr < nl)) {
1479
+ if (cr === end - 1) {
1480
+ break;
1481
+ }
1482
+ cut = cr;
1483
+ next = chunk.charCodeAt(cr + 1) === 10 ? cr + 2 : cr + 1;
1484
+ } else {
1485
+ cut = nl;
1486
+ next = nl + 1;
1487
+ }
1488
+ this.lineNumber++;
1489
+ const piece = chunk.slice(start, cut);
1490
+ if (pending.length === 0) {
1491
+ yield piece;
1492
+ } else {
1493
+ pending.push(piece);
1494
+ const joined = pending.join("");
1495
+ pending.length = 0;
1496
+ yield joined;
1497
+ }
1498
+ start = next;
1499
+ if (nl >= 0 && nl < next) {
1500
+ nl = chunk.indexOf("\n", next);
1501
+ }
1502
+ if (cr >= 0 && cr < next) {
1503
+ cr = chunk.indexOf("\r", next);
1504
+ }
1505
+ }
1506
+ if (start < end) {
1507
+ pending.push(start === 0 ? chunk : chunk.slice(start));
1508
+ trailingCr = chunk.charCodeAt(end - 1) === 13;
1509
+ }
1510
+ }
1511
+ if (pending.length > 0) {
1512
+ this.lineNumber++;
1513
+ const joined = pending.join("");
1514
+ yield joined.endsWith("\r") ? joined.slice(0, -1) : joined;
1515
+ }
1516
+ }
1517
+ }
1518
+ const ID_COERCIONS = /* @__PURE__ */ new Set(["keep", "canonical", "string", "number"]);
1519
+ const NODE_ID_SOURCES = /* @__PURE__ */ new Set(["id", "label", "index"]);
1520
+ const DUPLICATE_POLICIES = /* @__PURE__ */ new Set(["keep", "error", "first", "last", "sum", "min", "max"]);
1521
+ const SELF_LOOP_POLICIES = /* @__PURE__ */ new Set(["keep", "drop", "error"]);
1522
+ const IMPORT_MIXED_POLICIES = /* @__PURE__ */ new Set(["expand", "directed", "undirected", "error"]);
1523
+ const EXPORT_MIXED_POLICIES = /* @__PURE__ */ new Set(["error", "directed", "undirected"]);
1524
+ const WEIGHT_DTYPES = /* @__PURE__ */ new Set(["f32", "f64"]);
1525
+ const LONG_MODES = /* @__PURE__ */ new Set(["f64", "string"]);
1526
+ const HYPEREDGE_POLICIES = /* @__PURE__ */ new Set(["error", "skip", "star", "clique"]);
1527
+ const SANITIZE_MODES = /* @__PURE__ */ new Set(["error", "mangle"]);
1528
+ const DEFAULT_ERROR_LIMIT = 100;
1529
+ const SINK_OPTION_NAMES = ["addMissingNodes", "duplicateEdges", "selfLoops", "weightDtype"];
1530
+ function reportSinkOptions(sink, options, report, enforcesMissingNodes = false) {
1531
+ if (options === void 0) {
1532
+ return 0;
1533
+ }
1534
+ let recorded = 0;
1535
+ for (const name of SINK_OPTION_NAMES) {
1536
+ const wanted = options[name];
1537
+ const actual = sink.options[name];
1538
+ if (wanted === void 0 || wanted === actual) {
1539
+ continue;
1540
+ }
1541
+ if (name === "addMissingNodes" && enforcesMissingNodes && wanted === false) {
1542
+ continue;
1543
+ }
1544
+ report.warning(
1545
+ "coercion",
1546
+ SINK_OPTION_CODE,
1547
+ `option ${name}: ${JSON.stringify(wanted)} requested but the sink uses ${JSON.stringify(actual)}; the sink's setting applies`,
1548
+ { element: name }
1549
+ );
1550
+ recorded++;
1551
+ }
1552
+ return recorded;
1553
+ }
1554
+ const IGNORABLE_OPTION_NAMES = [
1555
+ "ids",
1556
+ "nodeIdFrom",
1557
+ "addMissingNodes",
1558
+ "duplicateEdges",
1559
+ "selfLoops",
1560
+ "onMixedDirection",
1561
+ "defaultDirected",
1562
+ "weightFrom",
1563
+ "weightDtype",
1564
+ "long",
1565
+ "restoreMangledIds",
1566
+ "hyperedges"
1567
+ ];
1568
+ function reportUnusedOptions(options, report, used) {
1569
+ if (options === void 0) {
1570
+ return 0;
1571
+ }
1572
+ let recorded = 0;
1573
+ for (const name of IGNORABLE_OPTION_NAMES) {
1574
+ if (used.has(name) || SINK_OPTION_NAMES.includes(name)) {
1575
+ continue;
1576
+ }
1577
+ const value = options[name];
1578
+ if (value === void 0) {
1579
+ continue;
1580
+ }
1581
+ report.warning(
1582
+ "unsupported",
1583
+ OPTION_IGNORED_CODE,
1584
+ `option ${name}: ${JSON.stringify(value)} has no effect on the ${report.format} importer`,
1585
+ { element: name }
1586
+ );
1587
+ recorded++;
1588
+ }
1589
+ return recorded;
1590
+ }
1591
+ function resolveImportOptions(options, defaults) {
1592
+ const o = options ?? {};
1593
+ return Object.freeze({
1594
+ ids: enumOption("ids", o.ids, defaults.ids, ID_COERCIONS),
1595
+ nodeIdFrom: enumOption("nodeIdFrom", o.nodeIdFrom, "id", NODE_ID_SOURCES),
1596
+ addMissingNodes: booleanOption("addMissingNodes", o.addMissingNodes, defaults.addMissingNodes ?? true),
1597
+ duplicateEdges: enumOption("duplicateEdges", o.duplicateEdges, "keep", DUPLICATE_POLICIES),
1598
+ selfLoops: enumOption("selfLoops", o.selfLoops, "keep", SELF_LOOP_POLICIES),
1599
+ onMixedDirection: enumOption("onMixedDirection", o.onMixedDirection, "expand", IMPORT_MIXED_POLICIES),
1600
+ defaultDirected: booleanOption("defaultDirected", o.defaultDirected, defaults.defaultDirected),
1601
+ weightFrom: weightFromOption(o.weightFrom, defaults.weightFrom),
1602
+ weightDtype: enumOption("weightDtype", o.weightDtype, "f64", WEIGHT_DTYPES),
1603
+ long: enumOption("long", o.long, "f64", LONG_MODES),
1604
+ restoreMangledIds: booleanOption("restoreMangledIds", o.restoreMangledIds, true),
1605
+ hyperedges: enumOption("hyperedges", o.hyperedges, "skip", HYPEREDGE_POLICIES),
1606
+ errorLimit: errorLimitOption(o.errorLimit),
1607
+ signal: signalOption(o.signal),
1608
+ onProgress: progressOption(o.onProgress)
1609
+ });
1610
+ }
1611
+ function resolveExportOptions(options) {
1612
+ const o = options ?? {};
1613
+ return Object.freeze({
1614
+ sanitizeIds: enumOption("sanitizeIds", o.sanitizeIds, "error", SANITIZE_MODES),
1615
+ onMixedDirection: enumOption("onMixedDirection", o.onMixedDirection, "error", EXPORT_MIXED_POLICIES)
1616
+ });
1617
+ }
1618
+ function enumOption(name, value, fallback, allowed) {
1619
+ if (value === void 0) {
1620
+ return fallback;
1621
+ }
1622
+ if (typeof value !== "string" || !allowed.has(value)) {
1623
+ throw new GraphFormatError(
1624
+ "E_UNSUPPORTED",
1625
+ `option ${name}: ${describe(value)} is not one of ${list(allowed)}`,
1626
+ {
1627
+ option: name,
1628
+ found: value,
1629
+ supported: [...allowed]
1630
+ }
1631
+ );
1632
+ }
1633
+ return value;
1634
+ }
1635
+ function booleanOption(name, value, fallback) {
1636
+ if (value === void 0) {
1637
+ return fallback;
1638
+ }
1639
+ if (typeof value !== "boolean") {
1640
+ throw new GraphFormatError("E_UNSUPPORTED", `option ${name}: ${describe(value)} is not a boolean`, {
1641
+ option: name,
1642
+ found: value
1643
+ });
1644
+ }
1645
+ return value;
1646
+ }
1647
+ function weightFromOption(value, fallback) {
1648
+ if (value === void 0) {
1649
+ return fallback;
1650
+ }
1651
+ if (value === null || typeof value === "string" && value.length > 0) {
1652
+ return value;
1653
+ }
1654
+ throw new GraphFormatError(
1655
+ "E_UNSUPPORTED",
1656
+ `option weightFrom: ${describe(value)} is not an attribute name or null`,
1657
+ { option: "weightFrom", found: value }
1658
+ );
1659
+ }
1660
+ function errorLimitOption(value) {
1661
+ if (value === void 0) {
1662
+ return DEFAULT_ERROR_LIMIT;
1663
+ }
1664
+ if (typeof value === "number" && value >= 0 && (Number.isInteger(value) || value === Infinity)) {
1665
+ return value;
1666
+ }
1667
+ throw new GraphFormatError(
1668
+ "E_UNSUPPORTED",
1669
+ `option errorLimit: ${describe(value)} is not a non-negative integer or Infinity`,
1670
+ { option: "errorLimit", found: value }
1671
+ );
1672
+ }
1673
+ function signalOption(value) {
1674
+ if (value === void 0 || value === null) {
1675
+ return null;
1676
+ }
1677
+ if (typeof value === "object" && typeof value.aborted === "boolean") {
1678
+ return value;
1679
+ }
1680
+ throw new GraphFormatError("E_UNSUPPORTED", `option signal: ${describe(value)} is not an AbortSignal`, {
1681
+ option: "signal",
1682
+ found: typeof value
1683
+ });
1684
+ }
1685
+ function progressOption(value) {
1686
+ if (value === void 0 || value === null) {
1687
+ return null;
1688
+ }
1689
+ if (typeof value === "function") {
1690
+ return value;
1691
+ }
1692
+ throw new GraphFormatError("E_UNSUPPORTED", `option onProgress: ${describe(value)} is not a function`, {
1693
+ option: "onProgress",
1694
+ found: typeof value
1695
+ });
1696
+ }
1697
+ function describe(value) {
1698
+ switch (typeof value) {
1699
+ case "string":
1700
+ return JSON.stringify(value);
1701
+ case "number":
1702
+ case "boolean":
1703
+ return String(value);
1704
+ default:
1705
+ return value === null ? "null" : typeof value;
1706
+ }
1707
+ }
1708
+ function list(allowed) {
1709
+ return [...allowed].map((v) => JSON.stringify(v)).join(", ");
1710
+ }
1711
+ class ImportError extends GraphFormatError {
1712
+ /**
1713
+ * Create an ImportError.
1714
+ * @param message - a plain-ASCII human-readable message
1715
+ * @param report - the partial report
1716
+ * @param details - optional machine-readable context
1717
+ */
1718
+ constructor(message, report, details) {
1719
+ super("E_IMPORT", message, details);
1720
+ this.name = "ImportError";
1721
+ this.report = report;
1722
+ }
1723
+ }
1724
+ const CATEGORY_BY_CODE = {
1725
+ E_UNKNOWN_NODE: "missing-value",
1726
+ E_UNKNOWN_COLUMN: "missing-value",
1727
+ E_DIRECTED: "coercion",
1728
+ E_TOO_LARGE: "unsupported",
1729
+ E_UNSUPPORTED: "unsupported",
1730
+ E_GPU_INELIGIBLE: "unsupported"
1731
+ };
1732
+ const PARSE_ERROR_CODE = "E_PARSE";
1733
+ class ImportReportBuilder {
1734
+ /**
1735
+ * Create a builder for one import call.
1736
+ * @param format - the importer's format name
1737
+ * @param errorLimit - errors tolerated before the import aborts; Infinity for no limit
1738
+ */
1739
+ constructor(format, errorLimit) {
1740
+ this.counts = { nodes: 0, edges: 0, skippedNodes: 0, skippedEdges: 0, expandedMixed: 0 };
1741
+ this.issueList = [];
1742
+ this.lossList = [];
1743
+ this.onceCodes = /* @__PURE__ */ new Set();
1744
+ this.errors = 0;
1745
+ this.warnings = 0;
1746
+ this.truncatedFlag = false;
1747
+ this.format = format;
1748
+ this.errorLimit = errorLimit;
1749
+ this.startedAt = now();
1750
+ }
1751
+ /**
1752
+ * Issues recorded so far, in order.
1753
+ * @returns the live list (not copied; finish() copies)
1754
+ */
1755
+ get issues() {
1756
+ return this.issueList;
1757
+ }
1758
+ /**
1759
+ * Issues with severity "error".
1760
+ * @returns the count
1761
+ */
1762
+ get errorCount() {
1763
+ return this.errors;
1764
+ }
1765
+ /**
1766
+ * Issues with severity "warning".
1767
+ * @returns the count
1768
+ */
1769
+ get warningCount() {
1770
+ return this.warnings;
1771
+ }
1772
+ /**
1773
+ * Whether the error limit was exceeded.
1774
+ * @returns true once an error beyond the limit was recorded
1775
+ */
1776
+ get truncated() {
1777
+ return this.truncatedFlag;
1778
+ }
1779
+ /**
1780
+ * Loss notes recorded so far.
1781
+ * @returns the live list
1782
+ */
1783
+ get lossy() {
1784
+ return this.lossList;
1785
+ }
1786
+ /**
1787
+ * Record an error. When the count goes beyond the error limit the report is marked truncated
1788
+ * and an ImportError carrying it is thrown; the importer does not catch that.
1789
+ * @param category - the issue category
1790
+ * @param code - a stable code such as "E_UNKNOWN_NODE"
1791
+ * @param message - a plain-ASCII message
1792
+ * @param where - the line and element, when known
1793
+ * @returns the recorded issue
1794
+ */
1795
+ error(category, code, message, where) {
1796
+ const issue = makeIssue(category, "error", code, message, where);
1797
+ this.issueList.push(issue);
1798
+ this.errors++;
1799
+ if (this.errors > this.errorLimit) {
1800
+ this.truncatedFlag = true;
1801
+ throw this.abort(`error limit of ${this.errorLimit} exceeded: ${message}`, {
1802
+ code,
1803
+ limit: this.errorLimit
1804
+ });
1805
+ }
1806
+ return issue;
1807
+ }
1808
+ /**
1809
+ * Record a warning; warnings never count toward the limit.
1810
+ * @param category - the issue category
1811
+ * @param code - a stable code such as "W_WIDENED"
1812
+ * @param message - a plain-ASCII message
1813
+ * @param where - the line and element, when known
1814
+ * @returns the recorded issue
1815
+ */
1816
+ warning(category, code, message, where) {
1817
+ const issue = makeIssue(category, "warning", code, message, where);
1818
+ this.issueList.push(issue);
1819
+ this.warnings++;
1820
+ return issue;
1821
+ }
1822
+ /**
1823
+ * Record a warning the first time its key is seen and ignore later repeats, for conditions
1824
+ * that would otherwise produce one warning per row.
1825
+ * @param category - the issue category
1826
+ * @param code - the stable code
1827
+ * @param message - a plain-ASCII message
1828
+ * @param where - the line and element, when known
1829
+ * @param key - what repeats are deduplicated on; the code itself by default (pass `code:column`
1830
+ * to warn once per column)
1831
+ * @returns the recorded issue, or null when the key was already recorded
1832
+ */
1833
+ warnOnce(category, code, message, where, key = code) {
1834
+ if (this.onceCodes.has(key)) {
1835
+ return null;
1836
+ }
1837
+ this.onceCodes.add(key);
1838
+ return this.warning(category, code, message, where);
1839
+ }
1840
+ /**
1841
+ * Record something the importer could not represent.
1842
+ * @param code - a stable code
1843
+ * @param message - a plain-ASCII message
1844
+ * @param column - the affected column, or null
1845
+ * @param count - the affected count, or null
1846
+ * @returns the recorded note
1847
+ */
1848
+ loss(code, message, column = null, count = null) {
1849
+ const note = Object.freeze({ code, message, column, count });
1850
+ this.lossList.push(note);
1851
+ return note;
1852
+ }
1853
+ /**
1854
+ * The per-element catch of design section 8.6: record a thrown GraphFormatError as an error
1855
+ * issue with its code and the category the code implies. Anything else is re-thrown unchanged:
1856
+ * an ImportError (the import already aborted), an abort reason, and any other thrown value (a
1857
+ * TypeError or RangeError is a programming error in the importer or the sink, never a defect of
1858
+ * the input, so it must surface as an exception rather than as an issue blamed on the file).
1859
+ * @param err - the thrown value
1860
+ * @param where - the line and element, when known
1861
+ * @returns the recorded issue
1862
+ */
1863
+ recordError(err, where) {
1864
+ if (err instanceof GraphFormatError && !(err instanceof ImportError)) {
1865
+ const category = CATEGORY_BY_CODE[err.code] ?? "validation-error";
1866
+ return this.error(category, err.code, err.message, where);
1867
+ }
1868
+ throw err;
1869
+ }
1870
+ /**
1871
+ * Build the ImportError that aborts the import, carrying the report so far. The caller throws
1872
+ * it; this method only constructs it so it can be used in expression position.
1873
+ * @param message - a plain-ASCII message
1874
+ * @param details - optional machine-readable context
1875
+ * @returns the error to throw
1876
+ */
1877
+ abort(message, details) {
1878
+ return new ImportError(message, this.finish(), details);
1879
+ }
1880
+ /**
1881
+ * Record a fatal parse error and abort: the issue is recorded (even beyond the error limit)
1882
+ * and an ImportError carrying the report is thrown.
1883
+ * @param code - a stable code such as "E_INVALID_UTF8"
1884
+ * @param message - a plain-ASCII message
1885
+ * @param where - the line and element, when known
1886
+ * @param details - optional machine-readable context for the ImportError
1887
+ */
1888
+ fail(code, message, where, details) {
1889
+ const issue = makeIssue("parse-error", "error", code, message, where);
1890
+ this.issueList.push(issue);
1891
+ this.errors++;
1892
+ if (this.errors > this.errorLimit) {
1893
+ this.truncatedFlag = true;
1894
+ }
1895
+ throw this.abort(message, { code, ...details });
1896
+ }
1897
+ /**
1898
+ * The report as it stands: a frozen snapshot with the parse duration so far. Can be called more
1899
+ * than once; each call reflects everything recorded up to that point.
1900
+ * @returns the report
1901
+ */
1902
+ finish() {
1903
+ const { nodes, edges, skippedNodes, skippedEdges, expandedMixed } = this.counts;
1904
+ return Object.freeze({
1905
+ format: this.format,
1906
+ counts: Object.freeze({ nodes, edges, skippedNodes, skippedEdges, expandedMixed }),
1907
+ issues: Object.freeze([...this.issueList]),
1908
+ errorCount: this.errors,
1909
+ warningCount: this.warnings,
1910
+ truncated: this.truncatedFlag,
1911
+ lossy: Object.freeze([...this.lossList]),
1912
+ durationMs: now() - this.startedAt
1913
+ });
1914
+ }
1915
+ }
1916
+ function makeIssue(category, severity, code, message, where) {
1917
+ return Object.freeze({
1918
+ category,
1919
+ severity,
1920
+ code,
1921
+ message,
1922
+ line: where?.line ?? null,
1923
+ element: where?.element ?? null
1924
+ });
1925
+ }
1926
+ function isAbortError(err) {
1927
+ return typeof err === "object" && err !== null && err.name === "AbortError";
1928
+ }
1929
+ function now() {
1930
+ return typeof performance === "object" ? performance.now() : Date.now();
1931
+ }
1932
+ function formatF32(value) {
1933
+ if (!Number.isFinite(value)) {
1934
+ return String(value);
1935
+ }
1936
+ if (value === 0) {
1937
+ return "0";
1938
+ }
1939
+ for (let digits = 1; digits <= 9; digits++) {
1940
+ const text = value.toPrecision(digits);
1941
+ if (Math.fround(Number(text)) === value) {
1942
+ return String(Number(text));
1943
+ }
1944
+ }
1945
+ return String(value);
1946
+ }
1947
+ function formatF64(value) {
1948
+ return String(value);
1949
+ }
1950
+ function formatDecimal(value, dtype = "f64") {
1951
+ if (!Number.isFinite(value)) {
1952
+ return String(value);
1953
+ }
1954
+ if (Object.is(value, -0)) {
1955
+ return "-0.0";
1956
+ }
1957
+ const text = formatNumber(value, dtype);
1958
+ return text.includes(".") || text.includes("e") ? text : `${text}.0`;
1959
+ }
1960
+ function formatGmlReal(value, dtype = "f64") {
1961
+ if (Number.isNaN(value)) {
1962
+ return "NAN";
1963
+ }
1964
+ if (!Number.isFinite(value)) {
1965
+ return value > 0 ? "+INF" : "-INF";
1966
+ }
1967
+ const text = formatDecimal(value, dtype);
1968
+ if (text.includes(".")) {
1969
+ return text;
1970
+ }
1971
+ const e = text.indexOf("e");
1972
+ return `${text.slice(0, e)}.0${text.slice(e)}`;
1973
+ }
1974
+ function formatInteger(value) {
1975
+ if (Number.isSafeInteger(value) || !Number.isFinite(value)) {
1976
+ return String(value);
1977
+ }
1978
+ return BigInt(value).toString();
1979
+ }
1980
+ function formatNumber(value, dtype) {
1981
+ return dtype === "f32" ? formatF32(value) : formatF64(value);
1982
+ }
1983
+ const DEFAULT_WEIGHT = 1;
1984
+ function isWeightField(name, weightFrom) {
1985
+ return weightFrom !== null && name === weightFrom;
1986
+ }
1987
+ function parseWeightText(text) {
1988
+ const trimmed = text.trim();
1989
+ if (trimmed.length === 0) {
1990
+ return void 0;
1991
+ }
1992
+ let value;
1993
+ try {
1994
+ value = parseDecimalText(trimmed);
1995
+ } catch (err) {
1996
+ throw invalidWeight(text, err);
1997
+ }
1998
+ if (Number.isNaN(value)) {
1999
+ throw invalidWeight(text, null);
2000
+ }
2001
+ return value;
2002
+ }
2003
+ function weightFromValue(value) {
2004
+ if (value === void 0 || value === null) {
2005
+ return void 0;
2006
+ }
2007
+ if (typeof value === "number") {
2008
+ if (Number.isNaN(value)) {
2009
+ throw invalidWeight(value, null);
2010
+ }
2011
+ return value;
2012
+ }
2013
+ if (typeof value === "string") {
2014
+ return parseWeightText(value);
2015
+ }
2016
+ throw invalidWeight(value, null);
2017
+ }
2018
+ function invalidWeight(value, cause) {
2019
+ let shown;
2020
+ switch (typeof value) {
2021
+ case "string":
2022
+ shown = JSON.stringify(value);
2023
+ break;
2024
+ case "number":
2025
+ shown = String(value);
2026
+ break;
2027
+ default:
2028
+ shown = typeof value;
2029
+ }
2030
+ return new GraphFormatError("E_INVALID_WEIGHT", `invalid edge weight ${shown}`, {
2031
+ value: typeof value === "object" ? typeof value : value,
2032
+ cause: cause instanceof Error ? cause.message : null
2033
+ });
2034
+ }
2035
+ const NUMERIC_DTYPES = /* @__PURE__ */ new Set(["f32", "f64", "i32", "u32", "u8"]);
2036
+ function weightFormatter(dtype) {
2037
+ switch (dtype) {
2038
+ case "f32":
2039
+ return formatF32;
2040
+ case "f64":
2041
+ return formatF64;
2042
+ default:
2043
+ return formatInteger;
2044
+ }
2045
+ }
2046
+ function explicitWeights(snapshot) {
2047
+ const shadowColumn = snapshot.edges.byRole("weight");
2048
+ const shadow = shadowColumn !== null && NUMERIC_DTYPES.has(shadowColumn.dtype) ? shadowColumn : null;
2049
+ if (shadow !== null) {
2050
+ const dtype = shadow.dtype === "f32" ? "f32" : "f64";
2051
+ const format = weightFormatter(shadow.dtype);
2052
+ return {
2053
+ weighted: true,
2054
+ dtype,
2055
+ isExplicit: (e) => shadow.isSet(e),
2056
+ value: (e) => shadow.value(e),
2057
+ text: (e, integral = false) => {
2058
+ if (!shadow.isSet(e)) {
2059
+ return null;
2060
+ }
2061
+ const value = shadow.value(e);
2062
+ return integral && Number.isInteger(value) ? formatInteger(value) : format(value);
2063
+ }
2064
+ };
2065
+ }
2066
+ const { weights } = snapshot.edgeList();
2067
+ if (weights === null || !snapshot.flags.weighted) {
2068
+ return {
2069
+ weighted: false,
2070
+ dtype: "f32",
2071
+ isExplicit: () => false,
2072
+ value: () => DEFAULT_WEIGHT,
2073
+ text: () => null
2074
+ };
2075
+ }
2076
+ return {
2077
+ weighted: true,
2078
+ dtype: "f32",
2079
+ isExplicit: () => true,
2080
+ value: (e) => weights[e],
2081
+ text: (e, integral = false) => {
2082
+ const value = weights[e];
2083
+ return integral && Number.isInteger(value) ? formatInteger(value) : formatF32(value);
2084
+ }
2085
+ };
2086
+ }
2087
+ const LOSS = Object.freeze({
2088
+ /** The format has no mixed direction; expanded pairs are written per onMixedDirection. */
2089
+ MIXED_DIRECTION: "W_MIXED_DIRECTION",
2090
+ /** The format has no mixed direction and onMixedDirection is "error": export() will throw E_DIRECTED. */
2091
+ MIXED_DIRECTION_ERROR: MIXED_DIRECTION_CODE,
2092
+ /** Parallel edges in a format without them. */
2093
+ MULTI_EDGES: "W_MULTI_EDGES",
2094
+ /** Self-loops in a format without them. */
2095
+ SELF_LOOPS: "W_SELF_LOOPS",
2096
+ /** The format requires edge ids and the snapshot has none; canonical e0..e{E-1} are generated. */
2097
+ EDGE_IDS_GENERATED: "W_EDGE_IDS_GENERATED",
2098
+ /** The snapshot has an edge id column and the format cannot write one. */
2099
+ EDGE_IDS_DROPPED: "W_EDGE_IDS_DROPPED",
2100
+ /** Ids outside the format's charset are rewritten (sanitizeIds "mangle"). */
2101
+ ID_MANGLED: "W_ID_MANGLED",
2102
+ /** Ids outside the format's charset and sanitizeIds "error": export() will throw E_INVALID_ID. */
2103
+ ID_CHARSET: "E_ID_CHARSET",
2104
+ /** The format numbers nodes 1..N; ids that are not their 1-based index are kept as labels only. */
2105
+ ID_RENUMBERED: "W_ID_RENUMBERED",
2106
+ /** A column dtype the format does not keep as declared. */
2107
+ DTYPE: "W_DTYPE_UNSUPPORTED",
2108
+ /** A multi-component column in a format without strides. */
2109
+ COMPONENTS: "W_COMPONENTS_FLATTENED",
2110
+ /** A list column in a format without lists. */
2111
+ LIST: "W_LIST_UNSUPPORTED",
2112
+ /** A json column in a format without nested values. */
2113
+ JSON: "W_JSON_UNSUPPORTED",
2114
+ /** A declared default in a format without defaults. */
2115
+ DEFAULT: "W_DEFAULT_DROPPED",
2116
+ /** Declared options in a format without enumerations. */
2117
+ OPTIONS: "W_OPTIONS_DROPPED",
2118
+ /** A parent / parents column in a format without containment. */
2119
+ HIERARCHY: "W_HIERARCHY_DROPPED",
2120
+ /** A start / end / timestamp column in a format without temporal support. */
2121
+ TEMPORAL: TEMPORAL_DROPPED_CODE,
2122
+ /** A `<column>.text` companion (design section 5.1) the format cannot carry. */
2123
+ TEMPORAL_TEXT: TEMPORAL_TEXT_DROPPED_CODE,
2124
+ /** A role column the format has no slot for; written as a plain attribute, the role lost. */
2125
+ ROLE: ROLE_DROPPED_CODE,
2126
+ /** A parents (multi-parent) column in a format with single containment only. */
2127
+ PARENTS: PARENTS_DROPPED_CODE,
2128
+ /** A mutual pair written as two directed edges without the mutual mark. */
2129
+ MUTUAL_EXPANDED: MUTUAL_EXPANDED_CODE,
2130
+ /** A mutual pair written as one undirected edge; the mark is lost and the pair reads back undirected. */
2131
+ MUTUAL_AS_UNDIRECTED: MUTUAL_AS_UNDIRECTED_CODE,
2132
+ /** Node ids whose written text reads back as the other type under the importer's id rule. */
2133
+ ID_TEXT_TYPE: ID_TEXT_TYPE_CODE,
2134
+ /** Two node ids share one written text; export() throws E_INVALID_ID. */
2135
+ ID_TEXT_COLLISION: ID_TEXT_COLLISION_CODE,
2136
+ /** A plain column named like the importer's weight key reads back as THE weight. */
2137
+ WEIGHT_KEY_CLASH: WEIGHT_KEY_CLASH_CODE,
2138
+ /** A role-less column whose written name the importer maps to a role. */
2139
+ ROLE_ASSUMED: ROLE_ASSUMED_CODE,
2140
+ /** A role column written into the format's slot reads back under the importer's fixed name. */
2141
+ COLUMN_NAME_CHANGED: COLUMN_RENAMED_LOSS_CODE,
2142
+ /** A declared column whose every row is unset vanishes through a format without declarations. */
2143
+ EMPTY_COLUMN: EMPTY_COLUMN_DROPPED_CODE,
2144
+ /** A string / dict column that reads back as the other storage class (the dictionary heuristic). */
2145
+ STORAGE_CLASS: STORAGE_CLASS_CODE,
2146
+ /** An f64 column whose set values are all integral reads back as i32 through an untyped format. */
2147
+ INTEGRAL_F64: INTEGRAL_F64_CODE,
2148
+ /** Text cells that read back as numbers or booleans under the 5.1 grammar. */
2149
+ TEXT_INFERRED: TEXT_INFERRED_CODE,
2150
+ /** A string cell holding a character XML 1.0 forbids; export() throws E_COLUMN_TYPE. */
2151
+ XML_ILLEGAL_CHAR: XML_ILLEGAL_CHAR_CODE,
2152
+ /** A dict column without declared options gains one on re-import. */
2153
+ OPTIONS_GAINED: OPTIONS_GAINED_CODE,
2154
+ /** A spells column in a format without spells. */
2155
+ SPELLS: "W_SPELLS_DROPPED",
2156
+ /** Dynamic attribute values (extension tables) in a format without them. */
2157
+ DYNAMIC_VALUES: "W_DYNAMIC_VALUES_DROPPED",
2158
+ /** An open-interval column in a format without open intervals (design section 5.1). */
2159
+ OPEN_INTERVAL: "W_OPEN_INTERVAL",
2160
+ /** Graph-level attributes in a format without them. */
2161
+ GRAPH_ATTRIBUTES: "W_GRAPH_ATTRIBUTES_DROPPED",
2162
+ /** A position column in a format without positions. */
2163
+ POSITIONS: "W_POSITIONS_DROPPED",
2164
+ /** Visual columns in a format without them. */
2165
+ VIZ: "W_VIZ_DROPPED",
2166
+ /** An extension table the format cannot carry. */
2167
+ EXTENSION_TABLE: "W_EXTENSION_TABLE_DROPPED"
2168
+ });
2169
+ const NO_CAPABILITIES = Object.freeze({
2170
+ mixedDirection: false,
2171
+ multiEdges: false,
2172
+ selfLoops: false,
2173
+ edgeIds: "none",
2174
+ idCharset: "any",
2175
+ dtypes: Object.freeze([]),
2176
+ components: false,
2177
+ lists: false,
2178
+ json: false,
2179
+ defaults: false,
2180
+ options: false,
2181
+ hierarchy: false,
2182
+ temporal: "none",
2183
+ graphAttributes: false,
2184
+ positions: false,
2185
+ viz: false
2186
+ });
2187
+ function capabilities(supported) {
2188
+ return Object.freeze({ ...NO_CAPABILITIES, ...supported, dtypes: Object.freeze([...supported.dtypes ?? []]) });
2189
+ }
2190
+ const STRUCTURAL_ROLES = /* @__PURE__ */ new Set(["directed", "pair", "mutual", "weight", "originalId"]);
2191
+ const VIZ_ROLES = /* @__PURE__ */ new Set(["color", "size", "shape", "thickness"]);
2192
+ const TEMPORAL_ROLES = /* @__PURE__ */ new Set(["start", "end", "timestamp", "timestamps"]);
2193
+ const HIERARCHY_ROLES = /* @__PURE__ */ new Set(["parent", "parents"]);
2194
+ function checkCapabilities(snapshot, caps, options, extras = {}) {
2195
+ const notes = [];
2196
+ const note = (code, message, column = null, count = null) => {
2197
+ notes.push(Object.freeze({ code, message, column, count }));
2198
+ };
2199
+ const mixed = countMixedEdges(snapshot);
2200
+ if (mixed > 0 && !caps.mixedDirection) {
2201
+ if (options.onMixedDirection === "error") {
2202
+ note(
2203
+ LOSS.MIXED_DIRECTION_ERROR,
2204
+ `${mixed} undirected edge(s) in a directed graph; the format has no mixed direction and onMixedDirection is "error"`,
2205
+ null,
2206
+ mixed
2207
+ );
2208
+ } else {
2209
+ note(
2210
+ LOSS.MIXED_DIRECTION,
2211
+ `${mixed} undirected edge(s) in a directed graph written as ${options.onMixedDirection}`,
2212
+ null,
2213
+ mixed
2214
+ );
2215
+ }
2216
+ }
2217
+ if (snapshot.flags.multigraph && !caps.multiEdges) {
2218
+ const parallel = countParallelArcs(snapshot);
2219
+ note(LOSS.MULTI_EDGES, `${parallel} parallel edge(s); the format has no parallel edges`, null, parallel);
2220
+ }
2221
+ if (snapshot.selfLoopCount > 0 && !caps.selfLoops) {
2222
+ note(
2223
+ LOSS.SELF_LOOPS,
2224
+ `${snapshot.selfLoopCount} self-loop(s); the format has no self-loops`,
2225
+ null,
2226
+ snapshot.selfLoopCount
2227
+ );
2228
+ }
2229
+ const edgeIds = snapshot.edges.byRole("id");
2230
+ if (caps.edgeIds === "required" && edgeIds === null && snapshot.edgeCount > 0) {
2231
+ note(
2232
+ LOSS.EDGE_IDS_GENERATED,
2233
+ "the format requires edge ids; canonical e0..e{E-1} are generated and not written back",
2234
+ null,
2235
+ snapshot.edgeCount
2236
+ );
2237
+ } else if (caps.edgeIds === "none" && edgeIds !== null) {
2238
+ note(
2239
+ LOSS.EDGE_IDS_DROPPED,
2240
+ `edge id column "${edgeIds.meta.name}" cannot be written`,
2241
+ edgeIds.meta.name,
2242
+ snapshot.edgeCount
2243
+ );
2244
+ }
2245
+ const unrepresentable = countUnrepresentableIds(snapshot, caps.idCharset);
2246
+ if (unrepresentable > 0) {
2247
+ if (caps.idCharset === "dense-1-based") {
2248
+ note(
2249
+ LOSS.ID_RENUMBERED,
2250
+ options.sanitizeIds === "mangle" ? `${unrepresentable} node id(s) are not their 1-based index; nodes are numbered 1..N, the originals kept in the exporter's originalId attribute (restored by restoreMangledIds) and as labels of the nodes without a label value` : `${unrepresentable} node id(s) are not their 1-based index; nodes are numbered 1..N and ids kept as labels of the nodes without a label value (a node with one loses its id)`,
2251
+ null,
2252
+ unrepresentable
2253
+ );
2254
+ } else if (options.sanitizeIds === "mangle") {
2255
+ note(
2256
+ LOSS.ID_MANGLED,
2257
+ `${unrepresentable} node id(s) outside the ${caps.idCharset} charset are rewritten; originals kept in the originalId attribute`,
2258
+ null,
2259
+ unrepresentable
2260
+ );
2261
+ } else {
2262
+ note(
2263
+ LOSS.ID_CHARSET,
2264
+ `${unrepresentable} node id(s) outside the ${caps.idCharset} charset; export() will throw unless sanitizeIds is "mangle"`,
2265
+ null,
2266
+ unrepresentable
2267
+ );
2268
+ }
2269
+ }
2270
+ checkColumns(snapshot.nodes, "node", caps, extras, note);
2271
+ checkColumns(snapshot.edges, "edge", caps, extras, note);
2272
+ const graphColumns = snapshot.graph.names();
2273
+ if (graphColumns.length > 0) {
2274
+ if (caps.graphAttributes) {
2275
+ checkColumns(snapshot.graph, "graph", caps, extras, note);
2276
+ } else {
2277
+ note(
2278
+ LOSS.GRAPH_ATTRIBUTES,
2279
+ `${graphColumns.length} graph attribute(s) cannot be written`,
2280
+ null,
2281
+ graphColumns.length
2282
+ );
2283
+ }
2284
+ }
2285
+ for (const [name, table] of snapshot.extensions) {
2286
+ if (name.startsWith("temporal:")) {
2287
+ if (caps.temporal !== "dynamic-values") {
2288
+ note(
2289
+ LOSS.DYNAMIC_VALUES,
2290
+ `dynamic values of "${name}" (${table.rowCount} row(s)) cannot be written`,
2291
+ name,
2292
+ table.rowCount
2293
+ );
2294
+ }
2295
+ } else {
2296
+ note(
2297
+ LOSS.EXTENSION_TABLE,
2298
+ `extension table "${name}" (${table.rowCount} row(s)) cannot be written`,
2299
+ name,
2300
+ table.rowCount
2301
+ );
2302
+ }
2303
+ }
2304
+ return notes;
2305
+ }
2306
+ function checkColumns(table, domain, caps, extras, note) {
2307
+ for (const column of table) {
2308
+ const { meta } = column;
2309
+ const { name, role } = meta;
2310
+ if (role !== null && STRUCTURAL_ROLES.has(role)) {
2311
+ continue;
2312
+ }
2313
+ const label = `${domain} column "${name}"`;
2314
+ if (role !== null && extras.roleNames !== void 0) {
2315
+ const fixed = extras.roleNames[role];
2316
+ if (fixed !== void 0 && fixed !== name) {
2317
+ note(
2318
+ LOSS.COLUMN_NAME_CHANGED,
2319
+ `${label} (${role}) is written into the format's ${role} slot and reads back as "${fixed}"`,
2320
+ name,
2321
+ column.length - column.nullCount
2322
+ );
2323
+ }
2324
+ }
2325
+ if (domain === "edge" && role === "id") {
2326
+ continue;
2327
+ }
2328
+ if (role === "timeText") {
2329
+ if (extras.temporalText !== true) {
2330
+ note(
2331
+ LOSS.TEMPORAL_TEXT,
2332
+ `${label} (the lexical form of a temporal column, design section 5.1) cannot be written`,
2333
+ name,
2334
+ column.length - column.nullCount
2335
+ );
2336
+ }
2337
+ continue;
2338
+ }
2339
+ if (role === "position") {
2340
+ if (!caps.positions) {
2341
+ note(LOSS.POSITIONS, `${label} (positions) cannot be written`, name, column.length - column.nullCount);
2342
+ } else if (extras.positionDtype !== void 0 && meta.dtype !== extras.positionDtype) {
2343
+ note(
2344
+ LOSS.DTYPE,
2345
+ `${label} (positions) is ${meta.dtype}; the format reads positions back as ${extras.positionDtype}`,
2346
+ name,
2347
+ column.length - column.nullCount
2348
+ );
2349
+ }
2350
+ continue;
2351
+ }
2352
+ if (role !== null && VIZ_ROLES.has(role)) {
2353
+ if (!caps.viz) {
2354
+ note(LOSS.VIZ, `${label} (${role}) cannot be written`, name, column.length - column.nullCount);
2355
+ }
2356
+ continue;
2357
+ }
2358
+ if (role !== null && HIERARCHY_ROLES.has(role)) {
2359
+ if (!caps.hierarchy) {
2360
+ note(LOSS.HIERARCHY, `${label} (${role}) cannot be written`, name, column.length - column.nullCount);
2361
+ }
2362
+ continue;
2363
+ }
2364
+ if (role !== null && TEMPORAL_ROLES.has(role)) {
2365
+ if (caps.temporal === "none") {
2366
+ note(LOSS.TEMPORAL, `${label} (${role}) cannot be written`, name, column.length - column.nullCount);
2367
+ }
2368
+ continue;
2369
+ }
2370
+ if (role === "spells") {
2371
+ if (caps.temporal !== "spells" && caps.temporal !== "dynamic-values") {
2372
+ note(LOSS.SPELLS, `${label} (spells) cannot be written`, name, column.length - column.nullCount);
2373
+ }
2374
+ continue;
2375
+ }
2376
+ if (role === "open") {
2377
+ if (extras.openIntervals !== true) {
2378
+ note(
2379
+ LOSS.OPEN_INTERVAL,
2380
+ `${label} (open intervals) cannot be written`,
2381
+ name,
2382
+ column.length - column.nullCount
2383
+ );
2384
+ }
2385
+ continue;
2386
+ }
2387
+ if (role !== null && !(extras.roles?.has(role) ?? false)) {
2388
+ note(
2389
+ LOSS.ROLE,
2390
+ `${label} (${role}) is written as a plain attribute; the format has no ${role} slot and the role is lost`,
2391
+ name,
2392
+ column.length - column.nullCount
2393
+ );
2394
+ }
2395
+ if (meta.dynamic && caps.temporal !== "dynamic-values") {
2396
+ note(LOSS.DYNAMIC_VALUES, `${label} is dynamic; only its static value can be written`, name, null);
2397
+ }
2398
+ checkDtype(column, label, caps, note);
2399
+ if (meta.default !== void 0 && !caps.defaults) {
2400
+ note(LOSS.DEFAULT, `${label} declares a default; the format has no defaults`, name, null);
2401
+ }
2402
+ if (meta.options !== null && !caps.options) {
2403
+ note(LOSS.OPTIONS, `${label} declares options; the format has no enumerations`, name, null);
2404
+ }
2405
+ }
2406
+ }
2407
+ function checkDtype(column, label, caps, note) {
2408
+ const { meta } = column;
2409
+ const { name } = meta;
2410
+ const set = column.length - column.nullCount;
2411
+ switch (meta.dtype) {
2412
+ case "list":
2413
+ if (!caps.lists) {
2414
+ note(LOSS.LIST, `${label} is a list; the format has no lists`, name, set);
2415
+ } else if (meta.itemDtype !== null && !caps.dtypes.includes(meta.itemDtype)) {
2416
+ note(
2417
+ LOSS.DTYPE,
2418
+ `${label} holds ${meta.itemDtype} items; the format cannot keep that dtype`,
2419
+ name,
2420
+ set
2421
+ );
2422
+ } else if (meta.itemDtype === "dict") {
2423
+ note(LOSS.DTYPE, `${label} holds dict items; list items read back as string`, name, set);
2424
+ }
2425
+ break;
2426
+ case "json":
2427
+ if (!caps.json) {
2428
+ note(LOSS.JSON, `${label} holds nested values; the format has no nested values`, name, set);
2429
+ }
2430
+ break;
2431
+ default:
2432
+ if (!caps.dtypes.includes(meta.dtype)) {
2433
+ note(LOSS.DTYPE, `${label} is ${meta.dtype}; the format cannot keep that dtype`, name, set);
2434
+ }
2435
+ if (meta.components > 1 && !caps.components) {
2436
+ note(
2437
+ LOSS.COMPONENTS,
2438
+ `${label} has ${meta.components} components; the format has no strides`,
2439
+ name,
2440
+ set
2441
+ );
2442
+ }
2443
+ break;
2444
+ }
2445
+ }
2446
+ function countMixedEdges(snapshot) {
2447
+ if (!snapshot.directed) {
2448
+ return 0;
2449
+ }
2450
+ const directed = snapshot.edges.byRole("directed");
2451
+ if (directed === null || directed.dtype !== "bool") {
2452
+ return 0;
2453
+ }
2454
+ const pair = snapshot.edges.byRole("pair");
2455
+ let undirectedRows = 0;
2456
+ let paired = 0;
2457
+ for (let e = 0; e < snapshot.edgeCount; e++) {
2458
+ if (directed.isSet(e) && directed.value(e) === false) {
2459
+ undirectedRows++;
2460
+ if (pair !== null && pair.dtype === "u32" && pair.isSet(e) && pair.data[e] !== INVALID_INDEX) {
2461
+ paired++;
2462
+ }
2463
+ }
2464
+ }
2465
+ return paired / 2 + (undirectedRows - paired);
2466
+ }
2467
+ function countParallelArcs(snapshot) {
2468
+ const { rowPtr, colIdx, nodeCount } = snapshot;
2469
+ let count = 0;
2470
+ for (let u = 0; u < nodeCount; u++) {
2471
+ for (let a = rowPtr[u] + 1; a < rowPtr[u + 1]; a++) {
2472
+ if (colIdx[a] === colIdx[a - 1]) {
2473
+ count++;
2474
+ }
2475
+ }
2476
+ }
2477
+ return snapshot.directed ? count : Math.ceil(count / 2);
2478
+ }
2479
+ const NAME_CHAR_RANGES = [
2480
+ [45, 46],
2481
+ [48, 58],
2482
+ [65, 90],
2483
+ [95, 95],
2484
+ [97, 122],
2485
+ [183, 183],
2486
+ [192, 214],
2487
+ [216, 246],
2488
+ [248, 893],
2489
+ [895, 8191],
2490
+ [8204, 8205],
2491
+ [8255, 8256],
2492
+ [8304, 8591],
2493
+ [11264, 12271],
2494
+ [12289, 55295],
2495
+ [63744, 64975],
2496
+ [65008, 65533],
2497
+ [65536, 983039]
2498
+ ];
2499
+ function isNameChar(cp) {
2500
+ for (const [lo, hi] of NAME_CHAR_RANGES) {
2501
+ if (cp < lo) {
2502
+ return false;
2503
+ }
2504
+ if (cp <= hi) {
2505
+ return true;
2506
+ }
2507
+ }
2508
+ return false;
2509
+ }
2510
+ function isNmtoken(text) {
2511
+ if (text.length === 0) {
2512
+ return false;
2513
+ }
2514
+ for (const ch of text) {
2515
+ const cp = ch.codePointAt(0);
2516
+ if (cp === void 0 || !isNameChar(cp)) {
2517
+ return false;
2518
+ }
2519
+ }
2520
+ return true;
2521
+ }
2522
+ function isRepresentableId(id, charset) {
2523
+ switch (charset) {
2524
+ case "any":
2525
+ return true;
2526
+ case "nmtoken":
2527
+ return isNmtoken(String(id));
2528
+ case "integer":
2529
+ return typeof id === "number" && Number.isSafeInteger(id);
2530
+ case "dense-1-based":
2531
+ return false;
2532
+ default: {
2533
+ const name = charset;
2534
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown id charset ${name}`, { charset: name });
2535
+ }
2536
+ }
2537
+ }
2538
+ function countUnrepresentableIds(snapshot, charset) {
2539
+ const { ids } = snapshot;
2540
+ if (charset === "any") {
2541
+ return 0;
2542
+ }
2543
+ if (charset === "dense-1-based") {
2544
+ if (ids.kind === "identity" && ids.offset === 1) {
2545
+ return 0;
2546
+ }
2547
+ let changed = 0;
2548
+ for (let i = 0; i < ids.size; i++) {
2549
+ if (ids.idOf(i) !== i + 1) {
2550
+ changed++;
2551
+ }
2552
+ }
2553
+ return changed;
2554
+ }
2555
+ let count = 0;
2556
+ for (let i = 0; i < ids.size; i++) {
2557
+ if (!isRepresentableId(ids.idOf(i), charset)) {
2558
+ count++;
2559
+ }
2560
+ }
2561
+ return count;
2562
+ }
2563
+ function sanitizeIds(snapshot, charset, mode) {
2564
+ const { ids } = snapshot;
2565
+ const n = ids.size;
2566
+ if (charset === "dense-1-based") {
2567
+ const changed = countUnrepresentableIds(snapshot, charset);
2568
+ return {
2569
+ charset,
2570
+ changed,
2571
+ idAt: (i) => i + 1,
2572
+ isChanged: (i) => ids.idOf(i) !== i + 1,
2573
+ originalAt: (i) => ids.idOf(i)
2574
+ };
2575
+ }
2576
+ const unchanged = {
2577
+ charset,
2578
+ changed: 0,
2579
+ idAt: (i) => ids.idOf(i),
2580
+ isChanged: () => false,
2581
+ originalAt: (i) => ids.idOf(i)
2582
+ };
2583
+ if (charset === "any") {
2584
+ return unchanged;
2585
+ }
2586
+ const bad = [];
2587
+ for (let i = 0; i < n; i++) {
2588
+ if (!isRepresentableId(ids.idOf(i), charset)) {
2589
+ bad.push(i);
2590
+ }
2591
+ }
2592
+ if (bad.length === 0) {
2593
+ return unchanged;
2594
+ }
2595
+ if (mode === "error") {
2596
+ const first = ids.idOf(bad[0]);
2597
+ throw new GraphFormatError(
2598
+ "E_INVALID_ID",
2599
+ `${bad.length} node id(s) cannot be written as ${charset} (first: ${JSON.stringify(first)} at index ${bad[0]}); pass sanitizeIds: "mangle" to rewrite them`,
2600
+ { reason: "charset", charset, count: bad.length, id: first, index: bad[0] }
2601
+ );
2602
+ }
2603
+ const written = /* @__PURE__ */ new Map();
2604
+ if (charset === "nmtoken") {
2605
+ const used = /* @__PURE__ */ new Set();
2606
+ for (let i = 0; i < n; i++) {
2607
+ const id = ids.idOf(i);
2608
+ if (isRepresentableId(id, charset)) {
2609
+ used.add(String(id));
2610
+ }
2611
+ }
2612
+ for (const i of bad) {
2613
+ const base = mangleNmtoken(String(ids.idOf(i)));
2614
+ let candidate = base;
2615
+ for (let k = 2; used.has(candidate); k++) {
2616
+ candidate = `${base}_${k}`;
2617
+ }
2618
+ used.add(candidate);
2619
+ written.set(i, candidate);
2620
+ }
2621
+ } else {
2622
+ const used = /* @__PURE__ */ new Set();
2623
+ for (let i = 0; i < n; i++) {
2624
+ const id = ids.idOf(i);
2625
+ if (typeof id === "number" && Number.isSafeInteger(id)) {
2626
+ used.add(id);
2627
+ }
2628
+ }
2629
+ let next = 0;
2630
+ for (const i of bad) {
2631
+ while (used.has(next)) {
2632
+ next++;
2633
+ }
2634
+ used.add(next);
2635
+ written.set(i, next);
2636
+ next++;
2637
+ }
2638
+ }
2639
+ return {
2640
+ charset,
2641
+ changed: bad.length,
2642
+ idAt: (i) => written.get(i) ?? ids.idOf(i),
2643
+ isChanged: (i) => written.has(i),
2644
+ originalAt: (i) => ids.idOf(i)
2645
+ };
2646
+ }
2647
+ function mangleNmtoken(text) {
2648
+ let out = "";
2649
+ for (const ch of text) {
2650
+ const cp = ch.codePointAt(0);
2651
+ out += cp !== void 0 && isNameChar(cp) ? ch : "_";
2652
+ }
2653
+ return out.length === 0 ? "_" : out;
2654
+ }
2655
+ const DEFAULT_CHUNK_BYTES = 64 * 1024;
2656
+ async function* encodeChunks(parts, chunkBytes = DEFAULT_CHUNK_BYTES) {
2657
+ const encoder = new TextEncoder();
2658
+ let pending = [];
2659
+ let pendingUnits = 0;
2660
+ for await (const part of parts) {
2661
+ if (part.length === 0) {
2662
+ continue;
2663
+ }
2664
+ pending.push(part);
2665
+ pendingUnits += part.length;
2666
+ if (pendingUnits >= chunkBytes) {
2667
+ yield encoder.encode(pending.length === 1 ? pending[0] : pending.join(""));
2668
+ pending = [];
2669
+ pendingUnits = 0;
2670
+ }
2671
+ }
2672
+ if (pending.length > 0) {
2673
+ yield encoder.encode(pending.length === 1 ? pending[0] : pending.join(""));
2674
+ }
2675
+ }
2676
+ async function joinText(parts) {
2677
+ const collected = [];
2678
+ for await (const part of parts) {
2679
+ collected.push(part);
2680
+ }
2681
+ return collected.join("");
2682
+ }
2683
+ async function decodeChunks(chunks) {
2684
+ const decoder = new TextDecoder("utf-8", { fatal: true });
2685
+ const parts = [];
2686
+ for await (const chunk of chunks) {
2687
+ parts.push(decoder.decode(chunk, { stream: true }));
2688
+ }
2689
+ parts.push(decoder.decode());
2690
+ return parts.join("");
2691
+ }
2692
+ async function collectBytes(chunks) {
2693
+ const parts = [];
2694
+ let total = 0;
2695
+ for await (const chunk of chunks) {
2696
+ parts.push(chunk);
2697
+ total += chunk.byteLength;
2698
+ }
2699
+ const out = new Uint8Array(total);
2700
+ let offset = 0;
2701
+ for (const part of parts) {
2702
+ out.set(part, offset);
2703
+ offset += part.byteLength;
2704
+ }
2705
+ return out;
2706
+ }
2707
+ function toReadableStream(chunks) {
2708
+ const iterator = chunks[Symbol.asyncIterator]();
2709
+ return new ReadableStream({
2710
+ async pull(controller) {
2711
+ const { done, value } = await iterator.next();
2712
+ if (done) {
2713
+ controller.close();
2714
+ return;
2715
+ }
2716
+ controller.enqueue(value);
2717
+ },
2718
+ async cancel() {
2719
+ await iterator.return?.(void 0);
2720
+ }
2721
+ });
2722
+ }
2723
+ export {
2724
+ TEMPORAL_TEXT_DROPPED_CODE as $,
2725
+ MUTUAL_COLUMN as A,
2726
+ BAD_DEFAULT_CODE as B,
2727
+ COLUMN_RENAMED_CODE as C,
2728
+ DEFAULT_CHUNK_BYTES as D,
2729
+ EMPTY_COLUMN_DROPPED_CODE as E,
2730
+ MUTUAL_EXPANDED_CODE as F,
2731
+ NO_GRAPH_CODE as G,
2732
+ HYPEREDGE_CODE as H,
2733
+ ImportReportBuilder as I,
2734
+ OPTION_IGNORED_CODE as J,
2735
+ PARENTS_DROPPED_CODE as K,
2736
+ LOSS as L,
2737
+ MISSING_ENDPOINT_CODE as M,
2738
+ NO_CAPABILITIES as N,
2739
+ OPTIONS_GAINED_CODE as O,
2740
+ PAIR_COLUMN as P,
2741
+ PARSE_ERROR_CODE as Q,
2742
+ PRECISION_CODE as R,
2743
+ RENAMED_CODE as S,
2744
+ ROLE_ASSUMED_CODE as T,
2745
+ ROLE_DROPPED_CODE as U,
2746
+ ROLE_TAKEN_CODE as V,
2747
+ SINK_OPTION_CODE as W,
2748
+ STORAGE_CLASS_CODE as X,
2749
+ STRAY_TEXT_CODE as Y,
2750
+ SYNTAX_CODE as Z,
2751
+ TEMPORAL_DROPPED_CODE as _,
2752
+ throwIfAborted as a,
2753
+ TEXT_INFERRED_CODE as a0,
2754
+ UNKNOWN_ATTR_TYPE_CODE as a1,
2755
+ UNKNOWN_ELEMENT_CODE as a2,
2756
+ UNKNOWN_PARENT_CODE as a3,
2757
+ UNKNOWN_TYPE_CODE as a4,
2758
+ WEIGHT_KEY_CLASH_CODE as a5,
2759
+ XML_ILLEGAL_CHAR_CODE as a6,
2760
+ XML_SYNTAX_CODE as a7,
2761
+ canonicalId as a8,
2762
+ capabilities as a9,
2763
+ reportUnusedOptions as aA,
2764
+ resolveExportOptions as aB,
2765
+ resolveImportOptions as aC,
2766
+ sanitizeIds as aD,
2767
+ toReadableStream as aE,
2768
+ isNameChar as aF,
2769
+ countMixedEdges as aG,
2770
+ weightFromValue as aH,
2771
+ uniqueColumnName as aI,
2772
+ TIME_TEXT_ROLE as aJ,
2773
+ TIME_TEXT_SUFFIX as aK,
2774
+ declareAttribute as aL,
2775
+ parseDeclaredTemporal as aM,
2776
+ parseDeclaredValue as aN,
2777
+ losesPrecision as aO,
2778
+ parseTimeText as aP,
2779
+ splitListText as aQ,
2780
+ timeTextCompanion as aR,
2781
+ parseDecimalText as aS,
2782
+ formatTimeValue as aT,
2783
+ mapDeclaredType as aU,
2784
+ joinListText as aV,
2785
+ formatTemporal as aW,
2786
+ DictHeuristic as aX,
2787
+ DICT_SAMPLE_ROWS as aY,
2788
+ takenIn as aZ,
2789
+ declareOn as a_,
2790
+ checkCapabilities as aa,
2791
+ coerceId as ab,
2792
+ coerceIdText as ac,
2793
+ collectBytes as ad,
2794
+ declareCompanion as ae,
2795
+ declareResolved as af,
2796
+ decodeChunks as ag,
2797
+ encodeChunks as ah,
2798
+ explicitWeights as ai,
2799
+ formatDecimal as aj,
2800
+ formatF32 as ak,
2801
+ formatF64 as al,
2802
+ formatGmlReal as am,
2803
+ formatInteger as an,
2804
+ inputLength as ao,
2805
+ isAbortError as ap,
2806
+ isCanonicalIntegerText as aq,
2807
+ isImportInput as ar,
2808
+ isNmtoken as as,
2809
+ isWeightField as at,
2810
+ joinText as au,
2811
+ mangleNmtoken as av,
2812
+ pairFolding as aw,
2813
+ parseWeightText as ax,
2814
+ readText as ay,
2815
+ reportSinkOptions as az,
2816
+ BAD_OPTIONS_CODE as b,
2817
+ COLUMN_RENAMED_LOSS_CODE as c,
2818
+ COUNT_HINT_CODE as d,
2819
+ DEFAULT_ERROR_LIMIT as e,
2820
+ DIRECTED_COLUMN as f,
2821
+ DIRECTION_FORCED_CODE as g,
2822
+ DIRECTION_REFUSED_CODE as h,
2823
+ DUPLICATE_EDGE_ID_CODE as i,
2824
+ DUPLICATE_KEY_CODE as j,
2825
+ DUPLICATE_NODE_CODE as k,
2826
+ DirectionResolver as l,
2827
+ EMPTY_INPUT_CODE as m,
2828
+ ID_MERGED_CODE as n,
2829
+ ID_TEXT_COLLISION_CODE as o,
2830
+ ID_TEXT_TYPE_CODE as p,
2831
+ INTEGRAL_F64_CODE as q,
2832
+ INVALID_UTF8_CODE as r,
2833
+ IdCoercer as s,
2834
+ textChunks as t,
2835
+ ImportError as u,
2836
+ LineReader as v,
2837
+ MISSING_ID_CODE as w,
2838
+ MIXED_DIRECTION_CODE as x,
2839
+ MULTIPLE_GRAPHS_CODE as y,
2840
+ MUTUAL_AS_UNDIRECTED_CODE as z
2841
+ };
2842
+ //# sourceMappingURL=writer-DxSKC7TL.js.map