@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
package/dist/neo4j.js ADDED
@@ -0,0 +1,2046 @@
1
+ import { V as ROLE_TAKEN_CODE$1, n as ID_MERGED_CODE$1, k as DUPLICATE_NODE_CODE$1, M as MISSING_ENDPOINT_CODE$1, w as MISSING_ID_CODE$1, aC as resolveImportOptions, I as ImportReportBuilder, az as reportSinkOptions, aA as reportUnusedOptions, a as throwIfAborted, s as IdCoercer, l as DirectionResolver, u as ImportError, aL as declareAttribute, a_ as declareOn, aI as uniqueColumnName, S as RENAMED_CODE, ax as parseWeightText, aM as parseDeclaredTemporal, aN as parseDeclaredValue, aO as losesPrecision, ae as declareCompanion, R as PRECISION_CODE, aQ as splitListText, ar as isImportInput, aZ as takenIn, ao as inputLength, a9 as capabilities, o as ID_TEXT_COLLISION_CODE, p as ID_TEXT_TYPE_CODE, au as joinText, ah as encodeChunks, aB as resolveExportOptions, aa as checkCapabilities, aw as pairFolding, L as LOSS, aU as mapDeclaredType, ai as explicitWeights, aq as isCanonicalIntegerText, aW as formatTemporal, al as formatF64, ak as formatF32, an as formatInteger, W as SINK_OPTION_CODE, J as OPTION_IGNORED_CODE, C as COLUMN_RENAMED_CODE, a1 as UNKNOWN_ATTR_TYPE_CODE, r as INVALID_UTF8_CODE, a5 as WEIGHT_KEY_CLASH_CODE, U as ROLE_DROPPED_CODE, F as MUTUAL_EXPANDED_CODE } from "./chunks/writer-DxSKC7TL.js";
2
+ import { c as checkRecordSyntax, R as RecordReader, B as BAD_QUOTE_CODE, U as UNCLOSED_QUOTE_CODE } from "./chunks/records-CGpxszm1.js";
3
+ import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
4
+ import { m as quoteCsvCell } from "./chunks/escape-DyI8JofU.js";
5
+ const STRUCTURAL = /* @__PURE__ */ new Set(["ID", "LABEL", "START_ID", "END_ID", "TYPE", "IGNORE"]);
6
+ const SPACE_KINDS = /* @__PURE__ */ new Set(["ID", "START_ID", "END_ID"]);
7
+ const HEADER_MARKER = /^[^:{}()]*:\s*(ID|START_ID|END_ID)\s*(\(|\{|$)/i;
8
+ const CELL = /^([^:(){}]*)(?::([^:(){}]*))?(?:\(([^)]*)\))?(?:\{([^}]*)\})?$/;
9
+ function isHeaderRecord(cells, count) {
10
+ for (let i = 0; i < count; i++) {
11
+ const cell = cells[i];
12
+ if (cell.indexOf(":") >= 0 && HEADER_MARKER.test(cell.trim())) {
13
+ return true;
14
+ }
15
+ }
16
+ return false;
17
+ }
18
+ function parseHeaderField(text) {
19
+ const trimmed = text.trim();
20
+ const m = CELL.exec(trimmed);
21
+ if (m === null) {
22
+ throw headerError$1(text, "not a neo4j header cell");
23
+ }
24
+ const name = m[1].trim();
25
+ const rawType = m[2] === void 0 ? null : m[2].trim();
26
+ const space = m[3] === void 0 ? null : m[3].trim();
27
+ const options = parseOptions(text, m[4]);
28
+ if (rawType === null) {
29
+ if (name.length === 0) {
30
+ throw headerError$1(text, "an empty header cell");
31
+ }
32
+ if (space !== null || options.size > 0) {
33
+ throw headerError$1(text, "an id space or options need a field type");
34
+ }
35
+ return { text, name, kind: "PROPERTY", type: null, space: null, options };
36
+ }
37
+ if (rawType.length === 0) {
38
+ throw headerError$1(text, "an empty field type");
39
+ }
40
+ const upper = rawType.toUpperCase();
41
+ if (STRUCTURAL.has(upper)) {
42
+ const kind = upper;
43
+ if (space !== null && !SPACE_KINDS.has(kind)) {
44
+ throw headerError$1(text, `an id space is only allowed on ID, START_ID and END_ID, not ${kind}`);
45
+ }
46
+ if (name.length > 0 && kind !== "ID" && kind !== "IGNORE") {
47
+ throw headerError$1(text, `a ${kind} field cannot be stored as a property`);
48
+ }
49
+ return { text, name, kind, type: null, space: space === "" ? null : space, options };
50
+ }
51
+ if (name.length === 0) {
52
+ throw headerError$1(text, "a property column needs a name");
53
+ }
54
+ if (space !== null) {
55
+ throw headerError$1(text, "an id space is only allowed on ID, START_ID and END_ID");
56
+ }
57
+ return { text, name, kind: "PROPERTY", type: rawType, space: null, options };
58
+ }
59
+ function parseOptions(text, body) {
60
+ const options = /* @__PURE__ */ new Map();
61
+ if (body === void 0 || body.trim().length === 0) {
62
+ return options;
63
+ }
64
+ for (const part of body.split(",")) {
65
+ const colon = part.indexOf(":");
66
+ if (colon < 0) {
67
+ throw headerError$1(text, `option "${part.trim()}" is not key:value`);
68
+ }
69
+ const key = part.slice(0, colon).trim().toLowerCase();
70
+ const value = part.slice(colon + 1).trim();
71
+ if (key.length === 0) {
72
+ throw headerError$1(text, "an option needs a key");
73
+ }
74
+ options.set(key, value);
75
+ }
76
+ return options;
77
+ }
78
+ function headerError$1(text, reason) {
79
+ return new GraphFormatError("E_UNSUPPORTED", `header cell "${text}": ${reason}`, {
80
+ reason: "header",
81
+ cell: text,
82
+ detail: reason
83
+ });
84
+ }
85
+ function formatHeaderField(name, type, space) {
86
+ const base = `${name}:${type}`;
87
+ return space === null ? base : `${base}(${space})`;
88
+ }
89
+ const LABELS_COLUMN = "labels";
90
+ const TYPE_COLUMN = "type";
91
+ const ID_SPACE_COLUMN = "idSpace";
92
+ const HEADER_CODE = "E_NEO4J_HEADER";
93
+ const COLUMN_COUNT_CODE = "E_NEO4J_COLUMN_COUNT";
94
+ const MISSING_ID_CODE = MISSING_ID_CODE$1;
95
+ const MISSING_ENDPOINT_CODE = MISSING_ENDPOINT_CODE$1;
96
+ const DUPLICATE_NODE_CODE = DUPLICATE_NODE_CODE$1;
97
+ const ID_SPACE_COLLISION_CODE = "E_NEO4J_ID_SPACE_COLLISION";
98
+ const ID_MERGED_CODE = ID_MERGED_CODE$1;
99
+ const HEADER_OPTION_CODE = "W_NEO4J_HEADER_OPTION_IGNORED";
100
+ const ROLE_TAKEN_CODE = ROLE_TAKEN_CODE$1;
101
+ const IGNORED_COLUMNS_LOSS = "W_NEO4J_IGNORED_COLUMNS";
102
+ const USED_OPTIONS = /* @__PURE__ */ new Set([
103
+ "ids",
104
+ "addMissingNodes",
105
+ "duplicateEdges",
106
+ "selfLoops",
107
+ "onMixedDirection",
108
+ "weightFrom",
109
+ "weightDtype",
110
+ "long",
111
+ "errorLimit",
112
+ "signal",
113
+ "onProgress"
114
+ ]);
115
+ const NEO4J$1 = "neo4j";
116
+ const ABORT_CHECK_INTERVAL = 64;
117
+ const LABELS_DECL = {
118
+ name: LABELS_COLUMN,
119
+ dtype: "list",
120
+ itemDtype: "dict",
121
+ nullable: true,
122
+ role: "labels",
123
+ origin: { format: NEO4J$1, id: ":LABEL", title: null, type: "LABEL", namespace: null }
124
+ };
125
+ const TYPE_DECL = {
126
+ name: TYPE_COLUMN,
127
+ dtype: "dict",
128
+ nullable: true,
129
+ role: "kind",
130
+ origin: { format: NEO4J$1, id: ":TYPE", title: null, type: "TYPE", namespace: null }
131
+ };
132
+ const ID_SPACE_DECL = {
133
+ name: ID_SPACE_COLUMN,
134
+ dtype: "dict",
135
+ nullable: true,
136
+ role: "idSpace",
137
+ origin: { format: NEO4J$1, id: ":ID", title: null, type: "ID", namespace: null }
138
+ };
139
+ const ARRAY_DELIMITERS = { ";": "semicolon", ",": "comma", "|": "pipe" };
140
+ const NEO4J_DELIMITER_CANDIDATES = Object.freeze([",", " "]);
141
+ function resolveNeo4jOptions(options) {
142
+ const o = options ?? {};
143
+ const arrayDelimiter = o.arrayDelimiter ?? ";";
144
+ const listSyntax = ARRAY_DELIMITERS[arrayDelimiter];
145
+ if (typeof arrayDelimiter !== "string" || listSyntax === void 0) {
146
+ throw new GraphFormatError(
147
+ "E_UNSUPPORTED",
148
+ `option arrayDelimiter: ${JSON.stringify(arrayDelimiter)} is not one of ";", ",", "|"`,
149
+ { option: "arrayDelimiter", found: arrayDelimiter }
150
+ );
151
+ }
152
+ const syntax = checkRecordSyntax({
153
+ delimiter: o.delimiter ?? null,
154
+ quote: o.quote ?? '"',
155
+ candidates: NEO4J_DELIMITER_CANDIDATES.filter((d) => d !== arrayDelimiter)
156
+ });
157
+ if (syntax.delimiter === arrayDelimiter) {
158
+ throw new GraphFormatError("E_UNSUPPORTED", "options delimiter and arrayDelimiter must differ", {
159
+ option: "arrayDelimiter",
160
+ found: arrayDelimiter
161
+ });
162
+ }
163
+ return {
164
+ nodes: inputList("nodes", o.nodes),
165
+ relationships: inputList("relationships", o.relationships),
166
+ syntax,
167
+ listSyntax
168
+ };
169
+ }
170
+ function inputList(name, value) {
171
+ if (value === void 0 || value === null) {
172
+ return [];
173
+ }
174
+ const list = Array.isArray(value) ? value : [value];
175
+ for (const item of list) {
176
+ if (!isImportInput(item)) {
177
+ throw new GraphFormatError("E_UNSUPPORTED", `option ${name}: an entry is not an ImportInput`, {
178
+ option: name,
179
+ found: typeof item
180
+ });
181
+ }
182
+ }
183
+ return list;
184
+ }
185
+ class NodeRegistry {
186
+ constructor() {
187
+ this.codes = new Uint32Array(1024);
188
+ this.spaces = /* @__PURE__ */ new Map();
189
+ }
190
+ /**
191
+ * The code of an id space (1 for "no space").
192
+ * @param space - the space name or null
193
+ * @returns a code >= 1
194
+ */
195
+ codeOf(space) {
196
+ let code = this.spaces.get(space);
197
+ if (code === void 0) {
198
+ code = this.spaces.size + 1;
199
+ this.spaces.set(space, code);
200
+ }
201
+ return code;
202
+ }
203
+ /**
204
+ * Record that a node row declared a node.
205
+ * @param index - the node index
206
+ * @param code - the space code
207
+ * @returns "new" for a first declaration, "duplicate" for a repeat in the same space, "collision" across spaces
208
+ */
209
+ declare(index, code) {
210
+ if (index >= this.codes.length) {
211
+ let size = this.codes.length * 2;
212
+ while (size <= index) {
213
+ size *= 2;
214
+ }
215
+ const grown = new Uint32Array(size);
216
+ grown.set(this.codes);
217
+ this.codes = grown;
218
+ }
219
+ const previous = this.codes[index];
220
+ if (previous === 0) {
221
+ this.codes[index] = code;
222
+ return "new";
223
+ }
224
+ return previous === code ? "duplicate" : "collision";
225
+ }
226
+ }
227
+ class ProgressTracker {
228
+ /**
229
+ * Create a tracker.
230
+ * @param callback - the caller's onProgress, or null
231
+ * @param inputs - every input in reading order
232
+ */
233
+ constructor(callback, inputs) {
234
+ this.offset = 0;
235
+ this.lastDone = 0;
236
+ this.callback = callback;
237
+ let total = 0;
238
+ for (const input of inputs) {
239
+ const length = inputLength(input);
240
+ if (length === null) {
241
+ total = void 0;
242
+ break;
243
+ }
244
+ total += length;
245
+ }
246
+ this.total = total;
247
+ }
248
+ /**
249
+ * The read options for one input.
250
+ * @param signal - the cancellation signal
251
+ * @returns options whose onProgress reports cumulative bytes
252
+ */
253
+ optionsFor(signal) {
254
+ const { callback } = this;
255
+ if (callback === null) {
256
+ return { signal };
257
+ }
258
+ return {
259
+ signal,
260
+ onProgress: (done) => {
261
+ this.lastDone = done;
262
+ callback(this.offset + done, this.total);
263
+ }
264
+ };
265
+ }
266
+ /** Move the offset past the input just finished. */
267
+ finishInput() {
268
+ this.offset += this.lastDone;
269
+ this.lastDone = 0;
270
+ }
271
+ }
272
+ class Neo4jImportSession {
273
+ /**
274
+ * Create a session.
275
+ * @param sink - the sink
276
+ * @param report - the report
277
+ * @param common - the resolved common options
278
+ * @param options - the resolved format options
279
+ */
280
+ constructor(sink, report, common, options) {
281
+ this.registry = new NodeRegistry();
282
+ this.labelsHandle = INVALID_INDEX;
283
+ this.typeHandle = INVALID_INDEX;
284
+ this.idSpaceHandle = INVALID_INDEX;
285
+ this.ignoredColumns = 0;
286
+ this.headerSet = false;
287
+ this.values = [];
288
+ this.texts = [];
289
+ this.precisionSlots = [];
290
+ this.sink = sink;
291
+ this.report = report;
292
+ this.common = common;
293
+ this.options = options;
294
+ this.coercer = new IdCoercer(common.ids);
295
+ this.direction = new DirectionResolver(sink, report, common.onMixedDirection);
296
+ }
297
+ /**
298
+ * Read every input.
299
+ * @param inputs - the inputs in reading order
300
+ */
301
+ async run(inputs) {
302
+ const progress = new ProgressTracker(this.common.onProgress, inputs);
303
+ for (const input of inputs) {
304
+ await this.readInput(input, progress.optionsFor(this.common.signal));
305
+ progress.finishInput();
306
+ }
307
+ if (this.ignoredColumns > 0) {
308
+ this.report.loss(
309
+ IGNORED_COLUMNS_LOSS,
310
+ `${this.ignoredColumns} :IGNORE column(s) were skipped as the header instructs`,
311
+ null,
312
+ this.ignoredColumns
313
+ );
314
+ }
315
+ }
316
+ /**
317
+ * Read one input: a header row, then data rows until the next header row.
318
+ * @param input - the input
319
+ * @param readOptions - cancellation and progress
320
+ */
321
+ async readInput(input, readOptions) {
322
+ const reader = new RecordReader(input, this.report, this.options.syntax, readOptions);
323
+ let section = null;
324
+ let sinceCheck = 0;
325
+ for await (const count of reader) {
326
+ if (section === null || isHeaderRecord(reader.cells, count)) {
327
+ section = this.declareSection(reader, count);
328
+ continue;
329
+ }
330
+ if (section.kind === "node") {
331
+ this.nodeRow(section, reader, count);
332
+ } else {
333
+ this.relationshipRow(section, reader, count);
334
+ }
335
+ if (++sinceCheck >= ABORT_CHECK_INTERVAL) {
336
+ sinceCheck = 0;
337
+ throwIfAborted(readOptions.signal);
338
+ }
339
+ }
340
+ if (section === null) {
341
+ this.report.fail(HEADER_CODE, "the input has no header row", { line: 1 });
342
+ }
343
+ }
344
+ /**
345
+ * Interpret a header row: parse every cell, check the section shape, declare its columns.
346
+ * @param reader - the reader positioned on the header
347
+ * @param count - the number of header cells
348
+ * @returns the section
349
+ */
350
+ declareSection(reader, count) {
351
+ const { line } = reader;
352
+ const fields = [];
353
+ try {
354
+ for (let i = 0; i < count; i++) {
355
+ fields.push(parseHeaderField(reader.cells[i]));
356
+ }
357
+ const kinds = /* @__PURE__ */ new Map();
358
+ fields.forEach((field, i) => {
359
+ const list = kinds.get(field.kind);
360
+ if (list === void 0) {
361
+ kinds.set(field.kind, [i]);
362
+ } else {
363
+ list.push(i);
364
+ }
365
+ });
366
+ const ids = kinds.get("ID") ?? [];
367
+ const starts = kinds.get("START_ID") ?? [];
368
+ const ends = kinds.get("END_ID") ?? [];
369
+ const isNode = ids.length > 0;
370
+ const isRelationship = starts.length > 0 || ends.length > 0;
371
+ if (isNode && isRelationship) {
372
+ throw headerError("a header mixes :ID with :START_ID / :END_ID");
373
+ }
374
+ if (!isNode && !isRelationship) {
375
+ throw headerError(
376
+ "a header needs an :ID column (nodes) or :START_ID and :END_ID columns (relationships)"
377
+ );
378
+ }
379
+ checkPropertyNames(fields);
380
+ this.ignoredColumns += (kinds.get("IGNORE") ?? []).length;
381
+ if (isNode) {
382
+ if (ids.length > 1) {
383
+ throw headerError("a node header has more than one :ID column");
384
+ }
385
+ if (kinds.has("TYPE")) {
386
+ throw headerError("a node header cannot have a :TYPE column");
387
+ }
388
+ return this.declareNodeSection(fields, ids[0], kinds.get("LABEL") ?? [], line);
389
+ }
390
+ if (starts.length !== 1 || ends.length !== 1) {
391
+ throw headerError("a relationship header needs exactly one :START_ID and one :END_ID column");
392
+ }
393
+ if (kinds.has("LABEL")) {
394
+ throw headerError("a relationship header cannot have a :LABEL column");
395
+ }
396
+ const types = kinds.get("TYPE") ?? [];
397
+ if (types.length > 1) {
398
+ throw headerError("a relationship header has more than one :TYPE column");
399
+ }
400
+ return this.declareRelationshipSection(
401
+ fields,
402
+ starts[0],
403
+ ends[0],
404
+ types.length === 1 ? types[0] : -1,
405
+ line
406
+ );
407
+ } catch (err) {
408
+ if (err instanceof GraphFormatError && !(err instanceof ImportError)) {
409
+ this.report.fail(HEADER_CODE, `line ${line}: ${err.message}`, { line }, { cause: err.code });
410
+ }
411
+ throw err;
412
+ }
413
+ }
414
+ /**
415
+ * Declare the columns of a node section.
416
+ * @param fields - the parsed header
417
+ * @param idCell - the `:ID` cell
418
+ * @param labelCells - the `:LABEL` cells
419
+ * @param line - the header line
420
+ * @returns the section
421
+ */
422
+ declareNodeSection(fields, idCell, labelCells, line) {
423
+ const idField = fields[idCell];
424
+ const extraLabels = [];
425
+ for (const field of fields) {
426
+ for (const [key, value] of field.options) {
427
+ if (key === "label" && field.kind === "ID") {
428
+ extraLabels.push(value);
429
+ } else {
430
+ this.report.warning(
431
+ "unsupported",
432
+ HEADER_OPTION_CODE,
433
+ `header option ${key}:${value} of "${field.text}" is ignored`,
434
+ { line, element: field.text }
435
+ );
436
+ }
437
+ }
438
+ }
439
+ if (labelCells.length > 0 || extraLabels.length > 0) {
440
+ this.ensureLabels();
441
+ }
442
+ if (idField.space !== null) {
443
+ this.ensureIdSpace();
444
+ }
445
+ let idHandle = INVALID_INDEX;
446
+ if (idField.name.length > 0) {
447
+ idHandle = this.declareProperty(
448
+ "node",
449
+ { ...idField, type: null },
450
+ idCell,
451
+ { origin: { format: NEO4J$1, id: idField.name, title: null, type: "ID", namespace: idField.space } },
452
+ line
453
+ ).handle;
454
+ }
455
+ const properties = this.declareProperties("node", fields, line, null);
456
+ return {
457
+ kind: "node",
458
+ width: fields.length,
459
+ idCell,
460
+ idHandle,
461
+ space: idField.space,
462
+ spaceCode: this.registry.codeOf(idField.space),
463
+ labelCells,
464
+ extraLabels,
465
+ properties
466
+ };
467
+ }
468
+ /**
469
+ * Declare the columns of a relationship section.
470
+ * @param fields - the parsed header
471
+ * @param startCell - the `:START_ID` cell
472
+ * @param endCell - the `:END_ID` cell
473
+ * @param typeCell - the `:TYPE` cell, or -1
474
+ * @param line - the header line
475
+ * @returns the section
476
+ */
477
+ declareRelationshipSection(fields, startCell, endCell, typeCell, line) {
478
+ for (const field of fields) {
479
+ for (const [key, value] of field.options) {
480
+ this.report.warning(
481
+ "unsupported",
482
+ HEADER_OPTION_CODE,
483
+ `header option ${key}:${value} of "${field.text}" is ignored`,
484
+ { line, element: field.text }
485
+ );
486
+ }
487
+ }
488
+ if (typeCell >= 0) {
489
+ this.ensureType();
490
+ }
491
+ const { weightFrom } = this.common;
492
+ let weightCell = -1;
493
+ if (weightFrom !== null) {
494
+ weightCell = fields.findIndex((field) => field.kind === "PROPERTY" && field.name === weightFrom);
495
+ }
496
+ const properties = this.declareProperties("edge", fields, line, weightCell);
497
+ return { kind: "relationship", width: fields.length, startCell, endCell, typeCell, weightCell, properties };
498
+ }
499
+ /**
500
+ * Declare every PROPERTY field of a header.
501
+ * @param domain - node or edge
502
+ * @param fields - the parsed header
503
+ * @param line - the header line
504
+ * @param skipCell - a cell to leave undeclared (the weight), or null
505
+ * @returns the property slots
506
+ */
507
+ declareProperties(domain, fields, line, skipCell) {
508
+ const slots = [];
509
+ fields.forEach((field, cell) => {
510
+ if (field.kind !== "PROPERTY" || cell === skipCell) {
511
+ return;
512
+ }
513
+ slots.push(this.declareProperty(domain, field, cell, {}, line));
514
+ });
515
+ const width = fields.length;
516
+ if (this.values.length < width) {
517
+ this.values = new Array(width);
518
+ this.texts = new Array(width);
519
+ }
520
+ return slots;
521
+ }
522
+ /**
523
+ * Declare one property column on the sink: the same name and shape again shares the column;
524
+ * a different shape under the same name is renamed `<name>#<name>` (design section 5.6).
525
+ * @param domain - node or edge
526
+ * @param field - the header field
527
+ * @param cell - the field's cell index
528
+ * @param patch - declaration fields to override (the id property's origin)
529
+ * @param line - the header line
530
+ * @returns the slot
531
+ */
532
+ declareProperty(domain, field, cell, patch, line) {
533
+ const { sink, report } = this;
534
+ const { listSyntax } = this.options;
535
+ const input = {
536
+ format: NEO4J$1,
537
+ id: field.name,
538
+ title: null,
539
+ type: field.type,
540
+ namespace: null,
541
+ listSyntax,
542
+ long: this.common.long
543
+ };
544
+ let declared = declareAttribute(input);
545
+ let decl = { ...declared.decl, ...patch };
546
+ let handle;
547
+ try {
548
+ handle = declareOn(sink, domain, decl);
549
+ } catch (err) {
550
+ if (!(err instanceof GraphFormatError) || err.code !== "E_COLUMN_EXISTS") {
551
+ throw err;
552
+ }
553
+ declared = declareAttribute({ ...input, taken: takenIn(sink, domain) });
554
+ decl = { ...declared.decl, ...patch, name: declared.decl.name };
555
+ handle = declareOn(sink, domain, decl);
556
+ }
557
+ for (const issue of declared.issues) {
558
+ report.warning(issue.category, issue.code, issue.message, { line, element: field.text });
559
+ }
560
+ return {
561
+ cell,
562
+ name: decl.name,
563
+ handle,
564
+ spec: declared.spec,
565
+ companionDecl: declared.companion,
566
+ companion: INVALID_INDEX
567
+ };
568
+ }
569
+ /** Declare (or adopt) the labels column. */
570
+ ensureLabels() {
571
+ if (this.labelsHandle === INVALID_INDEX) {
572
+ this.labelsHandle = this.declareReserved("node", LABELS_DECL);
573
+ }
574
+ }
575
+ /** Declare (or adopt) the relationship type column. */
576
+ ensureType() {
577
+ if (this.typeHandle === INVALID_INDEX) {
578
+ this.typeHandle = this.declareReserved("edge", TYPE_DECL);
579
+ }
580
+ }
581
+ /** Declare (or adopt) the id space column. */
582
+ ensureIdSpace() {
583
+ if (this.idSpaceHandle === INVALID_INDEX) {
584
+ this.idSpaceHandle = this.declareReserved("node", ID_SPACE_DECL);
585
+ }
586
+ }
587
+ /**
588
+ * Declare a reserved column: the same shape again shares it; a name taken by another shape is
589
+ * renamed `<name>#<origin.id>` and reported; a role the sink already holds elsewhere is dropped
590
+ * and reported.
591
+ * @param domain - node or edge
592
+ * @param decl - the reserved declaration
593
+ * @returns the handle
594
+ */
595
+ declareReserved(domain, decl) {
596
+ const { sink, report } = this;
597
+ let current = decl;
598
+ for (; ; ) {
599
+ try {
600
+ return declareOn(sink, domain, current);
601
+ } catch (err) {
602
+ if (!(err instanceof GraphFormatError)) {
603
+ throw err;
604
+ }
605
+ if (err.code === "E_COLUMN_EXISTS") {
606
+ const name = uniqueColumnName(current.name, current.origin?.id ?? null, takenIn(sink, domain));
607
+ report.warning(
608
+ "coercion",
609
+ RENAMED_CODE,
610
+ `column "${current.name}" renamed to "${name}": the name was taken`,
611
+ { element: current.name }
612
+ );
613
+ current = { ...current, name };
614
+ } else if (err.code === "E_DUPLICATE_ROLE") {
615
+ report.warning(
616
+ "coercion",
617
+ ROLE_TAKEN_CODE,
618
+ `column "${current.name}" declared without role "${String(current.role)}": the sink already holds that role`,
619
+ { element: current.name }
620
+ );
621
+ const { role: _role, ...withoutRole } = current;
622
+ current = withoutRole;
623
+ } else {
624
+ throw err;
625
+ }
626
+ }
627
+ }
628
+ }
629
+ /**
630
+ * Push one node row.
631
+ * @param section - the section
632
+ * @param reader - the reader positioned on the row
633
+ * @param count - the row's cell count
634
+ */
635
+ nodeRow(section, reader, count) {
636
+ const { sink, report } = this;
637
+ const { cells, quoted, line } = reader;
638
+ if (count !== section.width) {
639
+ report.error(
640
+ "validation-error",
641
+ COLUMN_COUNT_CODE,
642
+ `row has ${count} cell(s) but the header has ${section.width}`,
643
+ { line }
644
+ );
645
+ report.counts.skippedNodes++;
646
+ return;
647
+ }
648
+ const idText2 = cells[section.idCell];
649
+ if (idText2.length === 0 && !quoted[section.idCell]) {
650
+ report.error("missing-value", MISSING_ID_CODE, "empty :ID cell", { line });
651
+ report.counts.skippedNodes++;
652
+ return;
653
+ }
654
+ const id = this.coerceId(idText2, line);
655
+ if (id === null || !this.parseProperties(section.properties, cells, quoted, line, idText2)) {
656
+ report.counts.skippedNodes++;
657
+ return;
658
+ }
659
+ let labels;
660
+ if (section.labelCells.length > 0 || section.extraLabels.length > 0) {
661
+ labels = this.labelsOf(section, cells, quoted);
662
+ }
663
+ const index = sink.addNode(id);
664
+ const status = this.registry.declare(index, section.spaceCode);
665
+ if (status === "collision") {
666
+ report.error(
667
+ "validation-error",
668
+ ID_SPACE_COLLISION_CODE,
669
+ `node ${idText2} is declared in id space ${section.space ?? "(none)"} and in another id space; the core has one id space and this row is skipped`,
670
+ { line, element: idText2 }
671
+ );
672
+ report.counts.skippedNodes++;
673
+ return;
674
+ }
675
+ if (status === "duplicate") {
676
+ report.warning(
677
+ "merged",
678
+ DUPLICATE_NODE_CODE,
679
+ `node ${idText2} is declared twice; the later properties win`,
680
+ {
681
+ line,
682
+ element: idText2
683
+ }
684
+ );
685
+ }
686
+ if (section.idHandle !== INVALID_INDEX) {
687
+ sink.setNodeValue(section.idHandle, index, idText2);
688
+ }
689
+ if (section.space !== null) {
690
+ sink.setNodeValue(this.idSpaceHandle, index, section.space);
691
+ }
692
+ if (labels !== void 0) {
693
+ sink.setNodeValue(this.labelsHandle, index, labels);
694
+ }
695
+ this.writeProperties("node", section.properties, index);
696
+ this.reportPrecision(line, idText2);
697
+ report.counts.nodes++;
698
+ }
699
+ /**
700
+ * Push one relationship row.
701
+ * @param section - the section
702
+ * @param reader - the reader positioned on the row
703
+ * @param count - the row's cell count
704
+ */
705
+ relationshipRow(section, reader, count) {
706
+ const { sink, report } = this;
707
+ const { cells, quoted, line } = reader;
708
+ if (!this.headerSet) {
709
+ this.headerSet = true;
710
+ this.direction.setHeader(true, { line });
711
+ }
712
+ if (count !== section.width) {
713
+ report.error(
714
+ "validation-error",
715
+ COLUMN_COUNT_CODE,
716
+ `row has ${count} cell(s) but the header has ${section.width}`,
717
+ { line }
718
+ );
719
+ report.counts.skippedEdges++;
720
+ return;
721
+ }
722
+ const startText = cells[section.startCell];
723
+ const endText = cells[section.endCell];
724
+ const startMissing = startText.length === 0 && !quoted[section.startCell];
725
+ if (startMissing || endText.length === 0 && !quoted[section.endCell]) {
726
+ report.error(
727
+ "missing-value",
728
+ MISSING_ENDPOINT_CODE,
729
+ `empty ${startMissing ? ":START_ID" : ":END_ID"} cell`,
730
+ {
731
+ line
732
+ }
733
+ );
734
+ report.counts.skippedEdges++;
735
+ return;
736
+ }
737
+ const element = `${startText}->${endText}`;
738
+ const source = this.coerceId(startText, line);
739
+ const target = source === null ? null : this.coerceId(endText, line);
740
+ if (source === null || target === null) {
741
+ report.counts.skippedEdges++;
742
+ return;
743
+ }
744
+ let weight;
745
+ if (section.weightCell >= 0) {
746
+ try {
747
+ weight = parseWeightText(cells[section.weightCell]);
748
+ } catch (err) {
749
+ report.recordError(err, { line, element });
750
+ report.counts.skippedEdges++;
751
+ return;
752
+ }
753
+ }
754
+ if (!this.parseProperties(section.properties, cells, quoted, line, element)) {
755
+ report.counts.skippedEdges++;
756
+ return;
757
+ }
758
+ let edge;
759
+ try {
760
+ edge = this.direction.addEdge(source, target, "directed", weight, { line, element });
761
+ } catch (err) {
762
+ report.recordError(err, { line, element });
763
+ report.counts.skippedEdges++;
764
+ return;
765
+ }
766
+ if (section.typeCell >= 0) {
767
+ const type = cells[section.typeCell];
768
+ if (type.length > 0) {
769
+ sink.setEdgeValue(this.typeHandle, edge, type);
770
+ }
771
+ }
772
+ this.writeProperties("edge", section.properties, edge);
773
+ this.reportPrecision(line, element);
774
+ report.counts.edges++;
775
+ }
776
+ /**
777
+ * Coerce an id cell, reporting a merge under `ids: "number"` and an invalid id as an error.
778
+ * @param text - the cell
779
+ * @param line - the row's line
780
+ * @returns the id, or null when the cell was rejected (the error is recorded)
781
+ */
782
+ coerceId(text, line) {
783
+ const { coercer, report } = this;
784
+ let id;
785
+ try {
786
+ id = coercer.text(text);
787
+ } catch (err) {
788
+ report.recordError(err, { line, element: text });
789
+ return null;
790
+ }
791
+ const merge = coercer.lastMerge;
792
+ if (merge !== null) {
793
+ report.warning(
794
+ "coercion",
795
+ ID_MERGED_CODE,
796
+ `id "${merge.text}" merged with "${merge.previousText}" as ${merge.id} under ids: "number"`,
797
+ { line, element: text }
798
+ );
799
+ }
800
+ return id;
801
+ }
802
+ /**
803
+ * Parse the property cells of a row into the scratch arrays.
804
+ * @param slots - the section's property slots
805
+ * @param cells - the row's cells
806
+ * @param quoted - whether each cell was quoted
807
+ * @param line - the row's line
808
+ * @param element - the row's element name for issues
809
+ * @returns true when every cell parsed; false after recording the first error
810
+ */
811
+ parseProperties(slots, cells, quoted, line, element) {
812
+ const { values, texts, precisionSlots } = this;
813
+ precisionSlots.length = 0;
814
+ for (const slot of slots) {
815
+ const text = cells[slot.cell];
816
+ texts[slot.cell] = null;
817
+ if (text.length === 0) {
818
+ values[slot.cell] = quoted[slot.cell] ? emptyValue(slot.spec) : void 0;
819
+ continue;
820
+ }
821
+ const { spec } = slot;
822
+ try {
823
+ if (spec.temporal !== null && !spec.list) {
824
+ const parsed = parseDeclaredTemporal(text, spec);
825
+ values[slot.cell] = parsed.value;
826
+ texts[slot.cell] = parsed.text;
827
+ } else {
828
+ values[slot.cell] = parseDeclaredValue(text, spec, this.options.listSyntax);
829
+ }
830
+ } catch (err) {
831
+ this.report.recordError(err, { line, element: `${element} ${slot.name}` });
832
+ return false;
833
+ }
834
+ if (spec.precision && losesPrecision(spec, text)) {
835
+ precisionSlots.push(slot);
836
+ }
837
+ }
838
+ return true;
839
+ }
840
+ /**
841
+ * Write the parsed property values of a row.
842
+ * @param domain - node or edge
843
+ * @param slots - the section's property slots
844
+ * @param row - the node or edge index
845
+ */
846
+ writeProperties(domain, slots, row) {
847
+ const { sink, values, texts } = this;
848
+ for (const slot of slots) {
849
+ const value = values[slot.cell];
850
+ if (value === void 0) {
851
+ continue;
852
+ }
853
+ const text = texts[slot.cell];
854
+ if (text !== null && slot.companion === INVALID_INDEX && slot.companionDecl !== null) {
855
+ slot.companion = declareCompanion(sink, domain, slot.companionDecl);
856
+ }
857
+ if (domain === "node") {
858
+ sink.setNodeValue(slot.handle, row, value);
859
+ if (text !== null) {
860
+ sink.setNodeValue(slot.companion, row, text);
861
+ }
862
+ } else {
863
+ sink.setEdgeValue(slot.handle, row, value);
864
+ if (text !== null) {
865
+ sink.setEdgeValue(slot.companion, row, text);
866
+ }
867
+ }
868
+ }
869
+ }
870
+ /**
871
+ * Report the precision losses of the row just accepted.
872
+ * @param line - the row's line
873
+ * @param element - the row's element name
874
+ */
875
+ reportPrecision(line, element) {
876
+ for (const slot of this.precisionSlots) {
877
+ this.report.warning(
878
+ "precision",
879
+ PRECISION_CODE,
880
+ `long value of "${slot.name}" is beyond 2^53 and was stored as the nearest f64`,
881
+ { line, element }
882
+ );
883
+ }
884
+ this.precisionSlots.length = 0;
885
+ }
886
+ /**
887
+ * The labels of a node row: every `:LABEL` cell split by the array delimiter (empty items
888
+ * dropped), plus the header's `{label:...}` options.
889
+ * @param section - the section
890
+ * @param cells - the row's cells
891
+ * @param quoted - whether each cell was quoted
892
+ * @returns the labels, or undefined when every label cell is unset and no extra label exists
893
+ */
894
+ labelsOf(section, cells, quoted) {
895
+ let labels;
896
+ for (const cell of section.labelCells) {
897
+ const text = cells[cell];
898
+ if (text.length === 0 && !quoted[cell]) {
899
+ continue;
900
+ }
901
+ if (labels === void 0) {
902
+ labels = [];
903
+ }
904
+ for (const item of splitListText(text, this.options.listSyntax)) {
905
+ if (item.length > 0) {
906
+ labels.push(item);
907
+ }
908
+ }
909
+ }
910
+ if (section.extraLabels.length > 0) {
911
+ if (labels === void 0) {
912
+ labels = [];
913
+ }
914
+ labels.push(...section.extraLabels);
915
+ }
916
+ return labels;
917
+ }
918
+ }
919
+ function emptyValue(spec) {
920
+ if (spec.list) {
921
+ return [];
922
+ }
923
+ if (spec.kind === "string" || spec.kind === "duration") {
924
+ return "";
925
+ }
926
+ return void 0;
927
+ }
928
+ function checkPropertyNames(fields) {
929
+ const seen = /* @__PURE__ */ new Set();
930
+ for (const field of fields) {
931
+ if (field.name.length === 0 || field.kind === "IGNORE") {
932
+ continue;
933
+ }
934
+ if (seen.has(field.name)) {
935
+ throw headerError(`property "${field.name}" is declared twice`);
936
+ }
937
+ seen.add(field.name);
938
+ }
939
+ }
940
+ function headerError(reason) {
941
+ return new GraphFormatError("E_UNSUPPORTED", reason, { reason: "header" });
942
+ }
943
+ function sniffNeo4j(head) {
944
+ const text = new TextDecoder("utf-8", { fatal: false, ignoreBOM: false }).decode(head);
945
+ const end = text.search(/[\r\n]/);
946
+ const first = end < 0 ? text : text.slice(0, end);
947
+ const cells = first.split(/[,;|\t]/);
948
+ return isHeaderRecord(cells, cells.length) ? 0.95 : 0;
949
+ }
950
+ const neo4jImporter = Object.freeze({
951
+ format: NEO4J$1,
952
+ extensions: Object.freeze([".csv", ".tsv"]),
953
+ mimeTypes: Object.freeze(["text/csv", "text/tab-separated-values"]),
954
+ sniff: sniffNeo4j,
955
+ /**
956
+ * Read one or more neo4j-admin CSV inputs into the sink.
957
+ * @param input - the primary input (a node table, a relationship table, or sections of both)
958
+ * @param sink - the sink
959
+ * @param options - format-specific and common options
960
+ * @returns the report; ImportError beyond the error limit or on a malformed header
961
+ */
962
+ async import(input, sink, options) {
963
+ const common = resolveImportOptions(options, { ids: "canonical", defaultDirected: true, weightFrom: "weight" });
964
+ const format = resolveNeo4jOptions(options);
965
+ const report = new ImportReportBuilder(NEO4J$1, common.errorLimit);
966
+ reportSinkOptions(sink, options, report);
967
+ reportUnusedOptions(options, report, USED_OPTIONS);
968
+ const session = new Neo4jImportSession(sink, report, common, format);
969
+ await session.run([input, ...format.nodes, ...format.relationships]);
970
+ throwIfAborted(common.signal);
971
+ return report.finish();
972
+ }
973
+ });
974
+ const UNDIRECTED_LOSS = "W_NEO4J_UNDIRECTED_AS_DIRECTED";
975
+ const ID_TEXT_TYPE_LOSS = ID_TEXT_TYPE_CODE;
976
+ const ID_TEXT_COLLISION_LOSS = ID_TEXT_COLLISION_CODE;
977
+ const WEIGHT_COLUMN_TAKEN_LOSS = "E_NEO4J_WEIGHT_COLUMN_TAKEN";
978
+ const ID_COLUMN_TAKEN_LOSS = "E_NEO4J_ID_COLUMN_TAKEN";
979
+ const MULTIPLE_ID_PROPERTIES_LOSS = "W_NEO4J_MULTIPLE_ID_PROPERTIES";
980
+ const DECLARED_TYPE_CHANGED_LOSS = "W_NEO4J_DECLARED_TYPE_CHANGED";
981
+ const ARRAY_DELIMITER_LOSS = "W_NEO4J_ARRAY_DELIMITER";
982
+ const SLOT_ROLES = /* @__PURE__ */ new Set(["kind", "labels", "idSpace"]);
983
+ const NEO4J = "neo4j";
984
+ const ID_TYPE = "ID";
985
+ const DEFAULT_WEIGHT_COLUMN = "weight";
986
+ const ROLE_NAMES = Object.freeze({
987
+ kind: TYPE_COLUMN,
988
+ labels: LABELS_COLUMN,
989
+ idSpace: ID_SPACE_COLUMN
990
+ });
991
+ const NEO4J_CAPABILITIES = capabilities({
992
+ mixedDirection: false,
993
+ multiEdges: true,
994
+ selfLoops: true,
995
+ edgeIds: "none",
996
+ idCharset: "any",
997
+ dtypes: ["f32", "f64", "i32", "bool", "string"],
998
+ components: false,
999
+ lists: true,
1000
+ json: false,
1001
+ defaults: false,
1002
+ options: false,
1003
+ hierarchy: false,
1004
+ temporal: "none",
1005
+ graphAttributes: false,
1006
+ positions: false,
1007
+ viz: false
1008
+ });
1009
+ const SKIPPED_ROLES = /* @__PURE__ */ new Set([
1010
+ "directed",
1011
+ "pair",
1012
+ "mutual",
1013
+ "weight",
1014
+ "timeText",
1015
+ "originalId",
1016
+ "parent",
1017
+ "parents",
1018
+ "start",
1019
+ "end",
1020
+ "timestamp",
1021
+ "timestamps",
1022
+ "spells",
1023
+ "open"
1024
+ ]);
1025
+ const EDGE_NOTE_CODES = /* @__PURE__ */ new Set([
1026
+ LOSS.MIXED_DIRECTION,
1027
+ LOSS.MIXED_DIRECTION_ERROR,
1028
+ LOSS.MULTI_EDGES,
1029
+ LOSS.SELF_LOOPS,
1030
+ LOSS.EDGE_IDS_GENERATED,
1031
+ LOSS.EDGE_IDS_DROPPED,
1032
+ UNDIRECTED_LOSS,
1033
+ WEIGHT_COLUMN_TAKEN_LOSS
1034
+ ]);
1035
+ const NODE_NOTE_CODES = /* @__PURE__ */ new Set([
1036
+ LOSS.ID_MANGLED,
1037
+ LOSS.ID_CHARSET,
1038
+ LOSS.ID_RENUMBERED,
1039
+ ID_TEXT_TYPE_LOSS,
1040
+ ID_COLUMN_TAKEN_LOSS,
1041
+ MULTIPLE_ID_PROPERTIES_LOSS
1042
+ ]);
1043
+ const ARRAY_SYNTAXES = /* @__PURE__ */ new Set([";", ",", "|"]);
1044
+ const PARTS = /* @__PURE__ */ new Set(["all", "nodes", "relationships"]);
1045
+ function resolveNeo4jExportOptions(options) {
1046
+ const o = options ?? {};
1047
+ const part = o.part ?? "all";
1048
+ if (!PARTS.has(part)) {
1049
+ throw new GraphFormatError(
1050
+ "E_UNSUPPORTED",
1051
+ `option part: ${JSON.stringify(part)} is not "all", "nodes" or "relationships"`,
1052
+ {
1053
+ option: "part",
1054
+ found: part
1055
+ }
1056
+ );
1057
+ }
1058
+ const arrayDelimiter = o.arrayDelimiter ?? ";";
1059
+ if (!ARRAY_SYNTAXES.has(arrayDelimiter)) {
1060
+ throw new GraphFormatError(
1061
+ "E_UNSUPPORTED",
1062
+ `option arrayDelimiter: ${JSON.stringify(arrayDelimiter)} is not one of ";", ",", "|"`,
1063
+ { option: "arrayDelimiter", found: arrayDelimiter }
1064
+ );
1065
+ }
1066
+ const delimiter = o.delimiter ?? ",";
1067
+ const syntax = { ...checkRecordSyntax({ delimiter, quote: o.quote ?? '"' }), delimiter };
1068
+ if (syntax.delimiter === arrayDelimiter) {
1069
+ throw new GraphFormatError("E_UNSUPPORTED", "options delimiter and arrayDelimiter must differ", {
1070
+ option: "arrayDelimiter",
1071
+ found: arrayDelimiter
1072
+ });
1073
+ }
1074
+ const weightColumn = o.weightColumn === void 0 ? "weight" : o.weightColumn;
1075
+ if (weightColumn !== null && (typeof weightColumn !== "string" || weightColumn.length === 0)) {
1076
+ throw new GraphFormatError("E_UNSUPPORTED", "option weightColumn: expected a non-empty name or null", {
1077
+ option: "weightColumn",
1078
+ found: weightColumn
1079
+ });
1080
+ }
1081
+ const idColumn = o.idColumn ?? null;
1082
+ if (idColumn !== null && (typeof idColumn !== "string" || idColumn.length === 0)) {
1083
+ throw new GraphFormatError("E_UNSUPPORTED", "option idColumn: expected a non-empty name or null", {
1084
+ option: "idColumn",
1085
+ found: idColumn
1086
+ });
1087
+ }
1088
+ return { part, syntax, arrayDelimiter, weightColumn, idColumn };
1089
+ }
1090
+ class ExportPlan {
1091
+ /**
1092
+ * Build the plan.
1093
+ * @param snapshot - the snapshot
1094
+ * @param options - the resolved format options
1095
+ * @param common - the resolved common options
1096
+ */
1097
+ constructor(snapshot, options, common) {
1098
+ this.notes = [];
1099
+ this.ownNotes = [];
1100
+ this.fatalNodes = null;
1101
+ this.fatalEdges = null;
1102
+ this.fatalAll = null;
1103
+ this.nodeColumns = [];
1104
+ this.edgeColumns = [];
1105
+ this.idColumns = [];
1106
+ this.snapshot = snapshot;
1107
+ this.options = options;
1108
+ this.common = common;
1109
+ this.idSpace = snapshot.nodes.byRole("idSpace");
1110
+ this.labels = snapshot.nodes.byRole("labels");
1111
+ this.kind = snapshot.edges.byRole("kind");
1112
+ for (const column of snapshot.nodes) {
1113
+ if (isStoredId(column.meta)) {
1114
+ this.idColumns.push(column);
1115
+ }
1116
+ }
1117
+ const generic = checkCapabilities(snapshot, NEO4J_CAPABILITIES, common, {
1118
+ roles: SLOT_ROLES,
1119
+ roleNames: ROLE_NAMES,
1120
+ temporalText: true
1121
+ });
1122
+ this.planColumns("node", snapshot.nodes, this.nodeColumns);
1123
+ this.planColumns("edge", snapshot.edges, this.edgeColumns);
1124
+ this.weights = this.planWeightSource();
1125
+ this.folding = pairFolding(snapshot);
1126
+ this.checkIds();
1127
+ this.checkIdColumn();
1128
+ this.checkStoredIds();
1129
+ this.directionNotes();
1130
+ const slots = new Set([this.kind, this.labels, this.idSpace].flatMap((c) => c === null ? [] : [c.meta.name]));
1131
+ for (const gen of generic) {
1132
+ if (gen.code === LOSS.JSON && gen.column !== null && this.writtenAsPoint(gen.column)) {
1133
+ continue;
1134
+ }
1135
+ if (gen.code === LOSS.DTYPE && gen.column !== null && slots.has(gen.column)) {
1136
+ continue;
1137
+ }
1138
+ if (gen.code === LOSS.MIXED_DIRECTION_ERROR && this.fatalEdges === null) {
1139
+ this.fatalEdges = new GraphFormatError("E_DIRECTED", gen.message, { reason: "mixed direction" });
1140
+ }
1141
+ if ((gen.code === LOSS.POSITIONS || gen.code === LOSS.VIZ) && gen.column !== null) {
1142
+ this.notes.push(
1143
+ Object.freeze({
1144
+ code: gen.code,
1145
+ message: `node column "${gen.column}" is written as a plain property; its role is lost on re-import`,
1146
+ column: gen.column,
1147
+ count: gen.count
1148
+ })
1149
+ );
1150
+ continue;
1151
+ }
1152
+ this.notes.push(gen);
1153
+ }
1154
+ this.notes.push(...this.ownNotes);
1155
+ this.filterNotesByPart();
1156
+ }
1157
+ /**
1158
+ * The direction notes: an undirected snapshot writes every edge as a directed relationship;
1159
+ * a mixed snapshot folds its undirected pairs to one directed relationship each under
1160
+ * "directed" or "undirected" (Neo4j has no undirected relationship, so both policies write
1161
+ * the same file and the generic W_MIXED_DIRECTION note names the policy); a mutual pair is
1162
+ * written as two relationships without its mark.
1163
+ */
1164
+ directionNotes() {
1165
+ const { snapshot, folding } = this;
1166
+ if (!snapshot.directed) {
1167
+ this.note(
1168
+ UNDIRECTED_LOSS,
1169
+ `the snapshot is undirected; every edge is written as a directed relationship (${snapshot.edgeCount} edge(s))`,
1170
+ null,
1171
+ snapshot.edgeCount
1172
+ );
1173
+ } else if (this.common.onMixedDirection !== "error") {
1174
+ let undirected = 0;
1175
+ for (let e = 0; e < snapshot.edgeCount; e++) {
1176
+ if (!folding.folded(e) && !folding.sourceDirected(e)) {
1177
+ undirected++;
1178
+ }
1179
+ }
1180
+ if (undirected > 0) {
1181
+ this.note(
1182
+ UNDIRECTED_LOSS,
1183
+ `${undirected} undirected edge(s) are written as one directed relationship each (source to target, a pair folded to its primary); Neo4j has no undirected relationship`,
1184
+ null,
1185
+ undirected
1186
+ );
1187
+ }
1188
+ }
1189
+ if (folding.mutualCount > 0) {
1190
+ this.note(
1191
+ LOSS.MUTUAL_EXPANDED,
1192
+ `${folding.mutualCount} mutual pair(s) are written as two directed relationships; the mutual mark is lost`,
1193
+ null,
1194
+ folding.mutualCount
1195
+ );
1196
+ }
1197
+ }
1198
+ /**
1199
+ * Record a note.
1200
+ * @param code - the code
1201
+ * @param message - the message
1202
+ * @param column - the column, or null
1203
+ * @param count - the count, or null
1204
+ */
1205
+ note(code, message, column = null, count = null) {
1206
+ this.ownNotes.push(Object.freeze({ code, message, column, count }));
1207
+ }
1208
+ /**
1209
+ * Plan the property columns of one table.
1210
+ * @param domain - node or edge
1211
+ * @param table - the table
1212
+ * @param out - receives the plans in declaration order
1213
+ */
1214
+ planColumns(domain, table, out) {
1215
+ const all = [...table];
1216
+ const names = new Set(all.map((column) => column.meta.name));
1217
+ const companionFor = /* @__PURE__ */ new Map();
1218
+ const companions = /* @__PURE__ */ new Set();
1219
+ for (const column of all) {
1220
+ const target = column.meta.extra.for;
1221
+ if (column.dtype === "string" && typeof target === "string" && names.has(target)) {
1222
+ companionFor.set(target, column);
1223
+ companions.add(column);
1224
+ }
1225
+ }
1226
+ for (const column of all) {
1227
+ const { meta } = column;
1228
+ const { role } = meta;
1229
+ if (companions.has(column) || role !== null && SKIPPED_ROLES.has(role)) {
1230
+ continue;
1231
+ }
1232
+ if (domain === "node" && (isStoredId(meta) || column === this.idSpace || column === this.labels)) {
1233
+ continue;
1234
+ }
1235
+ if (domain === "edge" && (role === "id" || column === this.kind)) {
1236
+ continue;
1237
+ }
1238
+ out.push(this.planColumn(column, companionFor.get(meta.name) ?? null));
1239
+ }
1240
+ }
1241
+ /**
1242
+ * Plan one property column: its header type, cell kind and notes.
1243
+ * @param column - the column
1244
+ * @param companion - its text companion, or null
1245
+ * @returns the plan
1246
+ */
1247
+ planColumn(column, companion) {
1248
+ const { meta } = column;
1249
+ const declared = meta.origin?.type ?? null;
1250
+ const mapped = declared === null ? null : mapDeclaredType(NEO4J, declared, "f64");
1251
+ const label = `${meta.domain} column "${meta.name}"`;
1252
+ if (meta.dtype === "list") {
1253
+ const itemDtype = meta.itemDtype ?? "string";
1254
+ const compatible2 = mapped !== null && mapped.list && mapped.itemDtype === itemDtype && mapped.kind !== "long";
1255
+ let scalar2 = compatible2 ? { type: declaredScalar(declared), kind: kindOf(mapped), temporal: mapped.temporal } : defaultScalar(itemDtype);
1256
+ if (mapped !== null && mapped.list && mapped.kind === "long" && itemDtype === "f64") {
1257
+ scalar2 = this.integralOrDouble(column, declaredScalar(declared), label);
1258
+ }
1259
+ const itemComponents = meta.itemComponents ?? 1;
1260
+ if (itemComponents > 1) {
1261
+ this.note(
1262
+ LOSS.COMPONENTS,
1263
+ `${label} holds items of ${itemComponents} components; they are flattened into one array`,
1264
+ meta.name,
1265
+ column.length - column.nullCount
1266
+ );
1267
+ }
1268
+ this.checkArrayItems(column, scalar2, label);
1269
+ return {
1270
+ column,
1271
+ header: formatHeaderField(meta.name, `${scalar2.type}[]`, null),
1272
+ scalar: scalar2,
1273
+ list: true,
1274
+ companion
1275
+ };
1276
+ }
1277
+ let scalar;
1278
+ const compatible = mapped !== null && !mapped.list && mapped.dtype === meta.dtype;
1279
+ if (compatible && mapped.kind === "long") {
1280
+ scalar = this.integralOrDouble(column, declared, label);
1281
+ } else if (compatible) {
1282
+ scalar = { type: declared, kind: kindOf(mapped), temporal: mapped.temporal };
1283
+ } else {
1284
+ scalar = defaultScalar(meta.dtype);
1285
+ }
1286
+ const visual = meta.role === "position" || meta.role !== null && isVizRole(meta.role);
1287
+ if (visual && meta.dtype === "json" && scalar.kind !== "point") {
1288
+ this.note(
1289
+ LOSS.JSON,
1290
+ `${label} holds nested values; the format has no nested values`,
1291
+ meta.name,
1292
+ column.length - column.nullCount
1293
+ );
1294
+ } else if (visual && meta.dtype !== "json" && !NEO4J_CAPABILITIES.dtypes.includes(meta.dtype)) {
1295
+ this.note(
1296
+ LOSS.DTYPE,
1297
+ `${label} is ${meta.dtype}; the format cannot keep that dtype`,
1298
+ meta.name,
1299
+ column.length - column.nullCount
1300
+ );
1301
+ }
1302
+ if (meta.components > 1) {
1303
+ if (visual) {
1304
+ this.note(
1305
+ LOSS.COMPONENTS,
1306
+ `${label} has ${meta.components} components; the format has no strides`,
1307
+ meta.name,
1308
+ column.length - column.nullCount
1309
+ );
1310
+ }
1311
+ return {
1312
+ column,
1313
+ header: formatHeaderField(meta.name, `${scalar.type}[]`, null),
1314
+ scalar,
1315
+ list: true,
1316
+ companion
1317
+ };
1318
+ }
1319
+ const untyped = declared === null && meta.origin?.format === NEO4J && meta.dtype === "string";
1320
+ const header = untyped ? meta.name : formatHeaderField(meta.name, scalar.type, null);
1321
+ return { column, header, scalar, list: false, companion };
1322
+ }
1323
+ /**
1324
+ * The plan of an f64 column declared with an integer type: kept when every set value is
1325
+ * integral, written as double (with a note) otherwise.
1326
+ * @param column - the column
1327
+ * @param declared - the declared type text
1328
+ * @param label - the column label for messages
1329
+ * @returns the scalar plan
1330
+ */
1331
+ integralOrDouble(column, declared, label) {
1332
+ let integral = true;
1333
+ if (column.dtype === "f64") {
1334
+ for (let r = 0; r < column.length && integral; r++) {
1335
+ if (column.isSet(r) && !Number.isInteger(column.data[r])) {
1336
+ integral = false;
1337
+ }
1338
+ }
1339
+ } else if (column.dtype === "list" && column.child.dtype === "f64") {
1340
+ const { data } = column.child;
1341
+ for (let i = 0; i < data.length && integral; i++) {
1342
+ if (!Number.isInteger(data[i])) {
1343
+ integral = false;
1344
+ }
1345
+ }
1346
+ }
1347
+ if (integral) {
1348
+ return { type: declared, kind: "integer", temporal: null };
1349
+ }
1350
+ this.note(
1351
+ DECLARED_TYPE_CHANGED_LOSS,
1352
+ `${label} is declared ${declared} but holds non-integral values; written as double`,
1353
+ column.meta.name,
1354
+ null
1355
+ );
1356
+ return { type: "double", kind: "double", temporal: null };
1357
+ }
1358
+ /**
1359
+ * Count the rows of a list column with an item containing the array delimiter.
1360
+ * @param column - the list column
1361
+ * @param scalar - the item plan
1362
+ * @param label - the column label
1363
+ */
1364
+ checkArrayItems(column, scalar, label) {
1365
+ if (column.dtype !== "list" || scalar.kind !== "string" && scalar.kind !== "json" && scalar.kind !== "point") {
1366
+ return;
1367
+ }
1368
+ const { arrayDelimiter } = this.options;
1369
+ let rows = 0;
1370
+ for (let r = 0; r < column.length; r++) {
1371
+ if (!column.isSet(r)) {
1372
+ continue;
1373
+ }
1374
+ for (const item of column.sliceOf(r)) {
1375
+ if (formatScalar(item, scalar, null).includes(arrayDelimiter)) {
1376
+ rows++;
1377
+ break;
1378
+ }
1379
+ }
1380
+ }
1381
+ if (rows > 0) {
1382
+ this.note(
1383
+ ARRAY_DELIMITER_LOSS,
1384
+ `${label}: ${rows} row(s) hold an item containing the array delimiter "${arrayDelimiter}", which Neo4j cannot escape`,
1385
+ column.meta.name,
1386
+ rows
1387
+ );
1388
+ }
1389
+ }
1390
+ /**
1391
+ * The explicit weights (design section 3.7), and the weight-column name check.
1392
+ * @returns the weights, or null when no weights are written
1393
+ */
1394
+ planWeightSource() {
1395
+ const { snapshot } = this;
1396
+ const name = this.options.weightColumn;
1397
+ if (name === null) {
1398
+ return null;
1399
+ }
1400
+ const weights = explicitWeights(snapshot);
1401
+ const taken = this.edgeColumns.find((plan2) => plan2.column.meta.name === name);
1402
+ if (taken !== void 0 && weights.weighted) {
1403
+ this.note(
1404
+ WEIGHT_COLUMN_TAKEN_LOSS,
1405
+ `edge column "${name}" already exists; explicit weights cannot be written under weightColumn "${name}"`,
1406
+ name,
1407
+ null
1408
+ );
1409
+ if (this.fatalEdges === null) {
1410
+ this.fatalEdges = new GraphFormatError(
1411
+ "E_COLUMN_EXISTS",
1412
+ `edge column "${name}" already exists; choose another weightColumn`,
1413
+ { column: name, domain: "edge" }
1414
+ );
1415
+ }
1416
+ } else if (taken !== void 0 && name === DEFAULT_WEIGHT_COLUMN) {
1417
+ this.note(
1418
+ LOSS.WEIGHT_KEY_CLASH,
1419
+ `edge column "${name}" is written under the property the importer reads THE weight from (weightFrom "${name}"); it reads back as the weight, not as a column`,
1420
+ name,
1421
+ taken.column.length - taken.column.nullCount
1422
+ );
1423
+ }
1424
+ return weights.weighted ? weights : null;
1425
+ }
1426
+ /** Check the node ids' text forms: type changes under the canonical rule and collisions. */
1427
+ checkIds() {
1428
+ const { ids } = this.snapshot;
1429
+ let typeChanges = 0;
1430
+ let collisions = 0;
1431
+ switch (ids.kind) {
1432
+ case "identity":
1433
+ case "dense":
1434
+ break;
1435
+ case "numeric":
1436
+ for (let i = 0; i < ids.size; i++) {
1437
+ if (!Number.isSafeInteger(ids.idOf(i))) {
1438
+ typeChanges++;
1439
+ }
1440
+ }
1441
+ break;
1442
+ case "string":
1443
+ for (let i = 0; i < ids.size; i++) {
1444
+ if (isCanonicalIntegerText(String(ids.idOf(i)))) {
1445
+ typeChanges++;
1446
+ }
1447
+ }
1448
+ break;
1449
+ case "mixed": {
1450
+ const seen = /* @__PURE__ */ new Set();
1451
+ for (let i = 0; i < ids.size; i++) {
1452
+ const id = ids.idOf(i);
1453
+ const text = String(id);
1454
+ if (typeof id === "number" ? !Number.isSafeInteger(id) : isCanonicalIntegerText(text)) {
1455
+ typeChanges++;
1456
+ }
1457
+ if (seen.has(text)) {
1458
+ collisions++;
1459
+ } else {
1460
+ seen.add(text);
1461
+ }
1462
+ }
1463
+ break;
1464
+ }
1465
+ default: {
1466
+ const name = ids.kind;
1467
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown id map kind ${name}`, { kind: name });
1468
+ }
1469
+ }
1470
+ if (typeChanges > 0) {
1471
+ this.note(
1472
+ ID_TEXT_TYPE_LOSS,
1473
+ `${typeChanges} node id(s) re-import as another type under the canonical id rule (a string of canonical integer text, or a non-integer number)`,
1474
+ null,
1475
+ typeChanges
1476
+ );
1477
+ }
1478
+ if (collisions > 0) {
1479
+ this.note(
1480
+ ID_TEXT_COLLISION_LOSS,
1481
+ `${collisions} node id(s) share their text with another id (a number and a string); export() will throw`,
1482
+ null,
1483
+ collisions
1484
+ );
1485
+ this.fatalAll = new GraphFormatError(
1486
+ "E_INVALID_ID",
1487
+ `${collisions} node id(s) share their text with another id; Neo4j ids are text`,
1488
+ { reason: "text collision", count: collisions }
1489
+ );
1490
+ }
1491
+ }
1492
+ /** Check that the idColumn option does not name an existing node property. */
1493
+ checkIdColumn() {
1494
+ const name = this.options.idColumn;
1495
+ if (name === null) {
1496
+ return;
1497
+ }
1498
+ const taken = this.nodeColumns.some((plan2) => plan2.column.meta.name === name) || this.idColumns.some((column) => column.meta.name === name);
1499
+ if (!taken) {
1500
+ return;
1501
+ }
1502
+ this.note(
1503
+ ID_COLUMN_TAKEN_LOSS,
1504
+ `node column "${name}" already exists; the :ID column cannot be named idColumn "${name}"`,
1505
+ name,
1506
+ null
1507
+ );
1508
+ if (this.fatalNodes === null) {
1509
+ this.fatalNodes = new GraphFormatError(
1510
+ "E_COLUMN_EXISTS",
1511
+ `node column "${name}" already exists; choose another idColumn`,
1512
+ { column: name, domain: "node" }
1513
+ );
1514
+ }
1515
+ }
1516
+ /** Count the nodes with more than one stored-id column set. */
1517
+ checkStoredIds() {
1518
+ if (this.idColumns.length < 2) {
1519
+ return;
1520
+ }
1521
+ let count = 0;
1522
+ for (let i = 0; i < this.snapshot.nodeCount; i++) {
1523
+ let set = 0;
1524
+ for (const column of this.idColumns) {
1525
+ if (column.isSet(i)) {
1526
+ set++;
1527
+ }
1528
+ }
1529
+ if (set > 1) {
1530
+ count++;
1531
+ }
1532
+ }
1533
+ if (count > 0) {
1534
+ this.note(
1535
+ MULTIPLE_ID_PROPERTIES_LOSS,
1536
+ `${count} node(s) have more than one stored-id column set; only the first in declaration order is written`,
1537
+ null,
1538
+ count
1539
+ );
1540
+ }
1541
+ }
1542
+ /**
1543
+ * Whether a json column is written as Neo4j points (so the generic nested-value note does not apply).
1544
+ * @param name - the column name
1545
+ * @returns true when its plan writes point literals
1546
+ */
1547
+ writtenAsPoint(name) {
1548
+ for (const plan2 of [...this.nodeColumns, ...this.edgeColumns]) {
1549
+ if (plan2.column.meta.name === name && plan2.scalar.kind === "point") {
1550
+ return true;
1551
+ }
1552
+ }
1553
+ return false;
1554
+ }
1555
+ /** Drop the notes about the part that is not written. */
1556
+ filterNotesByPart() {
1557
+ const { part } = this.options;
1558
+ if (part === "all") {
1559
+ return;
1560
+ }
1561
+ const { snapshot } = this;
1562
+ const keep = this.notes.filter((n) => {
1563
+ if (part === "nodes") {
1564
+ if (EDGE_NOTE_CODES.has(n.code)) {
1565
+ return false;
1566
+ }
1567
+ return n.column === null || !snapshot.edges.has(n.column) || snapshot.nodes.has(n.column);
1568
+ }
1569
+ if (NODE_NOTE_CODES.has(n.code)) {
1570
+ return false;
1571
+ }
1572
+ return n.column === null || !snapshot.nodes.has(n.column) || snapshot.edges.has(n.column);
1573
+ });
1574
+ this.notes.length = 0;
1575
+ this.notes.push(...keep);
1576
+ }
1577
+ /**
1578
+ * The error export() throws for the requested part, or null when the export can proceed.
1579
+ * @returns the error
1580
+ */
1581
+ get fatal() {
1582
+ if (this.fatalAll !== null) {
1583
+ return this.fatalAll;
1584
+ }
1585
+ const { part } = this.options;
1586
+ if (part !== "relationships" && this.fatalNodes !== null) {
1587
+ return this.fatalNodes;
1588
+ }
1589
+ return part === "nodes" ? null : this.fatalEdges;
1590
+ }
1591
+ /**
1592
+ * The id space of a node.
1593
+ * @param index - the node index
1594
+ * @returns the space name, or null
1595
+ */
1596
+ spaceOf(index) {
1597
+ const { idSpace } = this;
1598
+ if (idSpace === null || !idSpace.isSet(index)) {
1599
+ return null;
1600
+ }
1601
+ const value = idSpace.value(index);
1602
+ return typeof value === "string" ? value : String(value);
1603
+ }
1604
+ /**
1605
+ * The stored-id column of a node: the first one set in declaration order.
1606
+ * @param index - the node index
1607
+ * @returns the column, or null
1608
+ */
1609
+ storedIdOf(index) {
1610
+ for (const column of this.idColumns) {
1611
+ if (column.isSet(index)) {
1612
+ return column;
1613
+ }
1614
+ }
1615
+ return null;
1616
+ }
1617
+ /**
1618
+ * The node sections, then the relationship sections, as CSV lines.
1619
+ * @yields one line at a time (with its line break)
1620
+ * @returns nothing
1621
+ */
1622
+ *lines() {
1623
+ if (this.fatal !== null) {
1624
+ throw this.fatal;
1625
+ }
1626
+ const { part } = this.options;
1627
+ if (part !== "relationships") {
1628
+ yield* this.nodeLines();
1629
+ }
1630
+ if (part !== "nodes") {
1631
+ yield* this.relationshipLines();
1632
+ }
1633
+ }
1634
+ /**
1635
+ * The node sections.
1636
+ * @yields one line at a time
1637
+ * @returns nothing
1638
+ */
1639
+ *nodeLines() {
1640
+ const { snapshot, labels, nodeColumns } = this;
1641
+ const { ids } = snapshot;
1642
+ const { delimiter } = this.options.syntax;
1643
+ const propertyHeaders = nodeColumns.map((plan2) => plan2.header).join(delimiter);
1644
+ let sections = 0;
1645
+ let sectionSpace = null;
1646
+ let sectionIdName = null;
1647
+ const cells = [];
1648
+ for (let i = 0; i < snapshot.nodeCount; i++) {
1649
+ const space = this.spaceOf(i);
1650
+ const stored = this.storedIdOf(i);
1651
+ const idName = stored === null ? this.options.idColumn ?? "" : stored.meta.name;
1652
+ if (sections === 0 || space !== sectionSpace || idName !== sectionIdName) {
1653
+ sections++;
1654
+ sectionSpace = space;
1655
+ sectionIdName = idName;
1656
+ const header = [formatHeaderField(idName, ID_TYPE, space)];
1657
+ if (labels !== null) {
1658
+ header.push(":LABEL");
1659
+ }
1660
+ if (propertyHeaders.length > 0) {
1661
+ header.push(propertyHeaders);
1662
+ }
1663
+ yield `${header.join(delimiter)}
1664
+ `;
1665
+ }
1666
+ cells.length = 0;
1667
+ cells.push(this.cell(idText(ids.idOf(i))));
1668
+ if (labels !== null) {
1669
+ cells.push(this.cell(this.labelsText(i)));
1670
+ }
1671
+ for (const plan2 of nodeColumns) {
1672
+ cells.push(this.cell(this.valueText(plan2, i)));
1673
+ }
1674
+ yield `${cells.join(delimiter)}
1675
+ `;
1676
+ }
1677
+ if (sections === 0) {
1678
+ const header = [formatHeaderField(this.options.idColumn ?? "", ID_TYPE, null)];
1679
+ if (labels !== null) {
1680
+ header.push(":LABEL");
1681
+ }
1682
+ if (propertyHeaders.length > 0) {
1683
+ header.push(propertyHeaders);
1684
+ }
1685
+ yield `${header.join(delimiter)}
1686
+ `;
1687
+ }
1688
+ }
1689
+ /**
1690
+ * The relationship sections.
1691
+ * @yields one line at a time
1692
+ * @returns nothing
1693
+ */
1694
+ *relationshipLines() {
1695
+ const { snapshot, kind, weights, edgeColumns, folding } = this;
1696
+ const { ids } = snapshot;
1697
+ const { delimiter } = this.options.syntax;
1698
+ const list = snapshot.edgeList();
1699
+ const propertyHeaders = edgeColumns.map((plan2) => plan2.header).join(delimiter);
1700
+ const weightHeader = weights === null ? null : formatHeaderField(this.options.weightColumn ?? "weight", "double", null);
1701
+ let sections = 0;
1702
+ let sectionStart = null;
1703
+ let sectionEnd = null;
1704
+ const cells = [];
1705
+ const headerOf = (startSpace, endSpace) => {
1706
+ const header = [formatHeaderField("", "START_ID", startSpace), formatHeaderField("", "END_ID", endSpace)];
1707
+ if (kind !== null) {
1708
+ header.push(":TYPE");
1709
+ }
1710
+ if (weightHeader !== null) {
1711
+ header.push(weightHeader);
1712
+ }
1713
+ if (propertyHeaders.length > 0) {
1714
+ header.push(propertyHeaders);
1715
+ }
1716
+ return `${header.join(delimiter)}
1717
+ `;
1718
+ };
1719
+ for (let e = 0; e < snapshot.edgeCount; e++) {
1720
+ if (folding.folded(e)) {
1721
+ continue;
1722
+ }
1723
+ const u = list.src[e];
1724
+ const v = list.dst[e];
1725
+ const startSpace = this.spaceOf(u);
1726
+ const endSpace = this.spaceOf(v);
1727
+ if (sections === 0 || startSpace !== sectionStart || endSpace !== sectionEnd) {
1728
+ sections++;
1729
+ sectionStart = startSpace;
1730
+ sectionEnd = endSpace;
1731
+ yield headerOf(startSpace, endSpace);
1732
+ }
1733
+ cells.length = 0;
1734
+ cells.push(this.cell(idText(ids.idOf(u))), this.cell(idText(ids.idOf(v))));
1735
+ if (kind !== null) {
1736
+ cells.push(this.cell(kind.isSet(e) ? textOf(kind, e) : null));
1737
+ }
1738
+ if (weights !== null) {
1739
+ cells.push(this.cell(weights.text(e)));
1740
+ }
1741
+ for (const plan2 of edgeColumns) {
1742
+ cells.push(this.cell(this.valueText(plan2, e)));
1743
+ }
1744
+ yield `${cells.join(delimiter)}
1745
+ `;
1746
+ }
1747
+ if (sections === 0) {
1748
+ yield headerOf(null, null);
1749
+ }
1750
+ }
1751
+ /**
1752
+ * A CSV cell: empty for an unset value, a quoted empty string for a set empty string, the
1753
+ * quoted text otherwise.
1754
+ * @param text - the value text, or null for unset
1755
+ * @returns the cell as written
1756
+ */
1757
+ cell(text) {
1758
+ if (text === null) {
1759
+ return "";
1760
+ }
1761
+ const { quote } = this.options.syntax;
1762
+ if (text.length === 0) {
1763
+ return quote + quote;
1764
+ }
1765
+ return quoteCsvCell(text, this.options.syntax.delimiter);
1766
+ }
1767
+ /**
1768
+ * The `:LABEL` cell of a node.
1769
+ * @param index - the node index
1770
+ * @returns the labels joined by the array delimiter, or null when unset
1771
+ */
1772
+ labelsText(index) {
1773
+ const { labels } = this;
1774
+ if (labels === null || !labels.isSet(index)) {
1775
+ return null;
1776
+ }
1777
+ if (labels.dtype === "list") {
1778
+ return labels.sliceOf(index).map((item) => String(item)).join(this.options.arrayDelimiter);
1779
+ }
1780
+ return textOf(labels, index);
1781
+ }
1782
+ /**
1783
+ * The text of one property cell.
1784
+ * @param plan - the column plan
1785
+ * @param row - the row
1786
+ * @returns the text, or null when unset
1787
+ */
1788
+ valueText(plan2, row) {
1789
+ const { column, scalar, companion } = plan2;
1790
+ if (!column.isSet(row)) {
1791
+ return null;
1792
+ }
1793
+ if (plan2.list) {
1794
+ const items = column.dtype === "list" ? column.sliceOf(row) : column.value(row);
1795
+ const texts = [];
1796
+ for (const item of Array.from(items)) {
1797
+ if (Array.isArray(item) || ArrayBuffer.isView(item)) {
1798
+ for (const component of Array.from(item)) {
1799
+ texts.push(formatScalar(component, scalar, null));
1800
+ }
1801
+ } else {
1802
+ texts.push(formatScalar(item, scalar, null));
1803
+ }
1804
+ }
1805
+ return texts.join(this.options.arrayDelimiter);
1806
+ }
1807
+ const text = companion !== null && companion.isSet(row) ? textOf(companion, row) : null;
1808
+ return formatScalar(column.dtype === "json" ? column.values[row] : column.value(row), scalar, text);
1809
+ }
1810
+ }
1811
+ function isStoredId(meta) {
1812
+ return meta.role === null && meta.dtype === "string" && meta.origin !== null && meta.origin.format === NEO4J && meta.origin.type === ID_TYPE;
1813
+ }
1814
+ function isVizRole(role) {
1815
+ return role === "color" || role === "size" || role === "shape" || role === "thickness";
1816
+ }
1817
+ function declaredScalar(declared) {
1818
+ const trimmed = declared.trim();
1819
+ return trimmed.endsWith("[]") ? trimmed.slice(0, -2) : trimmed;
1820
+ }
1821
+ function kindOf(spec) {
1822
+ switch (spec.kind) {
1823
+ case "boolean":
1824
+ return "boolean";
1825
+ case "integer":
1826
+ case "long":
1827
+ return "integer";
1828
+ case "float":
1829
+ return "float";
1830
+ case "double":
1831
+ return "double";
1832
+ case "string":
1833
+ case "duration":
1834
+ return "string";
1835
+ case "temporal":
1836
+ return "temporal";
1837
+ case "point":
1838
+ return "point";
1839
+ case "json":
1840
+ return "json";
1841
+ default: {
1842
+ const name = spec.kind;
1843
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown value kind ${name}`, { kind: name });
1844
+ }
1845
+ }
1846
+ }
1847
+ function defaultScalar(dtype) {
1848
+ switch (dtype) {
1849
+ case "f32":
1850
+ return { type: "float", kind: "float", temporal: null };
1851
+ case "f64":
1852
+ return { type: "double", kind: "double", temporal: null };
1853
+ case "i32":
1854
+ case "u8":
1855
+ return { type: "int", kind: "integer", temporal: null };
1856
+ case "u32":
1857
+ return { type: "long", kind: "integer", temporal: null };
1858
+ case "bool":
1859
+ return { type: "boolean", kind: "boolean", temporal: null };
1860
+ case "dict":
1861
+ case "string":
1862
+ return { type: "string", kind: "string", temporal: null };
1863
+ case "json":
1864
+ return { type: "string", kind: "json", temporal: null };
1865
+ case "list":
1866
+ return { type: "string", kind: "json", temporal: null };
1867
+ default: {
1868
+ const name = dtype;
1869
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown dtype ${name}`, { dtype: name });
1870
+ }
1871
+ }
1872
+ }
1873
+ function formatScalar(value, scalar, text) {
1874
+ switch (scalar.kind) {
1875
+ case "integer":
1876
+ return typeof value === "number" ? formatInteger(value) : String(value);
1877
+ case "float":
1878
+ return typeof value === "number" ? formatF32(value) : String(value);
1879
+ case "double":
1880
+ return typeof value === "number" ? formatF64(value) : String(value);
1881
+ case "boolean":
1882
+ return value === true ? "true" : "false";
1883
+ case "string":
1884
+ return typeof value === "string" ? value : String(value);
1885
+ case "temporal":
1886
+ if (text !== null) {
1887
+ return text;
1888
+ }
1889
+ return typeof value === "number" && scalar.temporal !== null ? formatTemporal(value, scalar.temporal) : String(value);
1890
+ case "point":
1891
+ return formatPoint(value);
1892
+ case "json":
1893
+ return JSON.stringify(value) ?? "";
1894
+ default: {
1895
+ const name = scalar.kind;
1896
+ throw new GraphFormatError("E_UNSUPPORTED", `unknown cell kind ${name}`, { kind: name });
1897
+ }
1898
+ }
1899
+ }
1900
+ function formatPoint(value) {
1901
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1902
+ return JSON.stringify(value) ?? "";
1903
+ }
1904
+ const parts = [];
1905
+ for (const [key, item] of Object.entries(value)) {
1906
+ if (typeof item === "number") {
1907
+ parts.push(`${key}:${formatF64(item)}`);
1908
+ } else if (typeof item === "string") {
1909
+ parts.push(`${key}:'${item.replace(/'/g, "")}'`);
1910
+ } else {
1911
+ return JSON.stringify(value) ?? "";
1912
+ }
1913
+ }
1914
+ return `{${parts.join(", ")}}`;
1915
+ }
1916
+ function idText(id) {
1917
+ if (typeof id === "string") {
1918
+ return id;
1919
+ }
1920
+ return Number.isInteger(id) ? formatInteger(id) : String(id);
1921
+ }
1922
+ function textOf(column, row) {
1923
+ switch (column.dtype) {
1924
+ case "string":
1925
+ return column.valueAt(row);
1926
+ case "dict":
1927
+ return String(column.value(row));
1928
+ case "list":
1929
+ return column.sliceOf(row).map(String).join(",");
1930
+ case "json":
1931
+ return JSON.stringify(column.values[row]) ?? "";
1932
+ case "bool":
1933
+ return column.value(row) === true ? "true" : "false";
1934
+ default: {
1935
+ const value = column.value(row);
1936
+ return typeof value === "number" ? String(value) : Array.from(value).join(",");
1937
+ }
1938
+ }
1939
+ }
1940
+ const neo4jExporter = Object.freeze({
1941
+ format: NEO4J,
1942
+ capabilities: NEO4J_CAPABILITIES,
1943
+ /**
1944
+ * Pre-flight: what export() would lose.
1945
+ * @param snapshot - the snapshot
1946
+ * @param options - format-specific and common options
1947
+ * @returns the notes
1948
+ */
1949
+ check(snapshot, options) {
1950
+ return Object.freeze([...plan(snapshot, options).notes]);
1951
+ },
1952
+ /**
1953
+ * Write the snapshot as UTF-8 chunks.
1954
+ * @param snapshot - the snapshot
1955
+ * @param options - format-specific and common options
1956
+ * @returns the chunks
1957
+ */
1958
+ export(snapshot, options) {
1959
+ return encodeChunks(plan(snapshot, options).lines());
1960
+ },
1961
+ /**
1962
+ * Write the snapshot as one string.
1963
+ * @param snapshot - the snapshot
1964
+ * @param options - format-specific and common options
1965
+ * @returns the document
1966
+ */
1967
+ exportToString(snapshot, options) {
1968
+ return joinText(plan(snapshot, options).lines());
1969
+ }
1970
+ });
1971
+ function plan(snapshot, options) {
1972
+ return new ExportPlan(snapshot, resolveNeo4jExportOptions(options), resolveExportOptions(options));
1973
+ }
1974
+ const NEO4J_ISSUE = Object.freeze({
1975
+ /** The input holds invalid UTF-8 (fatal). */
1976
+ INVALID_UTF8: INVALID_UTF8_CODE,
1977
+ /** An unterminated quoted field (fatal). */
1978
+ CSV_UNCLOSED_QUOTE: UNCLOSED_QUOTE_CODE,
1979
+ /** Text after a closing quote (fatal). */
1980
+ CSV_QUOTE: BAD_QUOTE_CODE,
1981
+ /** No header or a malformed header (fatal). */
1982
+ HEADER: HEADER_CODE,
1983
+ /** A row with a different field count than the header. */
1984
+ COLUMN_COUNT: COLUMN_COUNT_CODE,
1985
+ /** A node row without an id. */
1986
+ MISSING_ID: MISSING_ID_CODE,
1987
+ /** A relationship row without a start or end id. */
1988
+ MISSING_ENDPOINT: MISSING_ENDPOINT_CODE,
1989
+ /** A node id repeated in one id space (last write wins). */
1990
+ DUPLICATE_NODE: DUPLICATE_NODE_CODE,
1991
+ /** A node id declared in two id spaces. */
1992
+ ID_SPACE_COLLISION: ID_SPACE_COLLISION_CODE,
1993
+ /** Two id texts merged into one number under ids "number". */
1994
+ ID_MERGED: ID_MERGED_CODE,
1995
+ /** A header brace option the importer does not apply. */
1996
+ HEADER_OPTION_IGNORED: HEADER_OPTION_CODE,
1997
+ /** A declared type the format does not define (kept as string). */
1998
+ UNKNOWN_ATTR_TYPE: UNKNOWN_ATTR_TYPE_CODE,
1999
+ /** A long value beyond 2^53 rounded. */
2000
+ PRECISION: PRECISION_CODE,
2001
+ /** A column renamed `<name>#<id>` because the name was taken (design section 5.6). */
2002
+ COLUMN_RENAMED: COLUMN_RENAMED_CODE,
2003
+ /** A role the caller's sink already holds. */
2004
+ ROLE_TAKEN: ROLE_TAKEN_CODE,
2005
+ /** A common option the importer has no use for (nodeIdFrom, defaultDirected, ...). */
2006
+ OPTION_IGNORED: OPTION_IGNORED_CODE,
2007
+ /** A builder-policy option the sink does not honour. */
2008
+ SINK_OPTION: SINK_OPTION_CODE
2009
+ });
2010
+ const NEO4J_LOSS = Object.freeze({
2011
+ /** `:IGNORE` columns skipped on import. */
2012
+ IGNORED_COLUMNS: IGNORED_COLUMNS_LOSS,
2013
+ /** Undirected edges (an undirected snapshot, the folded pairs of a mixed one) written as directed relationships. */
2014
+ UNDIRECTED_AS_DIRECTED: UNDIRECTED_LOSS,
2015
+ /** A mutual pair written as two directed relationships without its mark. */
2016
+ MUTUAL_EXPANDED: MUTUAL_EXPANDED_CODE,
2017
+ /** A role column Neo4j has no slot for (label, ...) written as a plain property. */
2018
+ ROLE_DROPPED: ROLE_DROPPED_CODE,
2019
+ /** A plain `weight` edge column reads back as THE weight (the importer's weightFrom default). */
2020
+ WEIGHT_KEY_CLASH: WEIGHT_KEY_CLASH_CODE,
2021
+ /** A node id whose text re-imports as another type under the canonical rule. */
2022
+ ID_TEXT_TYPE: ID_TEXT_TYPE_LOSS,
2023
+ /** A number and a string id with the same text; export() throws. */
2024
+ ID_TEXT_COLLISION: ID_TEXT_COLLISION_LOSS,
2025
+ /** The weight column name is taken by an edge column; export() throws. */
2026
+ WEIGHT_COLUMN_TAKEN: WEIGHT_COLUMN_TAKEN_LOSS,
2027
+ /** The id column name is taken by a node column; export() throws. */
2028
+ ID_COLUMN_TAKEN: ID_COLUMN_TAKEN_LOSS,
2029
+ /** Several stored-id properties; one becomes the `:ID` column. */
2030
+ MULTIPLE_ID_PROPERTIES: MULTIPLE_ID_PROPERTIES_LOSS,
2031
+ /** An integer-typed column with non-integral values written as double. */
2032
+ DECLARED_TYPE_CHANGED: DECLARED_TYPE_CHANGED_LOSS,
2033
+ /** A list item containing the array delimiter, which Neo4j cannot escape. */
2034
+ ARRAY_DELIMITER: ARRAY_DELIMITER_LOSS
2035
+ });
2036
+ export {
2037
+ ID_SPACE_COLUMN,
2038
+ LABELS_COLUMN,
2039
+ NEO4J_CAPABILITIES,
2040
+ NEO4J_ISSUE,
2041
+ NEO4J_LOSS,
2042
+ TYPE_COLUMN,
2043
+ neo4jExporter,
2044
+ neo4jImporter
2045
+ };
2046
+ //# sourceMappingURL=neo4j.js.map