@graphty/graph-io 0.3.47 → 0.3.48

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 (460) hide show
  1. package/dist/chunks/{approximations-wWjCRzKj.js → approximations-C2riR4NT.js} +2 -2
  2. package/dist/chunks/{approximations-wWjCRzKj.js.map → approximations-C2riR4NT.js.map} +1 -1
  3. package/dist/chunks/{block-DiGq58c3.js → block-Cu22ewuS.js} +92 -21
  4. package/dist/chunks/block-Cu22ewuS.js.map +1 -0
  5. package/dist/chunks/{choose--jM0yYCo.js → choose-Cdx6Enl2.js} +3 -3
  6. package/dist/chunks/{choose--jM0yYCo.js.map → choose-Cdx6Enl2.js.map} +1 -1
  7. package/dist/chunks/codes-CV_Bhcyi.js +153 -0
  8. package/dist/chunks/codes-CV_Bhcyi.js.map +1 -0
  9. package/dist/chunks/{constants-BKwqelYD.js → constants-D0LqcsLJ.js} +2 -2
  10. package/dist/chunks/{constants-BKwqelYD.js.map → constants-D0LqcsLJ.js.map} +1 -1
  11. package/dist/chunks/context-cgmmOxZ2.js +663 -0
  12. package/dist/chunks/context-cgmmOxZ2.js.map +1 -0
  13. package/dist/chunks/credentials-BiQsnMTb.js +347 -0
  14. package/dist/chunks/credentials-BiQsnMTb.js.map +1 -0
  15. package/dist/chunks/cx-export-BEeF6ymo.js +259 -0
  16. package/dist/chunks/cx-export-BEeF6ymo.js.map +1 -0
  17. package/dist/chunks/descriptor-BJ-W0Jd-.js +137 -0
  18. package/dist/chunks/descriptor-BJ-W0Jd-.js.map +1 -0
  19. package/dist/chunks/descriptor-C-YDhtsn.js +48 -0
  20. package/dist/chunks/descriptor-C-YDhtsn.js.map +1 -0
  21. package/dist/chunks/{emit-CwU8cUmG.js → emit-CD8xnY8s.js} +70 -10
  22. package/dist/chunks/emit-CD8xnY8s.js.map +1 -0
  23. package/dist/chunks/{escape-CgzxiYPg.js → escape-DH2Egt-l.js} +12 -12
  24. package/dist/chunks/{escape-CgzxiYPg.js.map → escape-DH2Egt-l.js.map} +1 -1
  25. package/dist/chunks/{export-BvqAXCKh.js → export-CZ7VRm4I.js} +53 -1443
  26. package/dist/chunks/export-CZ7VRm4I.js.map +1 -0
  27. package/dist/chunks/{exporter-DndFlGSa.js → exporter-BIdaoIgh.js} +7 -6
  28. package/dist/chunks/{exporter-DndFlGSa.js.map → exporter-BIdaoIgh.js.map} +1 -1
  29. package/dist/chunks/{exporter-CJgfhF-l.js → exporter-ChjL5fmP.js} +8 -7
  30. package/dist/chunks/{exporter-CJgfhF-l.js.map → exporter-ChjL5fmP.js.map} +1 -1
  31. package/dist/chunks/{exporter--Mk9gXCJ.js → exporter-Dcw7Ldtq.js} +6 -4
  32. package/dist/chunks/{exporter--Mk9gXCJ.js.map → exporter-Dcw7Ldtq.js.map} +1 -1
  33. package/dist/chunks/exporter-DtVU80Fa.js +1008 -0
  34. package/dist/chunks/exporter-DtVU80Fa.js.map +1 -0
  35. package/dist/chunks/{exporter-CrDWIyiq.js → exporter-bxxjOrDZ.js} +11 -9
  36. package/dist/chunks/{exporter-CrDWIyiq.js.map → exporter-bxxjOrDZ.js.map} +1 -1
  37. package/dist/chunks/format-BMS-1Nxb.js +64 -0
  38. package/dist/chunks/format-BMS-1Nxb.js.map +1 -0
  39. package/dist/chunks/{generic-Dq1ntt9q.js → generic-CP6YD34u.js} +4 -4
  40. package/dist/chunks/{generic-Dq1ntt9q.js.map → generic-CP6YD34u.js.map} +1 -1
  41. package/dist/chunks/{importer-DiZRmOQG.js → importer-B87_fmVx.js} +13 -17
  42. package/dist/chunks/importer-B87_fmVx.js.map +1 -0
  43. package/dist/chunks/{importer-BSvfrX_h.js → importer-Bji2MeWr.js} +18 -1014
  44. package/dist/chunks/importer-Bji2MeWr.js.map +1 -0
  45. package/dist/chunks/{importer-COTK9Tsl.js → importer-CB_zV5_L.js} +14 -11
  46. package/dist/chunks/importer-CB_zV5_L.js.map +1 -0
  47. package/dist/chunks/{importer-e6LGpqHB.js → importer-CVT-JyDE.js} +12 -9
  48. package/dist/chunks/importer-CVT-JyDE.js.map +1 -0
  49. package/dist/chunks/{importer-yh_aBCU6.js → importer-Cyy6UseH.js} +7 -6
  50. package/dist/chunks/{importer-yh_aBCU6.js.map → importer-Cyy6UseH.js.map} +1 -1
  51. package/dist/chunks/{importer-iFYPEKaJ.js → importer-DPZDN40-.js} +14 -10
  52. package/dist/chunks/{importer-iFYPEKaJ.js.map → importer-DPZDN40-.js.map} +1 -1
  53. package/dist/chunks/{importer-CMmeMWlC.js → importer-e3huMZbm.js} +8 -260
  54. package/dist/chunks/importer-e3huMZbm.js.map +1 -0
  55. package/dist/chunks/input-Ccg9I22A.js +1397 -0
  56. package/dist/chunks/input-Ccg9I22A.js.map +1 -0
  57. package/dist/chunks/{json-elements-DJZRZ6jd.js → json-elements-By172SVq.js} +4 -4
  58. package/dist/chunks/{json-elements-DJZRZ6jd.js.map → json-elements-By172SVq.js.map} +1 -1
  59. package/dist/chunks/{lexicon-CT69Kd87.js → lexicon-u2SI5xcx.js} +19 -5
  60. package/dist/chunks/lexicon-u2SI5xcx.js.map +1 -0
  61. package/dist/chunks/{ontology-export-G4MqRdux.js → ontology-export-BwgMyuF0.js} +4 -3
  62. package/dist/chunks/{ontology-export-G4MqRdux.js.map → ontology-export-BwgMyuF0.js.map} +1 -1
  63. package/dist/chunks/{records-CiaWZrFH.js → records-Cgvxz-Vx.js} +2 -2
  64. package/dist/chunks/{records-CiaWZrFH.js.map → records-Cgvxz-Vx.js.map} +1 -1
  65. package/dist/chunks/{format-BpQBx8MZ.js → report-CgdsL9Hb.js} +72 -129
  66. package/dist/chunks/report-CgdsL9Hb.js.map +1 -0
  67. package/dist/chunks/sigv4-LkAzsLAu.js +247 -0
  68. package/dist/chunks/sigv4-LkAzsLAu.js.map +1 -0
  69. package/dist/chunks/{styles-BQnsga7p.js → styles-CaABMoO1.js} +76 -43
  70. package/dist/chunks/styles-CaABMoO1.js.map +1 -0
  71. package/dist/chunks/{styles-D3fRphrO.js → styles-P_gmFAOi.js} +101 -21
  72. package/dist/chunks/styles-P_gmFAOi.js.map +1 -0
  73. package/dist/chunks/{styles-yed-BF5x3_Y5.js → styles-yed-Cs1nmrjn.js} +60 -20
  74. package/dist/chunks/styles-yed-Cs1nmrjn.js.map +1 -0
  75. package/dist/chunks/{text-CzJNFOS9.js → text-BuKQVMz2.js} +2 -2
  76. package/dist/chunks/{text-CzJNFOS9.js.map → text-BuKQVMz2.js.map} +1 -1
  77. package/dist/chunks/{writer-DMKvthQE.js → writer-DvHTl36J.js} +5 -4
  78. package/dist/chunks/writer-DvHTl36J.js.map +1 -0
  79. package/dist/csv.js +7 -5
  80. package/dist/csv.js.map +1 -1
  81. package/dist/cx.js +5 -4
  82. package/dist/cx.js.map +1 -1
  83. package/dist/cx2.js +2 -2
  84. package/dist/cys.js +1 -1
  85. package/dist/dot.js +2 -2
  86. package/dist/gexf.js +7 -5
  87. package/dist/gexf.js.map +1 -1
  88. package/dist/gml.js +16 -13
  89. package/dist/gml.js.map +1 -1
  90. package/dist/graph-io.js +1181 -224
  91. package/dist/graph-io.js.map +1 -1
  92. package/dist/graphml.js +3 -3
  93. package/dist/json.js +1 -1
  94. package/dist/neo4j.js +6 -4
  95. package/dist/neo4j.js.map +1 -1
  96. package/dist/obo.js +1 -1
  97. package/dist/pajek.js +1 -1
  98. package/dist/sources-cypher.d.ts +1 -0
  99. package/dist/sources-cypher.js +1236 -0
  100. package/dist/sources-cypher.js.map +1 -0
  101. package/dist/sources-ndex.d.ts +1 -0
  102. package/dist/sources-ndex.js +278 -0
  103. package/dist/sources-ndex.js.map +1 -0
  104. package/dist/sources-rest.d.ts +1 -0
  105. package/dist/sources-rest.js +1367 -0
  106. package/dist/sources-rest.js.map +1 -0
  107. package/dist/src/common/report.d.ts +6 -3
  108. package/dist/src/common/report.d.ts.map +1 -1
  109. package/dist/src/common/report.js +15 -9
  110. package/dist/src/common/report.js.map +1 -1
  111. package/dist/src/formats/cx/styles.d.ts.map +1 -1
  112. package/dist/src/formats/cx/styles.js +1 -0
  113. package/dist/src/formats/cx/styles.js.map +1 -1
  114. package/dist/src/formats/dot/styles.d.ts.map +1 -1
  115. package/dist/src/formats/dot/styles.js +3 -1
  116. package/dist/src/formats/dot/styles.js.map +1 -1
  117. package/dist/src/formats/gexf/styles.d.ts +29 -12
  118. package/dist/src/formats/gexf/styles.d.ts.map +1 -1
  119. package/dist/src/formats/gexf/styles.js +160 -31
  120. package/dist/src/formats/gexf/styles.js.map +1 -1
  121. package/dist/src/formats/gml/styles.d.ts.map +1 -1
  122. package/dist/src/formats/gml/styles.js +5 -4
  123. package/dist/src/formats/gml/styles.js.map +1 -1
  124. package/dist/src/formats/graphml/styles-plain.d.ts.map +1 -1
  125. package/dist/src/formats/graphml/styles-plain.js +3 -3
  126. package/dist/src/formats/graphml/styles-plain.js.map +1 -1
  127. package/dist/src/formats/graphml/styles-yed.d.ts.map +1 -1
  128. package/dist/src/formats/graphml/styles-yed.js +89 -19
  129. package/dist/src/formats/graphml/styles-yed.js.map +1 -1
  130. package/dist/src/formats/pajek/styles.d.ts.map +1 -1
  131. package/dist/src/formats/pajek/styles.js +1 -0
  132. package/dist/src/formats/pajek/styles.js.map +1 -1
  133. package/dist/src/formats/xgmml/styles.d.ts.map +1 -1
  134. package/dist/src/formats/xgmml/styles.js +1 -0
  135. package/dist/src/formats/xgmml/styles.js.map +1 -1
  136. package/dist/src/index.d.ts +8 -1
  137. package/dist/src/index.d.ts.map +1 -1
  138. package/dist/src/index.js +9 -1
  139. package/dist/src/index.js.map +1 -1
  140. package/dist/src/registry.d.ts +173 -1
  141. package/dist/src/registry.d.ts.map +1 -1
  142. package/dist/src/registry.js +196 -1
  143. package/dist/src/registry.js.map +1 -1
  144. package/dist/src/sources/builtins.d.ts +11 -0
  145. package/dist/src/sources/builtins.d.ts.map +1 -0
  146. package/dist/src/sources/builtins.js +47 -0
  147. package/dist/src/sources/builtins.js.map +1 -0
  148. package/dist/src/sources/codes.d.ts +230 -0
  149. package/dist/src/sources/codes.d.ts.map +1 -0
  150. package/dist/src/sources/codes.js +58 -0
  151. package/dist/src/sources/codes.js.map +1 -0
  152. package/dist/src/sources/context.d.ts +112 -0
  153. package/dist/src/sources/context.d.ts.map +1 -0
  154. package/dist/src/sources/context.js +739 -0
  155. package/dist/src/sources/context.js.map +1 -0
  156. package/dist/src/sources/credentials.d.ts +179 -0
  157. package/dist/src/sources/credentials.d.ts.map +1 -0
  158. package/dist/src/sources/credentials.js +301 -0
  159. package/dist/src/sources/credentials.js.map +1 -0
  160. package/dist/src/sources/cypher/descriptor.d.ts +21 -0
  161. package/dist/src/sources/cypher/descriptor.d.ts.map +1 -0
  162. package/dist/src/sources/cypher/descriptor.js +119 -0
  163. package/dist/src/sources/cypher/descriptor.js.map +1 -0
  164. package/dist/src/sources/cypher/flatten.d.ts +72 -0
  165. package/dist/src/sources/cypher/flatten.d.ts.map +1 -0
  166. package/dist/src/sources/cypher/flatten.js +279 -0
  167. package/dist/src/sources/cypher/flatten.js.map +1 -0
  168. package/dist/src/sources/cypher/index.d.ts +33 -0
  169. package/dist/src/sources/cypher/index.d.ts.map +1 -0
  170. package/dist/src/sources/cypher/index.js +405 -0
  171. package/dist/src/sources/cypher/index.js.map +1 -0
  172. package/dist/src/sources/cypher/params.d.ts +40 -0
  173. package/dist/src/sources/cypher/params.d.ts.map +1 -0
  174. package/dist/src/sources/cypher/params.js +6 -0
  175. package/dist/src/sources/cypher/params.js.map +1 -0
  176. package/dist/src/sources/cypher/profiles.d.ts +44 -0
  177. package/dist/src/sources/cypher/profiles.d.ts.map +1 -0
  178. package/dist/src/sources/cypher/profiles.js +61 -0
  179. package/dist/src/sources/cypher/profiles.js.map +1 -0
  180. package/dist/src/sources/cypher/query-builder.d.ts +106 -0
  181. package/dist/src/sources/cypher/query-builder.d.ts.map +1 -0
  182. package/dist/src/sources/cypher/query-builder.js +236 -0
  183. package/dist/src/sources/cypher/query-builder.js.map +1 -0
  184. package/dist/src/sources/cypher/transports/bolt.d.ts +21 -0
  185. package/dist/src/sources/cypher/transports/bolt.d.ts.map +1 -0
  186. package/dist/src/sources/cypher/transports/bolt.js +185 -0
  187. package/dist/src/sources/cypher/transports/bolt.js.map +1 -0
  188. package/dist/src/sources/cypher/transports/neptune-analytics.d.ts +15 -0
  189. package/dist/src/sources/cypher/transports/neptune-analytics.d.ts.map +1 -0
  190. package/dist/src/sources/cypher/transports/neptune-analytics.js +38 -0
  191. package/dist/src/sources/cypher/transports/neptune-analytics.js.map +1 -0
  192. package/dist/src/sources/cypher/transports/neptune-https.d.ts +31 -0
  193. package/dist/src/sources/cypher/transports/neptune-https.d.ts.map +1 -0
  194. package/dist/src/sources/cypher/transports/neptune-https.js +104 -0
  195. package/dist/src/sources/cypher/transports/neptune-https.js.map +1 -0
  196. package/dist/src/sources/cypher/transports/query-api.d.ts +14 -0
  197. package/dist/src/sources/cypher/transports/query-api.d.ts.map +1 -0
  198. package/dist/src/sources/cypher/transports/query-api.js +80 -0
  199. package/dist/src/sources/cypher/transports/query-api.js.map +1 -0
  200. package/dist/src/sources/cypher/transports/transport.d.ts +78 -0
  201. package/dist/src/sources/cypher/transports/transport.d.ts.map +1 -0
  202. package/dist/src/sources/cypher/transports/transport.js +77 -0
  203. package/dist/src/sources/cypher/transports/transport.js.map +1 -0
  204. package/dist/src/sources/cypher/types-map.d.ts +91 -0
  205. package/dist/src/sources/cypher/types-map.d.ts.map +1 -0
  206. package/dist/src/sources/cypher/types-map.js +306 -0
  207. package/dist/src/sources/cypher/types-map.js.map +1 -0
  208. package/dist/src/sources/define.d.ts +47 -0
  209. package/dist/src/sources/define.d.ts.map +1 -0
  210. package/dist/src/sources/define.js +173 -0
  211. package/dist/src/sources/define.js.map +1 -0
  212. package/dist/src/sources/fail.d.ts +33 -0
  213. package/dist/src/sources/fail.d.ts.map +1 -0
  214. package/dist/src/sources/fail.js +49 -0
  215. package/dist/src/sources/fail.js.map +1 -0
  216. package/dist/src/sources/ids.d.ts +64 -0
  217. package/dist/src/sources/ids.d.ts.map +1 -0
  218. package/dist/src/sources/ids.js +64 -0
  219. package/dist/src/sources/ids.js.map +1 -0
  220. package/dist/src/sources/ndex/client.d.ts +31 -0
  221. package/dist/src/sources/ndex/client.d.ts.map +1 -0
  222. package/dist/src/sources/ndex/client.js +90 -0
  223. package/dist/src/sources/ndex/client.js.map +1 -0
  224. package/dist/src/sources/ndex/descriptor.d.ts +10 -0
  225. package/dist/src/sources/ndex/descriptor.d.ts.map +1 -0
  226. package/dist/src/sources/ndex/descriptor.js +55 -0
  227. package/dist/src/sources/ndex/descriptor.js.map +1 -0
  228. package/dist/src/sources/ndex/expand.d.ts +20 -0
  229. package/dist/src/sources/ndex/expand.d.ts.map +1 -0
  230. package/dist/src/sources/ndex/expand.js +87 -0
  231. package/dist/src/sources/ndex/expand.js.map +1 -0
  232. package/dist/src/sources/ndex/index.d.ts +25 -0
  233. package/dist/src/sources/ndex/index.d.ts.map +1 -0
  234. package/dist/src/sources/ndex/index.js +191 -0
  235. package/dist/src/sources/ndex/index.js.map +1 -0
  236. package/dist/src/sources/ndex/params.d.ts +17 -0
  237. package/dist/src/sources/ndex/params.d.ts.map +1 -0
  238. package/dist/src/sources/ndex/params.js +6 -0
  239. package/dist/src/sources/ndex/params.js.map +1 -0
  240. package/dist/src/sources/ndex/share-url.d.ts +40 -0
  241. package/dist/src/sources/ndex/share-url.d.ts.map +1 -0
  242. package/dist/src/sources/ndex/share-url.js +64 -0
  243. package/dist/src/sources/ndex/share-url.js.map +1 -0
  244. package/dist/src/sources/params.d.ts +28 -0
  245. package/dist/src/sources/params.d.ts.map +1 -0
  246. package/dist/src/sources/params.js +6 -0
  247. package/dist/src/sources/params.js.map +1 -0
  248. package/dist/src/sources/registry.d.ts +118 -0
  249. package/dist/src/sources/registry.d.ts.map +1 -0
  250. package/dist/src/sources/registry.js +193 -0
  251. package/dist/src/sources/registry.js.map +1 -0
  252. package/dist/src/sources/rest/descriptor.d.ts +16 -0
  253. package/dist/src/sources/rest/descriptor.d.ts.map +1 -0
  254. package/dist/src/sources/rest/descriptor.js +71 -0
  255. package/dist/src/sources/rest/descriptor.js.map +1 -0
  256. package/dist/src/sources/rest/index.d.ts +14 -0
  257. package/dist/src/sources/rest/index.d.ts.map +1 -0
  258. package/dist/src/sources/rest/index.js +13 -0
  259. package/dist/src/sources/rest/index.js.map +1 -0
  260. package/dist/src/sources/rest/mapping.d.ts +30 -0
  261. package/dist/src/sources/rest/mapping.d.ts.map +1 -0
  262. package/dist/src/sources/rest/mapping.js +362 -0
  263. package/dist/src/sources/rest/mapping.js.map +1 -0
  264. package/dist/src/sources/rest/openalex.d.ts +27 -0
  265. package/dist/src/sources/rest/openalex.d.ts.map +1 -0
  266. package/dist/src/sources/rest/openalex.js +269 -0
  267. package/dist/src/sources/rest/openalex.js.map +1 -0
  268. package/dist/src/sources/rest/paging.d.ts +53 -0
  269. package/dist/src/sources/rest/paging.d.ts.map +1 -0
  270. package/dist/src/sources/rest/paging.js +138 -0
  271. package/dist/src/sources/rest/paging.js.map +1 -0
  272. package/dist/src/sources/rest/params.d.ts +216 -0
  273. package/dist/src/sources/rest/params.d.ts.map +1 -0
  274. package/dist/src/sources/rest/params.js +7 -0
  275. package/dist/src/sources/rest/params.js.map +1 -0
  276. package/dist/src/sources/rest/records.d.ts +41 -0
  277. package/dist/src/sources/rest/records.d.ts.map +1 -0
  278. package/dist/src/sources/rest/records.js +240 -0
  279. package/dist/src/sources/rest/records.js.map +1 -0
  280. package/dist/src/sources/rest/samples/deps-dev.d.ts +10 -0
  281. package/dist/src/sources/rest/samples/deps-dev.d.ts.map +1 -0
  282. package/dist/src/sources/rest/samples/deps-dev.js +47 -0
  283. package/dist/src/sources/rest/samples/deps-dev.js.map +1 -0
  284. package/dist/src/sources/rest/samples/string.d.ts +9 -0
  285. package/dist/src/sources/rest/samples/string.d.ts.map +1 -0
  286. package/dist/src/sources/rest/samples/string.js +42 -0
  287. package/dist/src/sources/rest/samples/string.js.map +1 -0
  288. package/dist/src/sources/rest/selectors.d.ts +41 -0
  289. package/dist/src/sources/rest/selectors.d.ts.map +1 -0
  290. package/dist/src/sources/rest/selectors.js +81 -0
  291. package/dist/src/sources/rest/selectors.js.map +1 -0
  292. package/dist/src/sources/rest/source.d.ts +67 -0
  293. package/dist/src/sources/rest/source.d.ts.map +1 -0
  294. package/dist/src/sources/rest/source.js +635 -0
  295. package/dist/src/sources/rest/source.js.map +1 -0
  296. package/dist/src/sources/rest/templates.d.ts +38 -0
  297. package/dist/src/sources/rest/templates.d.ts.map +1 -0
  298. package/dist/src/sources/rest/templates.js +125 -0
  299. package/dist/src/sources/rest/templates.js.map +1 -0
  300. package/dist/src/sources/run.d.ts +93 -0
  301. package/dist/src/sources/run.d.ts.map +1 -0
  302. package/dist/src/sources/run.js +673 -0
  303. package/dist/src/sources/run.js.map +1 -0
  304. package/dist/src/sources/sha256.d.ts +12 -0
  305. package/dist/src/sources/sha256.d.ts.map +1 -0
  306. package/dist/src/sources/sha256.js +95 -0
  307. package/dist/src/sources/sha256.js.map +1 -0
  308. package/dist/src/sources/sigv4.d.ts +24 -0
  309. package/dist/src/sources/sigv4.d.ts.map +1 -0
  310. package/dist/src/sources/sigv4.js +182 -0
  311. package/dist/src/sources/sigv4.js.map +1 -0
  312. package/dist/src/sources/testing.d.ts +96 -0
  313. package/dist/src/sources/testing.d.ts.map +1 -0
  314. package/dist/src/sources/testing.js +138 -0
  315. package/dist/src/sources/testing.js.map +1 -0
  316. package/dist/src/sources/types.d.ts +665 -0
  317. package/dist/src/sources/types.d.ts.map +1 -0
  318. package/dist/src/sources/types.js +10 -0
  319. package/dist/src/sources/types.js.map +1 -0
  320. package/dist/src/styles/canon/enums.d.ts.map +1 -1
  321. package/dist/src/styles/canon/enums.js +1 -0
  322. package/dist/src/styles/canon/enums.js.map +1 -1
  323. package/dist/src/styles/cyjs/cascade.d.ts.map +1 -1
  324. package/dist/src/styles/cyjs/cascade.js +68 -2
  325. package/dist/src/styles/cyjs/cascade.js.map +1 -1
  326. package/dist/src/styles/cyjs/defaults-table.d.ts.map +1 -1
  327. package/dist/src/styles/cyjs/defaults-table.js +2 -0
  328. package/dist/src/styles/cyjs/defaults-table.js.map +1 -1
  329. package/dist/src/styles/cyjs/emit.d.ts.map +1 -1
  330. package/dist/src/styles/cyjs/emit.js +18 -7
  331. package/dist/src/styles/cyjs/emit.js.map +1 -1
  332. package/dist/src/styles/cyjs/mappers.d.ts +4 -1
  333. package/dist/src/styles/cyjs/mappers.d.ts.map +1 -1
  334. package/dist/src/styles/cyjs/mappers.js +2 -0
  335. package/dist/src/styles/cyjs/mappers.js.map +1 -1
  336. package/dist/src/styles/cytoscape/emit.d.ts.map +1 -1
  337. package/dist/src/styles/cytoscape/emit.js +59 -7
  338. package/dist/src/styles/cytoscape/emit.js.map +1 -1
  339. package/dist/src/styles/cytoscape/lexicon.d.ts +6 -0
  340. package/dist/src/styles/cytoscape/lexicon.d.ts.map +1 -1
  341. package/dist/src/styles/cytoscape/lexicon.js +26 -2
  342. package/dist/src/styles/cytoscape/lexicon.js.map +1 -1
  343. package/dist/src/styles/cytoscape/resolve.d.ts +4 -1
  344. package/dist/src/styles/cytoscape/resolve.d.ts.map +1 -1
  345. package/dist/src/styles/cytoscape/resolve.js +10 -6
  346. package/dist/src/styles/cytoscape/resolve.js.map +1 -1
  347. package/dist/src/styles/cytoscape/translate.d.ts.map +1 -1
  348. package/dist/src/styles/cytoscape/translate.js +10 -10
  349. package/dist/src/styles/cytoscape/translate.js.map +1 -1
  350. package/dist/src/styles/defaults.d.ts +30 -0
  351. package/dist/src/styles/defaults.d.ts.map +1 -1
  352. package/dist/src/styles/defaults.js +45 -4
  353. package/dist/src/styles/defaults.js.map +1 -1
  354. package/dist/src/styles/io/block.d.ts.map +1 -1
  355. package/dist/src/styles/io/block.js +9 -0
  356. package/dist/src/styles/io/block.js.map +1 -1
  357. package/dist/src/styles/io/writer.d.ts.map +1 -1
  358. package/dist/src/styles/io/writer.js +7 -3
  359. package/dist/src/styles/io/writer.js.map +1 -1
  360. package/dist/src/styles/plan/resolve.d.ts +2 -1
  361. package/dist/src/styles/plan/resolve.d.ts.map +1 -1
  362. package/dist/src/styles/plan/resolve.js +13 -2
  363. package/dist/src/styles/plan/resolve.js.map +1 -1
  364. package/dist/src/styles/types.d.ts +19 -2
  365. package/dist/src/styles/types.d.ts.map +1 -1
  366. package/dist/src/styles/vocabulary.d.ts.map +1 -1
  367. package/dist/src/styles/vocabulary.js +15 -1
  368. package/dist/src/styles/vocabulary.js.map +1 -1
  369. package/dist/src/types.d.ts +19 -5
  370. package/dist/src/types.d.ts.map +1 -1
  371. package/dist/src/types.js.map +1 -1
  372. package/dist/styles.js +11 -11
  373. package/dist/xgmml.js +3 -3
  374. package/docs/samples/people.json +6 -0
  375. package/package.json +29 -4
  376. package/src/common/report.ts +27 -7
  377. package/src/formats/cx/styles.ts +1 -0
  378. package/src/formats/dot/styles.ts +3 -1
  379. package/src/formats/gexf/styles.ts +185 -37
  380. package/src/formats/gml/styles.ts +5 -4
  381. package/src/formats/graphml/styles-plain.ts +3 -9
  382. package/src/formats/graphml/styles-yed.ts +93 -20
  383. package/src/formats/pajek/styles.ts +1 -0
  384. package/src/formats/xgmml/styles.ts +1 -0
  385. package/src/index.ts +90 -0
  386. package/src/registry.ts +326 -1
  387. package/src/sources/builtins.ts +56 -0
  388. package/src/sources/codes.ts +235 -0
  389. package/src/sources/context.ts +893 -0
  390. package/src/sources/credentials.ts +409 -0
  391. package/src/sources/cypher/descriptor.ts +125 -0
  392. package/src/sources/cypher/flatten.ts +323 -0
  393. package/src/sources/cypher/index.ts +498 -0
  394. package/src/sources/cypher/params.ts +41 -0
  395. package/src/sources/cypher/profiles.ts +78 -0
  396. package/src/sources/cypher/query-builder.ts +294 -0
  397. package/src/sources/cypher/transports/bolt.ts +228 -0
  398. package/src/sources/cypher/transports/neptune-analytics.ts +41 -0
  399. package/src/sources/cypher/transports/neptune-https.ts +122 -0
  400. package/src/sources/cypher/transports/query-api.ts +100 -0
  401. package/src/sources/cypher/transports/transport.ts +139 -0
  402. package/src/sources/cypher/types-map.ts +418 -0
  403. package/src/sources/define.ts +195 -0
  404. package/src/sources/fail.ts +70 -0
  405. package/src/sources/ids.ts +74 -0
  406. package/src/sources/ndex/client.ts +102 -0
  407. package/src/sources/ndex/descriptor.ts +64 -0
  408. package/src/sources/ndex/expand.ts +100 -0
  409. package/src/sources/ndex/index.ts +235 -0
  410. package/src/sources/ndex/params.ts +17 -0
  411. package/src/sources/ndex/share-url.ts +84 -0
  412. package/src/sources/params.ts +29 -0
  413. package/src/sources/registry.ts +325 -0
  414. package/src/sources/rest/descriptor.ts +74 -0
  415. package/src/sources/rest/index.ts +22 -0
  416. package/src/sources/rest/mapping.ts +401 -0
  417. package/src/sources/rest/openalex.ts +288 -0
  418. package/src/sources/rest/paging.ts +180 -0
  419. package/src/sources/rest/params.ts +243 -0
  420. package/src/sources/rest/records.ts +289 -0
  421. package/src/sources/rest/samples/deps-dev.ts +49 -0
  422. package/src/sources/rest/samples/string.ts +44 -0
  423. package/src/sources/rest/selectors.ts +91 -0
  424. package/src/sources/rest/source.ts +760 -0
  425. package/src/sources/rest/templates.ts +151 -0
  426. package/src/sources/run.ts +793 -0
  427. package/src/sources/sha256.ts +99 -0
  428. package/src/sources/sigv4.ts +207 -0
  429. package/src/sources/testing.ts +210 -0
  430. package/src/sources/types.ts +741 -0
  431. package/src/styles/canon/enums.ts +1 -0
  432. package/src/styles/cyjs/cascade.ts +81 -2
  433. package/src/styles/cyjs/defaults-table.ts +2 -0
  434. package/src/styles/cyjs/emit.ts +18 -7
  435. package/src/styles/cyjs/mappers.ts +6 -1
  436. package/src/styles/cytoscape/emit.ts +61 -7
  437. package/src/styles/cytoscape/lexicon.ts +28 -2
  438. package/src/styles/cytoscape/resolve.ts +22 -6
  439. package/src/styles/cytoscape/translate.ts +11 -11
  440. package/src/styles/defaults.ts +93 -4
  441. package/src/styles/io/block.ts +10 -0
  442. package/src/styles/io/writer.ts +8 -3
  443. package/src/styles/plan/resolve.ts +14 -2
  444. package/src/styles/types.ts +28 -2
  445. package/src/styles/vocabulary.ts +15 -1
  446. package/src/types.ts +19 -5
  447. package/dist/chunks/block-DiGq58c3.js.map +0 -1
  448. package/dist/chunks/emit-CwU8cUmG.js.map +0 -1
  449. package/dist/chunks/export-BvqAXCKh.js.map +0 -1
  450. package/dist/chunks/format-BpQBx8MZ.js.map +0 -1
  451. package/dist/chunks/importer-BSvfrX_h.js.map +0 -1
  452. package/dist/chunks/importer-CMmeMWlC.js.map +0 -1
  453. package/dist/chunks/importer-COTK9Tsl.js.map +0 -1
  454. package/dist/chunks/importer-DiZRmOQG.js.map +0 -1
  455. package/dist/chunks/importer-e6LGpqHB.js.map +0 -1
  456. package/dist/chunks/lexicon-CT69Kd87.js.map +0 -1
  457. package/dist/chunks/styles-BQnsga7p.js.map +0 -1
  458. package/dist/chunks/styles-D3fRphrO.js.map +0 -1
  459. package/dist/chunks/styles-yed-BF5x3_Y5.js.map +0 -1
  460. package/dist/chunks/writer-DMKvthQE.js.map +0 -1
