@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,760 @@
1
+ /**
2
+ * Running a REST mapping (design section 13.2): `restSource(mapping, info)` turns one into a
3
+ * `records` source with `expand` (breadth-first, batched `{ids}`, depth 0 hydrates), `probe` and
4
+ * `validate`; the generic `rest` source takes the mapping as a parameter. Every request goes through
5
+ * `ctx.fetch`, so the credential, retries, caps and coded errors are the framework's.
6
+ */
7
+
8
+ import { type GraphSink } from "@graphty/graph-format";
9
+
10
+ import { throwIfAborted } from "../../common/input.js";
11
+ import { ImportError, type ImportReport } from "../../types.js";
12
+ import { sleep } from "../context.js";
13
+ import { originOf } from "../credentials.js";
14
+ import { defineSource } from "../define.js";
15
+ import { failAlone, failOn, warnOn } from "../fail.js";
16
+ import { REST_SOURCE_ID } from "../ids.js";
17
+ import {
18
+ type ExpandRequest,
19
+ type GraphSource,
20
+ type RecordBatch,
21
+ type SourceContext,
22
+ type SourceCursor,
23
+ type SourceDescriptor,
24
+ type SourceFinish,
25
+ type SourceFreshness,
26
+ type SourceProbe,
27
+ type SourceValidators,
28
+ } from "../types.js";
29
+ import { REST_DESCRIPTOR } from "./descriptor.js";
30
+ import { checkRestMapping } from "./mapping.js";
31
+ import { firstPageUrl, nextPosition, type PagePosition, pageUrl } from "./paging.js";
32
+ import { type RestMapping, type RestParams, type RestRequest } from "./params.js";
33
+ import { newState, type PageRecords, type RecordState, toRecords } from "./records.js";
34
+ import { selectRecords } from "./selectors.js";
35
+ import { buildRequest, type TemplateValues } from "./templates.js";
36
+
37
+ /** What a call runs: the mapping and the values its templates use. */
38
+ export interface RestPlan {
39
+ /** The mapping. */
40
+ readonly mapping: RestMapping;
41
+ /** The template values. */
42
+ readonly values: TemplateValues;
43
+ }
44
+
45
+ /** Why a plan is asked for: the values may differ (a validation selects only what it compares). */
46
+ export type RestPurpose = "load" | "expand" | "probe" | "validate";
47
+
48
+ /** Turns a call's params into the plan it runs. */
49
+ type RestPlanner<P> = (params: P, ctx: SourceContext, purpose: RestPurpose) => RestPlan;
50
+
51
+ /** Auth failures a later page may answer with, keeping the pages before it. */
52
+ const AUTH_CODES: ReadonlySet<string> = new Set(["E_SOURCE_AUTH", "E_SOURCE_FORBIDDEN"]);
53
+
54
+ /** One page read. */
55
+ interface Page {
56
+ readonly records: unknown[];
57
+ readonly next: PagePosition;
58
+ readonly headers: Headers;
59
+ readonly status: number;
60
+ }
61
+
62
+ /**
63
+ * How long until a budget resets.
64
+ * @param reset - the reset header's number: past 1e9 an epoch in seconds, otherwise seconds from now
65
+ * @returns milliseconds from now, never negative
66
+ */
67
+ function resetDelayMs(reset: number): number {
68
+ return Math.max(0, reset > 1e9 ? reset * 1000 - Date.now() : reset * 1000);
69
+ }
70
+
71
+ /** One call's running state. */
72
+ class RestRun {
73
+ readonly endpoint: URL;
74
+ readonly facts: { budget?: SourceFinish["budget"]; validators?: SourceValidators } = {};
75
+ private last = 0;
76
+
77
+ /**
78
+ * Create one.
79
+ * @param plan - the mapping and values
80
+ * @param ctx - the context
81
+ * @param paced - whether this run paces itself (the generic source, whose descriptor cannot)
82
+ */
83
+ constructor(
84
+ readonly plan: RestPlan,
85
+ readonly ctx: SourceContext,
86
+ private readonly paced: boolean,
87
+ ) {
88
+ this.endpoint = new URL(plan.mapping.endpoint);
89
+ if (ctx.endpoint !== null && originOf(ctx.endpoint) !== this.endpoint.origin) {
90
+ failOn(ctx.report, "E_SOURCE_PARAM", "the endpoint is not the mapping's", {
91
+ name: "endpoint",
92
+ reason: "origin",
93
+ });
94
+ }
95
+ }
96
+
97
+ /** Wait out the mapping's own rate limit (the generic source only). */
98
+ private async pace(): Promise<void> {
99
+ const rate = this.plan.mapping.rateLimit;
100
+ if (!this.paced || rate === undefined) {
101
+ return;
102
+ }
103
+ // ponytail: even spacing, one request at a time; a burst window is the descriptor's pacer
104
+ const wait = this.last + rate.perMs / rate.requests - Date.now();
105
+ if (wait > 0) {
106
+ await sleep(wait, this.ctx.signal);
107
+ }
108
+ this.last = Date.now();
109
+ }
110
+
111
+ /**
112
+ * Record the budget headers of a response.
113
+ * @param headers - the response headers
114
+ */
115
+ private noteBudget(headers: Headers): void {
116
+ const names = this.plan.mapping.budgetHeaders;
117
+ const remaining = names === undefined ? null : headers.get(names.remaining);
118
+ if (names === undefined || remaining === null || !Number.isFinite(Number(remaining))) {
119
+ return;
120
+ }
121
+ const reset = names.reset === undefined ? null : Number(headers.get(names.reset) ?? Number.NaN);
122
+ const resetMs = reset === null || !Number.isFinite(reset) ? undefined : resetDelayMs(reset);
123
+ this.facts.budget = { remaining: Number(remaining), ...(resetMs === undefined ? {} : { resetMs }) };
124
+ }
125
+
126
+ /**
127
+ * Read one page of a request.
128
+ * @param name - the request name
129
+ * @param values - the template values
130
+ * @param position - the page's position, or undefined for the first page
131
+ * @param headers - extra request headers (validation)
132
+ * @returns the page
133
+ */
134
+ async read(
135
+ name: string,
136
+ values: TemplateValues,
137
+ position: PagePosition | undefined,
138
+ headers?: Record<string, string>,
139
+ ): Promise<Page> {
140
+ const request: RestRequest = this.plan.mapping.requests[name];
141
+ const { report } = this.ctx;
142
+ const built = buildRequest(this.endpoint, request, values, report);
143
+ const url =
144
+ position === undefined
145
+ ? firstPageUrl(built.url, request.paging, this.endpoint, report)
146
+ : pageUrl(built.url, request.paging, position, this.endpoint, report);
147
+ await this.pace();
148
+ const response = await this.ctx.fetch(url, {
149
+ ...built.init,
150
+ headers: { accept: "application/json", ...(built.init.headers as Record<string, string>), ...headers },
151
+ allowStatus: [304],
152
+ });
153
+ this.noteBudget(response.headers);
154
+ if (response.status === 304) {
155
+ return { records: [], next: null, headers: response.headers, status: 304 };
156
+ }
157
+ const text = await response.text();
158
+ let body: unknown;
159
+ try {
160
+ body = JSON.parse(text) as unknown;
161
+ } catch {
162
+ failOn(report, "E_SOURCE_RESPONSE", "the response is not JSON", {
163
+ expected: "json",
164
+ contentType: response.headers.get("content-type"),
165
+ });
166
+ }
167
+ const records = selectRecords(body, request.records);
168
+ const here = position ?? null;
169
+ const next = nextPosition(
170
+ request.paging,
171
+ position ?? (request.paging?.style === "offset" ? (request.paging.base ?? 0) : here),
172
+ { body, headers: response.headers, url, records: records.length, runtime: this.ctx.runtime },
173
+ this.endpoint,
174
+ report,
175
+ );
176
+ return { records, next, headers: response.headers, status: response.status };
177
+ }
178
+
179
+ /**
180
+ * Record what the facts say so far, so the report carries them even when a later page fails.
181
+ * @param state - the load's record state
182
+ * @param next - where to resume, for an expansion
183
+ * @returns the report so far
184
+ */
185
+ finish(state: RecordState | null, next: SourceCursor | null = null): ImportReport {
186
+ if (state?.modified !== null && state?.modified !== undefined) {
187
+ this.facts.validators = { ...this.facts.validators, modified: state.modified };
188
+ }
189
+ return this.ctx.finish({ ...this.facts, next });
190
+ }
191
+
192
+ /**
193
+ * Note the HTTP validators of a load's first response.
194
+ * @param headers - its headers
195
+ */
196
+ noteValidators(headers: Headers): void {
197
+ const etag = headers.get("etag");
198
+ const lastModified = headers.get("last-modified");
199
+ this.facts.validators = {
200
+ ...this.facts.validators,
201
+ ...(etag === null ? {} : { etag }),
202
+ ...(lastModified === null ? {} : { lastModified }),
203
+ };
204
+ }
205
+
206
+ /**
207
+ * The graph facts of a first page.
208
+ * @returns the meta
209
+ */
210
+ meta(): RecordBatch["meta"] {
211
+ const { directed } = this.plan.mapping;
212
+ return directed === undefined ? undefined : { directed };
213
+ }
214
+ }
215
+
216
+ /**
217
+ * The template values of params: every string, number and boolean, as text.
218
+ * @param params - the params
219
+ * @param skip - a key to leave out
220
+ * @returns the values
221
+ */
222
+ function stringValues(params: Readonly<Record<string, unknown>>, skip?: string): TemplateValues {
223
+ const out: Record<string, string> = {};
224
+ for (const [k, v] of Object.entries(params)) {
225
+ if (k !== skip && (typeof v === "string" || typeof v === "number" || typeof v === "boolean")) {
226
+ out[k] = String(v);
227
+ }
228
+ }
229
+ return out;
230
+ }
231
+
232
+ /**
233
+ * The load: the seed request once, then the load request page by page.
234
+ * @param run - the call
235
+ * @yields one page of nodes and edges
236
+ */
237
+ async function* loadPages(run: RestRun): AsyncGenerator<RecordBatch> {
238
+ const { mapping, values } = run.plan;
239
+ const { cursor } = run.ctx;
240
+ if (cursor !== null && cursor.r !== mapping.load) {
241
+ failOn(run.ctx.report, "E_SOURCE_PARAM", "the cursor is not this mapping's", {
242
+ name: "cursor",
243
+ reason: "stale-cursor",
244
+ });
245
+ }
246
+ const state = newState();
247
+ const groups: { records: unknown[]; entity: string }[] = [];
248
+ if (cursor === null && mapping.seed !== undefined) {
249
+ const seed = await run.read(mapping.seed, values, undefined);
250
+ groups.push({ records: seed.records, entity: mapping.requests[mapping.seed].entity });
251
+ }
252
+ const { entity } = mapping.requests[mapping.load];
253
+ let position: PagePosition | undefined = cursor === null ? undefined : (cursor.at as PagePosition);
254
+ let first = true;
255
+ for (;;) {
256
+ const page = await run.read(mapping.load, values, position);
257
+ if (first && cursor === null) {
258
+ run.noteValidators(page.headers);
259
+ }
260
+ const out = toRecords(mapping, [...groups.splice(0), { records: page.records, entity }], state);
261
+ run.finish(state);
262
+ const next = page.next === null ? null : { r: mapping.load, at: page.next };
263
+ yield { nodes: out.nodes, edges: out.edges, next, meta: first ? run.meta() : undefined };
264
+ if (page.next === null) {
265
+ return;
266
+ }
267
+ first = false;
268
+ position = page.next;
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Write one page into a sink.
274
+ * @param out - the page
275
+ * @param sink - the sink
276
+ * @param ctx - the context
277
+ * @param types - the relationship types to keep, if any
278
+ */
279
+ function writePage(out: PageRecords, sink: GraphSink, ctx: SourceContext, types?: ReadonlySet<string>): void {
280
+ const { report } = ctx;
281
+ let n = 0;
282
+ for (const { id, ...attrs } of out.nodes) {
283
+ if (n++ % 64 === 0) {
284
+ throwIfAborted(ctx.signal);
285
+ }
286
+ try {
287
+ sink.addNodeRecord(id, attrs);
288
+ report.counts.nodes++;
289
+ } catch (err) {
290
+ report.recordError(err, { element: String(id) });
291
+ report.counts.skippedNodes++;
292
+ }
293
+ }
294
+ for (const { source, target, ...attrs } of out.edges) {
295
+ if (n++ % 64 === 0) {
296
+ throwIfAborted(ctx.signal);
297
+ }
298
+ if (types !== undefined && !types.has(String(attrs.type))) {
299
+ continue;
300
+ }
301
+ try {
302
+ sink.addEdgeRecord(source, target, attrs);
303
+ report.counts.edges++;
304
+ } catch (err) {
305
+ report.recordError(err, { element: `${String(source)} -> ${String(target)}` });
306
+ report.counts.skippedEdges++;
307
+ }
308
+ }
309
+ }
310
+
311
+ /**
312
+ * The requests an expansion runs for a direction; depth 0 hydrates.
313
+ * @param mapping - the mapping
314
+ * @param request - the expansion
315
+ * @returns the request names
316
+ */
317
+ function expandRequests(mapping: RestMapping, request: ExpandRequest<unknown>): string[] {
318
+ if (request.depth === 0) {
319
+ return mapping.hydrate === undefined ? [] : [mapping.hydrate];
320
+ }
321
+ const { expand } = mapping;
322
+ if (typeof expand === "string" || expand === undefined) {
323
+ return expand === undefined ? [] : [expand];
324
+ }
325
+ const direction = request.direction ?? "both";
326
+ const names = [direction !== "out" ? expand.in : undefined, direction !== "in" ? expand.out : undefined];
327
+ return [...new Set(names.filter((n): n is string => n !== undefined))];
328
+ }
329
+
330
+ /**
331
+ * Split ids into a request's batches.
332
+ * @param ids - the ids
333
+ * @param max - the batch size
334
+ * @returns the batches
335
+ */
336
+ function batches(ids: readonly string[], max: number): string[][] {
337
+ const out: string[][] = [];
338
+ for (let i = 0; i < ids.length; i += max) {
339
+ out.push(ids.slice(i, i + max));
340
+ }
341
+ return out;
342
+ }
343
+
344
+ /**
345
+ * Breadth-first expansion: each hop runs the direction's requests with the frontier's ids in
346
+ * `{ids}` batches; the nodes it adds are the next frontier. A capped first hop records where it
347
+ * stopped, so expanding again continues from there.
348
+ * @param run - the call
349
+ * @param request - the seeds and how far to go
350
+ * @param sink - where to write
351
+ * @returns the report
352
+ */
353
+ async function expandInto(run: RestRun, request: ExpandRequest<unknown>, sink: GraphSink): Promise<ImportReport> {
354
+ return new Expansion(run, request, sink).expand();
355
+ }
356
+
357
+ /** Which batch of which request of which hop is being read. */
358
+ interface BatchAt {
359
+ readonly hop: number;
360
+ readonly x: number;
361
+ readonly b: number;
362
+ }
363
+
364
+ /** One expansion's running state. */
365
+ class Expansion {
366
+ private readonly mapping: RestMapping;
367
+ private readonly names: string[];
368
+ private readonly state: RecordState;
369
+ private readonly types: ReadonlySet<string> | undefined;
370
+ private added = 0;
371
+ private wrote = false;
372
+
373
+ /**
374
+ * Check the expansion and set the sink's direction.
375
+ * @param run - the call
376
+ * @param request - the seeds and how far to go
377
+ * @param sink - where to write
378
+ */
379
+ constructor(
380
+ private readonly run: RestRun,
381
+ private readonly request: ExpandRequest<unknown>,
382
+ private readonly sink: GraphSink,
383
+ ) {
384
+ const { ctx } = run;
385
+ this.mapping = run.plan.mapping;
386
+ this.names = expandRequests(this.mapping, request);
387
+ if (this.names.length === 0) {
388
+ failOn(ctx.report, "E_SOURCE_UNSUPPORTED", "the mapping has no request for this expansion", {
389
+ feature: request.depth === 0 ? "hydrate" : "expand",
390
+ });
391
+ }
392
+ if (request.ids.length === 0) {
393
+ failOn(ctx.report, "E_SOURCE_PARAM", "an expansion needs seed ids", { name: "ids", reason: "required" });
394
+ }
395
+ this.state = newState([...request.ids.map(String), ...(request.exclude ?? []).map(String)]);
396
+ this.types = request.relationshipTypes === undefined ? undefined : new Set(request.relationshipTypes);
397
+ if (this.mapping.directed !== undefined && sink.edgeCount === 0) {
398
+ sink.setDirected(this.mapping.directed);
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Run every hop.
404
+ * @returns the report
405
+ */
406
+ async expand(): Promise<ImportReport> {
407
+ let frontier = this.request.ids.map(String);
408
+ const depth = Math.max(1, this.request.depth ?? 1);
409
+ for (let hop = 1; hop <= depth && frontier.length > 0; hop++) {
410
+ const reached: string[] = [];
411
+ const stop = await this.hop(hop, frontier, reached);
412
+ if (stop !== null) {
413
+ return stop;
414
+ }
415
+ frontier = [...new Set(reached)];
416
+ }
417
+ return this.run.finish(this.state, null);
418
+ }
419
+
420
+ /**
421
+ * Run one hop: every request over every batch of the frontier.
422
+ * @param hop - the hop, from 1
423
+ * @param frontier - the ids this hop starts from
424
+ * @param reached - collects the ids this hop adds
425
+ * @returns the report when the expansion stopped, else null
426
+ */
427
+ private async hop(hop: number, frontier: readonly string[], reached: string[]): Promise<ImportReport | null> {
428
+ for (const [x, name] of this.names.entries()) {
429
+ const max = this.mapping.requests[name].batch?.max ?? 1;
430
+ for (const [b, batch] of batches(frontier, max).entries()) {
431
+ if (this.skipped({ hop, x, b })) {
432
+ continue;
433
+ }
434
+ const stop = await this.readBatch({ hop, x, b }, name, batch, frontier, reached);
435
+ if (stop !== null) {
436
+ return stop;
437
+ }
438
+ }
439
+ }
440
+ return null;
441
+ }
442
+
443
+ /**
444
+ * Whether a resumed first hop already read this batch.
445
+ * @param at - the batch
446
+ * @returns true to skip it
447
+ */
448
+ private skipped(at: BatchAt): boolean {
449
+ const { hop, x, b } = at;
450
+ const resume = this.run.ctx.cursor;
451
+ if (hop !== 1 || resume === null) {
452
+ return false;
453
+ }
454
+ const rx = Number(resume.x);
455
+ return x < rx || (x === rx && b < Number(resume.b));
456
+ }
457
+
458
+ /**
459
+ * Where a batch starts: the recorded position for the batch a first hop stopped in.
460
+ * @param at - the batch
461
+ * @returns the position, or undefined for the first page
462
+ */
463
+ private startPosition(at: BatchAt): PagePosition | undefined {
464
+ const { hop, x, b } = at;
465
+ const resume = this.run.ctx.cursor;
466
+ return hop === 1 && resume !== null && x === Number(resume.x) && b === Number(resume.b)
467
+ ? ((resume.at as PagePosition) ?? undefined)
468
+ : undefined;
469
+ }
470
+
471
+ /**
472
+ * Read one batch page by page.
473
+ * @param at - the batch
474
+ * @param name - the request
475
+ * @param batch - its ids
476
+ * @param frontier - the hop's ids
477
+ * @param reached - collects the ids it adds
478
+ * @returns the report when the expansion stopped, else null
479
+ */
480
+ private async readBatch(
481
+ at: BatchAt,
482
+ name: string,
483
+ batch: readonly string[],
484
+ frontier: readonly string[],
485
+ reached: string[],
486
+ ): Promise<ImportReport | null> {
487
+ const r = this.mapping.requests[name];
488
+ const own = { ...this.run.plan.values, id: batch[0], ids: batch.join(r.batch?.join ?? ",") };
489
+ let position = this.startPosition(at);
490
+ for (;;) {
491
+ let page: Page;
492
+ try {
493
+ page = await this.run.read(name, own, position);
494
+ } catch (err) {
495
+ return this.authStop(err, at.hop === 1 ? { x: at.x, b: at.b, at: position ?? null } : null);
496
+ }
497
+ this.writeNew(page, r.entity, reached);
498
+ const stop = this.capStop(at, page, frontier);
499
+ if (stop !== null || page.next === null) {
500
+ return stop;
501
+ }
502
+ position = page.next;
503
+ }
504
+ }
505
+
506
+ /**
507
+ * Keep what was written when a later page needs a credential; rethrow anything else.
508
+ * @param err - what the page threw
509
+ * @param cursor - where to resume
510
+ * @returns the report
511
+ */
512
+ private authStop(err: unknown, cursor: SourceCursor | null): ImportReport {
513
+ if (!this.wrote || !(err instanceof ImportError) || !AUTH_CODES.has(String(err.issue?.code))) {
514
+ throw err;
515
+ }
516
+ const p = err.issue?.params ?? {};
517
+ warnOn(this.run.ctx.report, "W_SOURCE_AUTH", "a later page needs a credential; the pages before it are kept", {
518
+ origin: typeof p.origin === "string" ? p.origin : this.run.endpoint.origin,
519
+ status: typeof p.status === "number" ? p.status : null,
520
+ });
521
+ return this.run.finish(this.state, cursor);
522
+ }
523
+
524
+ /**
525
+ * Write a page and collect the nodes it added.
526
+ * @param page - the page
527
+ * @param entity - its records' entity
528
+ * @param reached - collects the ids it adds
529
+ */
530
+ private writeNew(page: Page, entity: string, reached: string[]): void {
531
+ const { state } = this;
532
+ const before = new Set([...state.full, ...state.stubs]);
533
+ const out = toRecords(this.mapping, [{ records: page.records, entity }], state);
534
+ writePage(out, this.sink, this.run.ctx, this.types);
535
+ this.wrote = true;
536
+ for (const node of out.nodes) {
537
+ const id = String(node.id);
538
+ if (!before.has(id) && !state.known.has(id)) {
539
+ reached.push(id);
540
+ this.added++;
541
+ }
542
+ }
543
+ }
544
+
545
+ /**
546
+ * Stop at the expansion's limit or the load's caps.
547
+ * @param at - the batch
548
+ * @param page - the page just written
549
+ * @param frontier - the hop's ids
550
+ * @returns the report when a cap was reached, else null
551
+ */
552
+ private capStop(at: BatchAt, page: Page, frontier: readonly string[]): ImportReport | null {
553
+ const { hop, x, b } = at;
554
+ const { ctx } = this.run;
555
+ const limited = this.request.limit !== undefined && this.added >= this.request.limit;
556
+ if (limited) {
557
+ warnOn(ctx.report, "W_SOURCE_LIMIT", "the expansion reached its limit", {
558
+ limit: "limit",
559
+ value: this.added,
560
+ });
561
+ }
562
+ if (!limited && !ctx.limits.reached()) {
563
+ return null;
564
+ }
565
+ const more = page.next === null ? null : { x, b, at: page.next };
566
+ // ponytail: only a capped first hop can resume; a deeper hop's frontier is not recorded
567
+ return this.run.finish(
568
+ this.state,
569
+ hop === 1 ? (more ?? nextBatch(x, b, this.names, frontier, this.mapping)) : null,
570
+ );
571
+ }
572
+ }
573
+
574
+ /**
575
+ * Where a first hop continues after a batch that ended on a cap.
576
+ * @param x - the request index
577
+ * @param b - the batch index
578
+ * @param names - the requests
579
+ * @param frontier - the seeds
580
+ * @param mapping - the mapping
581
+ * @returns the cursor, or null when nothing is left
582
+ */
583
+ function nextBatch(
584
+ x: number,
585
+ b: number,
586
+ names: readonly string[],
587
+ frontier: readonly string[],
588
+ mapping: RestMapping,
589
+ ): SourceCursor | null {
590
+ const count = Math.ceil(frontier.length / (mapping.requests[names[x]].batch?.max ?? 1));
591
+ if (b + 1 < count) {
592
+ return { x, b: b + 1, at: null };
593
+ }
594
+ return x + 1 < names.length ? { x: x + 1, b: 0, at: null } : null;
595
+ }
596
+
597
+ /**
598
+ * Whether data loaded earlier changed: the load's first page (with the seed), sent with the
599
+ * recorded HTTP validators; a 304 or an equal ETag is current, else the newest modification time
600
+ * is compared.
601
+ * @param run - the call
602
+ * @param seen - the validators recorded then
603
+ * @returns the freshness
604
+ */
605
+ async function validateRun(run: RestRun, seen: SourceValidators): Promise<SourceFreshness> {
606
+ const { mapping, values } = run.plan;
607
+ const headers: Record<string, string> = {};
608
+ if (seen.etag !== undefined) {
609
+ headers["if-none-match"] = seen.etag;
610
+ }
611
+ if (seen.lastModified !== undefined) {
612
+ headers["if-modified-since"] = seen.lastModified;
613
+ }
614
+ const state = newState();
615
+ const groups: { records: unknown[]; entity: string }[] = [];
616
+ if (mapping.seed !== undefined) {
617
+ const seed = await run.read(mapping.seed, values, undefined);
618
+ groups.push({ records: seed.records, entity: mapping.requests[mapping.seed].entity });
619
+ }
620
+ const page = await run.read(mapping.load, values, undefined, headers);
621
+ if (page.status === 304) {
622
+ return { status: "current", via: seen.etag === undefined ? "modified" : "etag", validators: seen };
623
+ }
624
+ toRecords(mapping, [...groups, { records: page.records, entity: mapping.requests[mapping.load].entity }], state);
625
+ run.noteValidators(page.headers);
626
+ const now: SourceValidators = {
627
+ ...run.facts.validators,
628
+ ...(state.modified === null ? {} : { modified: state.modified }),
629
+ };
630
+ if (seen.etag !== undefined && now.etag !== undefined) {
631
+ return { status: seen.etag === now.etag ? "current" : "changed", via: "etag", validators: now };
632
+ }
633
+ if (seen.modified !== undefined && now.modified !== undefined) {
634
+ return now.modified > seen.modified
635
+ ? { status: "changed", via: "modified", changedAt: now.modified, validators: now }
636
+ : { status: "current", via: "modified", validators: now };
637
+ }
638
+ return { status: "unverifiable", validators: now };
639
+ }
640
+
641
+ /**
642
+ * The methods of a source that runs plans.
643
+ * @param plan - turns params into the plan
644
+ * @param paced - whether the run paces itself
645
+ * @returns records, expand, probe and validate
646
+ */
647
+ export function restMethods<P>(
648
+ plan: RestPlanner<P>,
649
+ paced: boolean,
650
+ ): Pick<Required<GraphSource<P>>, "expand" | "probe" | "validate"> & {
651
+ records: (params: P, ctx: SourceContext) => AsyncIterable<RecordBatch>;
652
+ } {
653
+ return {
654
+ records: (params, ctx) => loadPages(new RestRun(plan(params, ctx, "load"), ctx, paced)),
655
+ expand: (request, sink, ctx) =>
656
+ expandInto(new RestRun(plan(request.params, ctx, "expand"), ctx, paced), request, sink),
657
+ async probe(params, ctx): Promise<SourceProbe> {
658
+ const run = new RestRun(plan(params, ctx, "probe"), ctx, paced);
659
+ const page = await run.read(run.plan.mapping.load, run.plan.values, undefined);
660
+ if (page.records.length === 0 && page.status !== 304) {
661
+ failOn(ctx.report, "E_SOURCE_NOT_FOUND", "the load request found no records", {
662
+ what: "records",
663
+ value: run.plan.mapping.load,
664
+ });
665
+ }
666
+ return { ok: true };
667
+ },
668
+ validate: (params, seen, ctx) => validateRun(new RestRun(plan(params, ctx, "validate"), ctx, paced), seen),
669
+ };
670
+ }
671
+
672
+ /**
673
+ * The plan of a mapping given as data, after it is checked.
674
+ * @param mapping - the mapping
675
+ * @param ctx - the context
676
+ * @returns the mapping, checked
677
+ */
678
+ function checkedMapping(mapping: unknown, ctx: SourceContext): RestMapping {
679
+ const [issue] = checkRestMapping(mapping);
680
+ if (issue !== undefined) {
681
+ failOn(ctx.report, "E_SOURCE_PARAM", `the mapping is invalid at ${issue.at}`, {
682
+ name: "mapping",
683
+ reason: issue.code,
684
+ at: issue.at,
685
+ });
686
+ }
687
+ return mapping as RestMapping;
688
+ }
689
+
690
+ /**
691
+ * What `restSource` needs beside the mapping: the source id and any descriptor fields to set.
692
+ * Experimental.
693
+ * @category Network sources
694
+ */
695
+ export type RestSourceInfo = {
696
+ /** The source id: lowercase letters, digits and dashes. */
697
+ readonly id: string;
698
+ } & Partial<SourceDescriptor>;
699
+
700
+ /**
701
+ * Turn a REST mapping into a source: a `records` source that loads the mapping's `load` request
702
+ * page by page, with `expand` (breadth-first over the `expand` requests, `{ids}` batched; depth 0
703
+ * fetches the seeds' own records through `hydrate`), `probe` and `validate`. The descriptor is
704
+ * derived from the mapping (its endpoint, params, auth and rate limit), and `info` may set any of
705
+ * its fields. A mapping with a problem is refused: E_SOURCE_INVALID with `reason: "mapping"`.
706
+ * Experimental.
707
+ * @param mapping - the mapping
708
+ * @param info - the id and descriptor fields
709
+ * @returns the source
710
+ * @example
711
+ * ```ts
712
+ * const people = restSource(mapping, { id: "people", plainName: "People" });
713
+ * registerSource(people);
714
+ * const { snapshot } = await loadFromSource("people", { team: "core" });
715
+ * ```
716
+ * @category Network sources
717
+ */
718
+ export function restSource(mapping: RestMapping, info: RestSourceInfo): GraphSource<Readonly<Record<string, unknown>>> {
719
+ const [issue] = checkRestMapping(mapping);
720
+ if (issue !== undefined) {
721
+ failAlone("E_SOURCE_INVALID", `the mapping of ${info.id} is invalid at ${issue.at}`, {
722
+ id: info.id,
723
+ reason: "mapping",
724
+ at: issue.at,
725
+ });
726
+ }
727
+ const { id, ...fields } = info;
728
+ const auth = mapping.auth === undefined ? [] : [mapping.auth.kind];
729
+ const descriptor: SourceDescriptor = Object.freeze({
730
+ plainName: id,
731
+ kind: "rest",
732
+ params: mapping.params ?? [],
733
+ defaultEndpoint: mapping.endpoint,
734
+ auth,
735
+ authRequired: auth.length === 0 ? "none" : "optional",
736
+ ...(mapping.rateLimit === undefined ? {} : { rateLimit: mapping.rateLimit }),
737
+ ...fields,
738
+ });
739
+ return defineSource<Readonly<Record<string, unknown>>>({
740
+ id,
741
+ descriptor,
742
+ ...restMethods((params) => ({ mapping, values: stringValues(params) }), false),
743
+ });
744
+ }
745
+
746
+ /**
747
+ * The generic REST mapping source: `params.mapping` says how to read the API, and the other params
748
+ * fill its templates. The mapping is checked before any request (E_SOURCE_PARAM, `name:
749
+ * "mapping"`, `reason` the problem's code, `at` where it is). With a credential, pass the mapping's
750
+ * endpoint as `endpoint`, so the credential is bound to it. Experimental.
751
+ * @category Network sources
752
+ */
753
+ export const restMappingSource: GraphSource<RestParams> = defineSource<RestParams>({
754
+ id: REST_SOURCE_ID,
755
+ descriptor: REST_DESCRIPTOR,
756
+ ...restMethods<RestParams>(
757
+ (params, ctx) => ({ mapping: checkedMapping(params.mapping, ctx), values: stringValues(params, "mapping") }),
758
+ true,
759
+ ),
760
+ });