@@ -0,0 +1,893 @@
1
+ /**
2
+ * The source context (design sections 2.1, 6.2, 7.2 and 9): the guarded `ctx.fetch` every source
3
+ * reads through. It applies the credential only to the origins it is bound to, refuses credentials
4
+ * in clear text and across redirects, retries what HTTP says is safe to retry, paces requests,
5
+ * enforces the caps, counts decoded bytes, times the load out and turns every failure into a coded
6
+ * issue. A source never sees the credential.
7
+ */
8
+
9
+ import { throwIfAborted } from "../common/input.js";
10
+ import { type ImportReportBuilder } from "../common/report.js";
11
+ import { type ImportReport } from "../types.js";
12
+ import { type SourceBlockedCause } from "./codes.js";
13
+ import { applyCredential, originOf } from "./credentials.js";
14
+ import { failOn, warnOn } from "./fail.js";
15
+ import {
16
+ type Credential,
17
+ type LoadFromSourceOptions,
18
+ type SourceContext,
19
+ type SourceCursor,
20
+ type SourceDescriptor,
21
+ type SourceFinish,
22
+ type SourceLimitOptions,
23
+ type SourceLimits,
24
+ type SourceReportInfo,
25
+ type SourceRequestInit,
26
+ type SourceRuntime,
27
+ } from "./types.js";
28
+
29
+ /** Statuses HTTP says a client may retry. */
30
+ const RETRY_STATUSES: ReadonlySet<number> = new Set([429, 502, 503, 504]);
31
+
32
+ /** Statuses whose response has no body by definition. */
33
+ const NULL_BODY_STATUSES: ReadonlySet<number> = new Set([101, 204, 205, 304]);
34
+
35
+ /** Redirect statuses. */
36
+ const REDIRECT_STATUSES: ReadonlySet<number> = new Set([301, 302, 303, 307, 308]);
37
+
38
+ /** Same-origin redirects followed with a credential before giving up. */
39
+ const MAX_REDIRECTS = 5;
40
+
41
+ /**
42
+ * Thrown inside a load when a cap stops it under `onLimit: "stop"`; the runner catches it, keeps
43
+ * what came first and records where to resume. Never leaves graph-io.
44
+ */
45
+ export class LimitStop extends Error {
46
+ /**
47
+ * Create one.
48
+ * @param limit - the cap's option name
49
+ * @param value - the value that reached it
50
+ */
51
+ constructor(
52
+ readonly limit: string,
53
+ readonly value: number,
54
+ ) {
55
+ super(`${limit} reached at ${String(value)}`);
56
+ this.name = "LimitStop";
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Where code is running. Node is checked first, since a test may stub a `document`.
62
+ * @returns the runtime
63
+ */
64
+ export function detectRuntime(): SourceRuntime {
65
+ const g = globalThis as {
66
+ process?: { versions?: { node?: unknown } };
67
+ importScripts?: unknown;
68
+ window?: unknown;
69
+ document?: unknown;
70
+ };
71
+ if (typeof g.process?.versions?.node === "string") {
72
+ return "node";
73
+ }
74
+ if (typeof g.importScripts === "function") {
75
+ return "worker";
76
+ }
77
+ return g.window !== undefined && g.document !== undefined ? "browser" : "other";
78
+ }
79
+
80
+ /**
81
+ * A URL as reports show it: origin and path, never the query string.
82
+ * @param url - the URL
83
+ * @returns origin plus path
84
+ */
85
+ function originPath(url: URL): string {
86
+ return `${originOf(url)}${url.pathname}`;
87
+ }
88
+
89
+ /**
90
+ * Whether a host is this machine.
91
+ * @param hostname - the URL's hostname
92
+ * @returns true for localhost, 127.0.0.0/8 and ::1
93
+ */
94
+ export function isLoopback(hostname: string): boolean {
95
+ const host = hostname.replaceAll(/(?:^\[)|(?:\]$)/g, "").toLowerCase();
96
+ return host === "localhost" || host.endsWith(".localhost") || /^127(\.\d{1,3}){3}$/.test(host) || host === "::1";
97
+ }
98
+
99
+ /**
100
+ * Whether a host is on a local network: loopback, private IPv4 ranges, link-local, unique local
101
+ * IPv6 or an mDNS `.local` name.
102
+ * @param hostname - the URL's hostname
103
+ * @returns true for a local-network host
104
+ */
105
+ export function isLocalNetwork(hostname: string): boolean {
106
+ const host = hostname.replaceAll(/(?:^\[)|(?:\]$)/g, "").toLowerCase();
107
+ return (
108
+ isLoopback(host) ||
109
+ host.endsWith(".local") ||
110
+ /^10(\.\d{1,3}){3}$/.test(host) ||
111
+ /^192\.168(\.\d{1,3}){2}$/.test(host) ||
112
+ /^169\.254(\.\d{1,3}){2}$/.test(host) ||
113
+ /^172\.(1[6-9]|2\d|3[01])(\.\d{1,3}){2}$/.test(host) ||
114
+ /^f[cd][0-9a-f]{2}:/.test(host) ||
115
+ host.startsWith("fe80:")
116
+ );
117
+ }
118
+
119
+ /** Requests in flight and recently sent, per source and origin, for `descriptor.rateLimit`. */
120
+ interface Pacer {
121
+ active: number;
122
+ times: number[];
123
+ readonly waiters: (() => void)[];
124
+ }
125
+
126
+ /** One pacer per source id and origin, shared by every load in this process. */
127
+ const PACERS = new Map<string, Pacer>();
128
+
129
+ /** What the runner reads back from a context after a load. */
130
+ export interface ContextState {
131
+ /** Requests sent. */
132
+ requests: number;
133
+ /** Decoded response bytes read. */
134
+ bytes: number;
135
+ /** What the source passed to finish(). */
136
+ finish: SourceFinish | null;
137
+ }
138
+
139
+ /** What createSourceContext() needs. */
140
+ interface ContextInput {
141
+ /** The source id. */
142
+ readonly id: string;
143
+ /** The source's descriptor, if it has one. */
144
+ readonly descriptor: SourceDescriptor | undefined;
145
+ /** The caller's options. */
146
+ readonly options: LoadFromSourceOptions;
147
+ /** The load's report. */
148
+ readonly report: ImportReportBuilder;
149
+ /** The resolved endpoint. */
150
+ readonly endpoint: URL | null;
151
+ /** The cursor the source sees (version stripped). */
152
+ readonly cursor: SourceCursor | null;
153
+ /** What progress counts: "bytes" for a file source, "items" otherwise. */
154
+ readonly unit: "items" | "bytes";
155
+ /** What the sink has received so far. */
156
+ readonly counts: () => { readonly nodes: number; readonly edges: number };
157
+ /** The runtime to act as; detected when absent (tests set it). */
158
+ readonly runtime?: SourceRuntime | undefined;
159
+ }
160
+
161
+ /** A context and what the runner needs to close it. */
162
+ export interface CreatedContext {
163
+ /** The context handed to the source. */
164
+ readonly ctx: SourceContext;
165
+ /** Counters the runner reads. */
166
+ readonly state: ContextState;
167
+ /**
168
+ * The report's `source` member as it stands.
169
+ * @param next - where to resume
170
+ * @returns the info
171
+ */
172
+ info(next: SourceCursor | null): SourceReportInfo;
173
+ /** Clear the timeout and detach from the caller's signal. */
174
+ dispose(): void;
175
+ }
176
+
177
+ /**
178
+ * Wait, unless the signal aborts first.
179
+ * @param ms - milliseconds
180
+ * @param signal - the signal
181
+ * @returns a promise that settles after the wait
182
+ */
183
+ export function sleep(ms: number, signal: AbortSignal): Promise<void> {
184
+ return new Promise<void>((resolve, reject) => {
185
+ if (signal.aborted) {
186
+ reject(signal.reason as Error);
187
+ return;
188
+ }
189
+ const onAbort = (): void => {
190
+ clearTimeout(timer);
191
+ reject(signal.reason as Error);
192
+ };
193
+ const timer = setTimeout(() => {
194
+ signal.removeEventListener("abort", onAbort);
195
+ resolve();
196
+ }, ms);
197
+ signal.addEventListener("abort", onAbort, { once: true });
198
+ });
199
+ }
200
+
201
+ /**
202
+ * The milliseconds a readable Retry-After header asks for.
203
+ * @param response - the response
204
+ * @returns milliseconds, or null when absent or unreadable
205
+ */
206
+ function retryAfterMs(response: Response): number | null {
207
+ const value = response.headers.get("retry-after");
208
+ if (value === null) {
209
+ return null;
210
+ }
211
+ const seconds = Number(value);
212
+ if (Number.isFinite(seconds)) {
213
+ return Math.max(0, seconds * 1000);
214
+ }
215
+ const date = Date.parse(value);
216
+ return Number.isNaN(date) ? null : Math.max(0, date - Date.now());
217
+ }
218
+
219
+ /**
220
+ * The origins a credential goes to: its own, else the endpoint's.
221
+ * @param credential - the credential, if any
222
+ * @param endpoint - the endpoint, if any
223
+ * @returns the origins
224
+ */
225
+ function boundOrigins(credential: Credential | undefined, endpoint: URL | null): readonly string[] {
226
+ if (credential === undefined) {
227
+ return [];
228
+ }
229
+ if (credential.origins.length > 0) {
230
+ return credential.origins;
231
+ }
232
+ return endpoint === null ? [] : [originOf(endpoint)];
233
+ }
234
+
235
+ /** The retry settings, resolved. */
236
+ interface RetrySettings {
237
+ readonly retries: number;
238
+ readonly baseDelayMs: number;
239
+ readonly maxDelayMs: number;
240
+ }
241
+
242
+ /**
243
+ * The machinery behind one context: the guarded fetch, the caps, pacing, the timeout and the
244
+ * counters.
245
+ */
246
+ class SourceFetcher {
247
+ readonly state: ContextState = { requests: 0, bytes: 0, finish: null };
248
+ readonly signal: AbortSignal;
249
+ readonly runtime: SourceRuntime;
250
+ private readonly controller = new AbortController();
251
+ private readonly fetchImpl: typeof fetch;
252
+ private readonly limitOptions: SourceLimitOptions;
253
+ private readonly retry: RetrySettings;
254
+ private readonly credentialOrigins: readonly string[];
255
+ private readonly warnedLimits = new Set<string>();
256
+ private readonly timer: ReturnType<typeof setTimeout> | undefined;
257
+ private byteTotal: number | undefined;
258
+
259
+ /**
260
+ * Set up one call.
261
+ * @param input - the source, options, report and counters
262
+ */
263
+ constructor(private readonly input: ContextInput) {
264
+ const { options, endpoint } = input;
265
+ this.runtime = input.runtime ?? detectRuntime();
266
+ this.fetchImpl = options.fetch ?? ((...args) => globalThis.fetch(...args));
267
+ this.limitOptions = options.limits ?? {};
268
+ const retry = options.retry === false ? { retries: 0 } : (options.retry ?? {});
269
+ this.retry = {
270
+ retries: retry.retries ?? 3,
271
+ baseDelayMs: retry.baseDelayMs ?? 500,
272
+ maxDelayMs: retry.maxDelayMs ?? 30_000,
273
+ };
274
+ this.credentialOrigins = boundOrigins(options.credential, endpoint);
275
+ // one controller for the caller's signal and the load timeout; its reason is what rejects
276
+ const callerSignal = options.signal;
277
+ if (callerSignal?.aborted === true) {
278
+ this.controller.abort(callerSignal.reason);
279
+ } else {
280
+ callerSignal?.addEventListener("abort", this.onCallerAbort, { once: true });
281
+ }
282
+ const { timeoutMs } = this.limitOptions;
283
+ this.timer =
284
+ timeoutMs === undefined
285
+ ? undefined
286
+ : setTimeout(() => {
287
+ this.timeOut(timeoutMs);
288
+ }, timeoutMs);
289
+ this.signal = this.controller.signal;
290
+ }
291
+
292
+ /** Clear the timeout and detach from the caller's signal. */
293
+ dispose(): void {
294
+ clearTimeout(this.timer);
295
+ this.input.options.signal?.removeEventListener("abort", this.onCallerAbort);
296
+ }
297
+
298
+ /** Forward the caller's abort, with its reason. */
299
+ private readonly onCallerAbort = (): void => {
300
+ this.controller.abort(this.input.options.signal?.reason);
301
+ };
302
+
303
+ /**
304
+ * Abort the load with E_SOURCE_TIMEOUT as the reason.
305
+ * @param timeoutMs - the limit that passed
306
+ */
307
+ private timeOut(timeoutMs: number): void {
308
+ try {
309
+ failOn(this.input.report, "E_SOURCE_TIMEOUT", `the load ran longer than ${String(timeoutMs)} ms`, {
310
+ timeoutMs,
311
+ serverSide: false,
312
+ });
313
+ } catch (err) {
314
+ this.controller.abort(err);
315
+ }
316
+ }
317
+
318
+ /**
319
+ * A cap was reached: fail under "error", else warn once and stop.
320
+ * @param limit - the option name
321
+ * @param value - the value reached
322
+ */
323
+ limitHit(limit: string, value: number): never {
324
+ const { report } = this.input;
325
+ if ((this.limitOptions.onLimit ?? "stop") === "error") {
326
+ failOn(report, "E_SOURCE_LIMIT", `${limit} of ${String(value)} reached`, { limit, value });
327
+ }
328
+ if (!this.warnedLimits.has(limit)) {
329
+ this.warnedLimits.add(limit);
330
+ warnOn(report, "W_SOURCE_LIMIT", `${limit} reached at ${String(value)}; what came first is kept`, {
331
+ limit,
332
+ value,
333
+ });
334
+ }
335
+ throw new LimitStop(limit, value);
336
+ }
337
+
338
+ /**
339
+ * Whether a cap is reached; warns once under "stop", throws under "error".
340
+ * @returns true when the source should stop
341
+ */
342
+ reached(): boolean {
343
+ const { nodes, edges } = this.input.counts();
344
+ const caps: [string, number, number | undefined][] = [
345
+ ["maxNodes", nodes, this.limitOptions.maxNodes],
346
+ ["maxEdges", edges, this.limitOptions.maxEdges],
347
+ ["maxBytes", this.state.bytes, this.limitOptions.maxBytes],
348
+ ["maxRequests", this.state.requests, this.limitOptions.maxRequests],
349
+ ];
350
+ const hit = caps.find(([, value, cap]) => cap !== undefined && value >= cap);
351
+ if (hit === undefined) {
352
+ return false;
353
+ }
354
+ try {
355
+ this.limitHit(hit[0], hit[1]);
356
+ } catch (err) {
357
+ if (!(err instanceof LimitStop)) {
358
+ throw err;
359
+ }
360
+ }
361
+ return true;
362
+ }
363
+
364
+ /**
365
+ * Report progress.
366
+ * @param done - how much is done
367
+ * @param total - the total, when known
368
+ * @param unit - what is counted
369
+ */
370
+ progress(done: number, total?: number, unit: "items" | "bytes" = "items"): void {
371
+ if (unit === "bytes") {
372
+ this.byteTotal = total;
373
+ }
374
+ this.input.options.onProgress?.(done, total, unit);
375
+ }
376
+
377
+ /**
378
+ * Resolve a request URL against the endpoint and refuse what must never be sent.
379
+ * @param target - the source's URL
380
+ * @returns the absolute URL
381
+ */
382
+ private resolve(target: string | URL): URL {
383
+ const { report, endpoint } = this.input;
384
+ let url: URL;
385
+ try {
386
+ url = endpoint === null ? new URL(target) : new URL(target, endpoint);
387
+ } catch {
388
+ return failOn(report, "E_SOURCE_PARAM", "a relative URL needs an endpoint", {
389
+ name: "endpoint",
390
+ reason: "missing",
391
+ });
392
+ }
393
+ if (url.username !== "" || url.password !== "") {
394
+ failOn(report, "E_SOURCE_PARAM", "a URL may not carry a user name or password", {
395
+ name: "endpoint",
396
+ reason: "credentials-in-url",
397
+ });
398
+ }
399
+ const page = (globalThis as { location?: { protocol?: string } }).location;
400
+ const plain = url.protocol === "http:" || url.protocol === "ws:";
401
+ if (this.runtime === "browser" && page?.protocol === "https:" && plain && !isLoopback(url.hostname)) {
402
+ failOn(report, "E_SOURCE_RUNTIME", "an https page cannot reach a plain http endpoint", {
403
+ runtime: this.runtime,
404
+ reason: "insecure-endpoint",
405
+ });
406
+ }
407
+ return url;
408
+ }
409
+
410
+ /**
411
+ * Wait for a slot under the descriptor's rate limit.
412
+ * @param url - the request URL
413
+ * @returns a release function
414
+ */
415
+ private async pace(url: URL): Promise<() => void> {
416
+ const rate = this.input.descriptor?.rateLimit;
417
+ if (rate === undefined) {
418
+ return () => undefined;
419
+ }
420
+ const key = `${this.input.id} ${originOf(url)}`;
421
+ const p = PACERS.get(key) ?? { active: 0, times: [], waiters: [] };
422
+ PACERS.set(key, p);
423
+ for (;;) {
424
+ const now = Date.now();
425
+ p.times = p.times.filter((t) => now - t < rate.perMs);
426
+ if (rate.concurrent !== undefined && p.active >= rate.concurrent) {
427
+ await new Promise<void>((r) => p.waiters.push(r));
428
+ } else if (p.times.length >= rate.requests) {
429
+ await sleep(Math.max(1, rate.perMs - (now - p.times[0])), this.signal);
430
+ } else {
431
+ p.times.push(now);
432
+ p.active++;
433
+ return () => {
434
+ p.active--;
435
+ p.waiters.shift()?.();
436
+ };
437
+ }
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Count the decoded bytes of a body as it is read, against `maxBytes`.
443
+ * @param response - the response
444
+ * @returns the response, with a counted body
445
+ */
446
+ private counted(response: Response): Response {
447
+ if (response.body === null || NULL_BODY_STATUSES.has(response.status)) {
448
+ return response;
449
+ }
450
+ const counter = new TransformStream<Uint8Array, Uint8Array>({
451
+ transform: (chunk, ctrl): void => {
452
+ this.state.bytes += chunk.byteLength;
453
+ const cap = this.limitOptions.maxBytes;
454
+ if (cap !== undefined && this.state.bytes > cap) {
455
+ this.limitHit("maxBytes", this.state.bytes);
456
+ }
457
+ if (this.input.unit === "bytes") {
458
+ this.input.options.onProgress?.(this.state.bytes, this.byteTotal, "bytes");
459
+ }
460
+ ctrl.enqueue(chunk);
461
+ },
462
+ });
463
+ return new Response(response.body.pipeThrough(counter), {
464
+ status: response.status,
465
+ statusText: response.statusText,
466
+ headers: response.headers,
467
+ });
468
+ }
469
+
470
+ /**
471
+ * Raise the coded issue of a status outside 200 to 299.
472
+ * @param response - the response
473
+ * @param url - the request URL
474
+ * @returns never; it throws
475
+ */
476
+ private async statusFailure(response: Response, url: URL): Promise<never> {
477
+ await response.body?.cancel().catch(() => undefined);
478
+ const { report } = this.input;
479
+ const { status } = response;
480
+ const where = originPath(url);
481
+ switch (status) {
482
+ case 401:
483
+ return failOn(report, "E_SOURCE_AUTH", `${where} answered 401`, {
484
+ origin: originOf(url),
485
+ status,
486
+ scheme: response.headers.get("www-authenticate")?.split(/\s/)[0] ?? null,
487
+ serverCode: null,
488
+ });
489
+ case 403:
490
+ return failOn(report, "E_SOURCE_FORBIDDEN", `${where} answered 403`, { status, serverCode: null });
491
+ case 404:
492
+ return failOn(report, "E_SOURCE_NOT_FOUND", `${where} answered 404`, { what: "url", value: where });
493
+ case 429:
494
+ return failOn(report, "E_SOURCE_QUOTA", `${where} answered 429`, {
495
+ retryAfterMs: retryAfterMs(response),
496
+ });
497
+ default:
498
+ return failOn(report, "E_FETCH", `${where} answered ${String(status)}`, { url: where, status });
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Build one request, with the credential when the URL's origin is bound.
504
+ * @param url - the request URL
505
+ * @param init - the source's init
506
+ * @param attach - whether the credential goes with it
507
+ * @returns the request
508
+ */
509
+ private async request(url: URL, init: SourceRequestInit, attach: boolean): Promise<Request> {
510
+ const { options, report } = this.input;
511
+ const headers = new Headers(init.headers);
512
+ if (options.userAgent !== undefined && this.runtime === "node") {
513
+ headers.set("User-Agent", options.userAgent);
514
+ }
515
+ const { retry: _retry, idempotent: _idempotent, allowStatus: _allowStatus, ...requestInit } = init;
516
+ const guarded: RequestInit = {
517
+ cache: "no-store",
518
+ credentials: "omit",
519
+ redirect: this.runtime === "browser" ? "error" : "manual",
520
+ };
521
+ const request = new Request(url, { ...requestInit, headers, signal: this.signal, ...(attach ? guarded : {}) });
522
+ if (!attach || options.credential === undefined) {
523
+ return request;
524
+ }
525
+ const signed = await applyCredential(options.credential, request);
526
+ const after = new URL(signed.url);
527
+ if (originOf(after) !== originOf(url)) {
528
+ failOn(report, "E_SOURCE_REDIRECT", "the credential changed the request's origin", {
529
+ from: originPath(url),
530
+ to: originPath(after),
531
+ });
532
+ }
533
+ return signed;
534
+ }
535
+
536
+ /**
537
+ * Send one request, following same-origin redirects by hand while a credential is attached.
538
+ * @param url - the request URL
539
+ * @param init - the source's init
540
+ * @param hops - redirects followed so far
541
+ * @returns the response
542
+ */
543
+ private async send(url: URL, init: SourceRequestInit, hops: number): Promise<Response> {
544
+ const { options, report } = this.input;
545
+ const attach = options.credential !== undefined && this.credentialOrigins.includes(originOf(url));
546
+ const plain = url.protocol === "http:" || url.protocol === "ws:";
547
+ if (attach && plain && !isLoopback(url.hostname) && options.allowInsecure !== true) {
548
+ failOn(report, "E_SOURCE_PARAM", "a credential is never sent without TLS", {
549
+ name: "endpoint",
550
+ reason: "insecure-endpoint",
551
+ });
552
+ }
553
+ const request = await this.request(url, init, attach);
554
+ this.state.requests++;
555
+ const response = await this.fetchImpl(request);
556
+ if (!attach || !REDIRECT_STATUSES.has(response.status)) {
557
+ return response;
558
+ }
559
+ const location = response.headers.get("location");
560
+ await response.body?.cancel().catch(() => undefined);
561
+ const to = location === null ? null : new URL(location, url);
562
+ if (to === null || originOf(to) !== originOf(url) || hops >= MAX_REDIRECTS) {
563
+ return failOn(report, "E_SOURCE_REDIRECT", "a redirect while a credential was attached", {
564
+ from: originPath(url),
565
+ to: to === null ? null : originPath(to),
566
+ });
567
+ }
568
+ return this.send(to, response.status === 303 ? { ...init, method: "GET", body: null } : init, hops + 1);
569
+ }
570
+
571
+ /**
572
+ * Whether a browser refused a credentialed request because it was redirected (sent with
573
+ * `redirect: "error"`, which fails like a CORS refusal): the same URL once more, WITHOUT the
574
+ * credential, under `redirect: "manual"`, whose answer to a redirect is an opaque redirect.
575
+ * @param url - the refused URL
576
+ * @returns true when the URL answers with a redirect
577
+ */
578
+ private async redirects(url: URL): Promise<boolean> {
579
+ if (this.input.options.retry === false) {
580
+ return false;
581
+ }
582
+ try {
583
+ const response = await this.fetchImpl(url, {
584
+ method: "GET",
585
+ redirect: "manual",
586
+ credentials: "omit",
587
+ cache: "no-store",
588
+ signal: this.signal,
589
+ });
590
+ await response.body?.cancel().catch(() => undefined);
591
+ return response.type === "opaqueredirect";
592
+ } catch {
593
+ throwIfAborted(this.signal);
594
+ return false;
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Why the browser refused a request: one bodyless no-cors GET of the origin's root.
600
+ * @param url - the refused URL
601
+ * @returns the cause
602
+ */
603
+ private async diagnose(url: URL): Promise<SourceBlockedCause> {
604
+ const page = (globalThis as { location?: { hostname?: string } }).location;
605
+ if (page?.hostname !== undefined && !isLocalNetwork(page.hostname) && isLocalNetwork(url.hostname)) {
606
+ return "local-network";
607
+ }
608
+ if (this.input.options.retry === false) {
609
+ return "unknown";
610
+ }
611
+ try {
612
+ await this.fetchImpl(`${originOf(url)}/`, {
613
+ mode: "no-cors",
614
+ method: "GET",
615
+ credentials: "omit",
616
+ cache: "no-store",
617
+ signal: this.signal,
618
+ });
619
+ return "cors";
620
+ } catch {
621
+ throwIfAborted(this.signal);
622
+ return "unreachable";
623
+ }
624
+ }
625
+
626
+ /**
627
+ * A full-jitter backoff delay.
628
+ * @param attempt - the attempt that failed, from 0
629
+ * @returns milliseconds
630
+ */
631
+ private backoff(attempt: number): number {
632
+ const jitter = Math.random(); // NOSONAR(S2245): retry jitter, not a security value
633
+ return jitter * Math.min(this.retry.maxDelayMs, this.retry.baseDelayMs * 2 ** attempt);
634
+ }
635
+
636
+ /**
637
+ * What a network failure becomes: a retry (true), or the coded issue.
638
+ * @param err - what fetch threw
639
+ * @param url - the request URL
640
+ * @param attempt - the attempt, from 0
641
+ * @param tries - the retries allowed
642
+ * @param statuses - the statuses seen so far
643
+ * @param probeRedirect - whether a refused redirect may explain it (a credentialed GET or HEAD)
644
+ * @returns true to retry
645
+ */
646
+ private async networkFailure(
647
+ err: unknown,
648
+ url: URL,
649
+ attempt: number,
650
+ tries: number,
651
+ statuses: (number | null)[],
652
+ probeRedirect: boolean,
653
+ ): Promise<boolean> {
654
+ throwIfAborted(this.signal);
655
+ if (!(err instanceof TypeError)) {
656
+ throw err;
657
+ }
658
+ const { report } = this.input;
659
+ statuses.push(null);
660
+ if (this.runtime === "browser") {
661
+ if (probeRedirect && (await this.redirects(url))) {
662
+ return failOn(report, "E_SOURCE_REDIRECT", "a redirect while a credential was attached", {
663
+ from: originPath(url),
664
+ to: null,
665
+ });
666
+ }
667
+ const cause = await this.diagnose(url);
668
+ // a hidden 429 looks like a CORS refusal, so that one cause is retried once
669
+ if (cause === "cors" && attempt === 0 && tries > 0) {
670
+ await sleep(this.backoff(0), this.signal);
671
+ return true;
672
+ }
673
+ return failOn(report, "E_SOURCE_BLOCKED", `the browser refused ${originPath(url)}`, {
674
+ url: originPath(url),
675
+ cause,
676
+ });
677
+ }
678
+ if (attempt < tries) {
679
+ await sleep(this.backoff(attempt), this.signal);
680
+ return true;
681
+ }
682
+ return failOn(report, "E_FETCH", `${originPath(url)} could not be reached`, {
683
+ url: originPath(url),
684
+ status: null,
685
+ });
686
+ }
687
+
688
+ /**
689
+ * Send one attempt under the request cap and the rate limit.
690
+ * @param url - the request URL
691
+ * @param init - the source's init
692
+ * @param attempt - the attempt, from 0
693
+ * @param tries - the retries allowed
694
+ * @param statuses - the statuses seen so far
695
+ * @returns the response, or null to try again
696
+ */
697
+ private async attempt(
698
+ url: URL,
699
+ init: SourceRequestInit,
700
+ attempt: number,
701
+ tries: number,
702
+ statuses: (number | null)[],
703
+ ): Promise<Response | null> {
704
+ const cap = this.limitOptions.maxRequests;
705
+ if (cap !== undefined && this.state.requests >= cap) {
706
+ this.limitHit("maxRequests", this.state.requests);
707
+ }
708
+ const release = await this.pace(url);
709
+ try {
710
+ return await this.send(url, init, 0);
711
+ } catch (err) {
712
+ const method = (init.method ?? "GET").toUpperCase();
713
+ const credentialed =
714
+ this.input.options.credential !== undefined && this.credentialOrigins.includes(originOf(url));
715
+ await this.networkFailure(
716
+ err,
717
+ url,
718
+ attempt,
719
+ tries,
720
+ statuses,
721
+ credentialed && (method === "GET" || method === "HEAD"),
722
+ );
723
+ return null;
724
+ } finally {
725
+ release();
726
+ }
727
+ }
728
+
729
+ /**
730
+ * The guarded fetch handed to sources.
731
+ * @param target - the URL, relative to the endpoint or absolute
732
+ * @param init - fetch's init and the source options
733
+ * @returns the response
734
+ */
735
+ async fetch(target: string | URL, init: SourceRequestInit = {}): Promise<Response> {
736
+ throwIfAborted(this.signal);
737
+ const url = this.resolve(target);
738
+ const method = (init.method ?? "GET").toUpperCase();
739
+ const idempotent = method === "GET" || method === "HEAD" || init.idempotent === true;
740
+ const tries = init.retry === false || !idempotent ? 0 : this.retry.retries;
741
+ const statuses: (number | null)[] = [];
742
+ for (let attempt = 0; ; attempt++) {
743
+ const response = await this.attempt(url, init, attempt, tries, statuses);
744
+ if (response === null) {
745
+ continue;
746
+ }
747
+ if (!RETRY_STATUSES.has(response.status) || attempt >= tries) {
748
+ return this.settle(response, url, init, attempt, statuses);
749
+ }
750
+ statuses.push(response.status);
751
+ const asked = retryAfterMs(response);
752
+ await response.body?.cancel().catch(() => undefined);
753
+ await sleep(asked === null ? this.backoff(attempt) : Math.min(asked, this.retry.maxDelayMs), this.signal);
754
+ }
755
+ }
756
+
757
+ /**
758
+ * What a fetch returns: a retried success warned, a failing status raised, the body counted.
759
+ * @param response - the response
760
+ * @param url - the request URL
761
+ * @param init - the source's init
762
+ * @param attempt - the attempt, from 0
763
+ * @param statuses - the statuses retried before it
764
+ * @returns the counted response
765
+ */
766
+ private settle(
767
+ response: Response,
768
+ url: URL,
769
+ init: SourceRequestInit,
770
+ attempt: number,
771
+ statuses: readonly (number | null)[],
772
+ ): Response | Promise<never> {
773
+ if (statuses.length > 0 && response.ok) {
774
+ const attempts = attempt + 1;
775
+ warnOn(
776
+ this.input.report,
777
+ "W_SOURCE_RETRIED",
778
+ `${originPath(url)} answered after ${String(attempts)} attempts`,
779
+ {
780
+ attempts,
781
+ statuses,
782
+ },
783
+ );
784
+ }
785
+ if (!response.ok && response.status !== 304 && !(init.allowStatus ?? []).includes(response.status)) {
786
+ return this.statusFailure(response, url);
787
+ }
788
+ return this.counted(response);
789
+ }
790
+
791
+ /**
792
+ * Fetch and parse JSON.
793
+ * @param target - the URL
794
+ * @param init - the request init
795
+ * @returns the parsed body
796
+ */
797
+ async fetchJson<T>(target: string | URL, init: SourceRequestInit = {}): Promise<T> {
798
+ const headers = new Headers(init.headers);
799
+ if (!headers.has("accept")) {
800
+ headers.set("Accept", "application/json");
801
+ }
802
+ const response = await this.fetch(target, { ...init, headers });
803
+ let text: string;
804
+ try {
805
+ text = await response.text();
806
+ } catch (err) {
807
+ throwIfAborted(this.signal);
808
+ throw err;
809
+ }
810
+ try {
811
+ return JSON.parse(text) as T;
812
+ } catch {
813
+ return failOn(this.input.report, "E_SOURCE_RESPONSE", "the response is not JSON", {
814
+ expected: "json",
815
+ contentType: response.headers.get("content-type"),
816
+ });
817
+ }
818
+ }
819
+
820
+ /**
821
+ * The report's `source` member as it stands.
822
+ * @param next - where to resume
823
+ * @returns the info
824
+ */
825
+ info(next: SourceCursor | null): SourceReportInfo {
826
+ const { finish } = this.state;
827
+ const { id, descriptor, endpoint } = this.input;
828
+ return Object.freeze({
829
+ id,
830
+ version: descriptor?.version ?? 1,
831
+ endpoint: endpoint === null ? null : originPath(endpoint),
832
+ requests: this.state.requests,
833
+ bytes: this.state.bytes,
834
+ next,
835
+ ...(finish?.server === undefined ? {} : { server: finish.server }),
836
+ ...(finish?.budget === undefined ? {} : { budget: finish.budget }),
837
+ ...(finish?.validators === undefined ? {} : { validators: finish.validators }),
838
+ });
839
+ }
840
+ }
841
+
842
+ /** The caller's options behind each context, for transports that cannot go through ctx.fetch. */
843
+ const CONTEXT_OPTIONS = new WeakMap<SourceContext, LoadFromSourceOptions>();
844
+
845
+ /**
846
+ * The credential and transport options of a context's load. Internal to graph-io: a transport that
847
+ * hands a credential to a driver (Bolt) reads it here and applies the origin and TLS rules itself.
848
+ * Sources outside graph-io never see a credential.
849
+ * @param ctx - the context
850
+ * @returns the options that matter to a transport
851
+ */
852
+ export function transportOptions(
853
+ ctx: SourceContext,
854
+ ): Pick<LoadFromSourceOptions, "credential" | "allowInsecure" | "userAgent"> {
855
+ const options = CONTEXT_OPTIONS.get(ctx) ?? {};
856
+ return { credential: options.credential, allowInsecure: options.allowInsecure, userAgent: options.userAgent };
857
+ }
858
+
859
+ /**
860
+ * Create the context of one source call.
861
+ * @param input - the source, options, report and counters
862
+ * @returns the context and its controls
863
+ */
864
+ export function createSourceContext(input: ContextInput): CreatedContext {
865
+ const f = new SourceFetcher(input);
866
+ const limits: SourceLimits = Object.freeze({ ...input.options.limits, reached: () => f.reached() });
867
+ const ctx: SourceContext = Object.freeze({
868
+ fetch: (target: string | URL, init?: SourceRequestInit) => f.fetch(target, init),
869
+ fetchJson: <T>(target: string | URL, init?: SourceRequestInit) => f.fetchJson<T>(target, init),
870
+ endpoint: input.endpoint,
871
+ signal: f.signal,
872
+ report: input.report,
873
+ limits,
874
+ cursor: input.cursor,
875
+ runtime: f.runtime,
876
+ progress: (done: number, total?: number, unit?: "items" | "bytes") => {
877
+ f.progress(done, total, unit);
878
+ },
879
+ finish: (result?: SourceFinish): ImportReport => {
880
+ f.state.finish = result ?? {};
881
+ return Object.freeze({ ...input.report.finish(), source: f.info(result?.next ?? null) });
882
+ },
883
+ });
884
+ CONTEXT_OPTIONS.set(ctx, input.options);
885
+ return {
886
+ ctx,
887
+ state: f.state,
888
+ info: (next) => f.info(next),
889
+ dispose: () => {
890
+ f.dispose();
891
+ },
892
+ };
893
+ }