@thomasfosterau/effect-rdf 0.0.0 → 0.1.0

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 (776) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2250 -1
  3. package/dist/Algebra-CGG4oae3.mjs +433 -0
  4. package/dist/Algebra-CGG4oae3.mjs.map +1 -0
  5. package/dist/Algebra-uGsIEOUA.d.mts +446 -0
  6. package/dist/Ast-CPRZmBvk.mjs +552 -0
  7. package/dist/Ast-CPRZmBvk.mjs.map +1 -0
  8. package/dist/Ast-vcw2W-lf.d.mts +827 -0
  9. package/dist/Bindings-Ck3m7JRC.mjs +249 -0
  10. package/dist/Bindings-Ck3m7JRC.mjs.map +1 -0
  11. package/dist/Bindings-DiKpBd7f.d.mts +153 -0
  12. package/dist/ClientErrors-CpXdFX5u.mjs +128 -0
  13. package/dist/ClientErrors-CpXdFX5u.mjs.map +1 -0
  14. package/dist/ClientErrors-D4IZHUM-.d.mts +101 -0
  15. package/dist/ClientFormats-CXomkXLV.mjs +73 -0
  16. package/dist/ClientFormats-CXomkXLV.mjs.map +1 -0
  17. package/dist/ClientFormats-Dm57ZPwR.d.mts +36 -0
  18. package/dist/Csv2Json-DynEcwCh.mjs +194 -0
  19. package/dist/Csv2Json-DynEcwCh.mjs.map +1 -0
  20. package/dist/Csv2Json-aXNVDoXQ.d.mts +40 -0
  21. package/dist/Csv2Rdf-8maXP6mt.d.mts +41 -0
  22. package/dist/Csv2Rdf-DuSEkaKE.mjs +161 -0
  23. package/dist/Csv2Rdf-DuSEkaKE.mjs.map +1 -0
  24. package/dist/Dataset-BlmlcI7J.mjs +782 -0
  25. package/dist/Dataset-BlmlcI7J.mjs.map +1 -0
  26. package/dist/Dataset-DS3ESOcO.d.mts +426 -0
  27. package/dist/Dataset.d.mts +2 -0
  28. package/dist/Dataset.mjs +1 -0
  29. package/dist/Datatype-CxMpamHC.d.mts +212 -0
  30. package/dist/Datatype-DQvkNkiH.mjs +1001 -0
  31. package/dist/Datatype-DQvkNkiH.mjs.map +1 -0
  32. package/dist/Dialect-8ChFj5EX.d.mts +113 -0
  33. package/dist/Dialect-CzVH38uo.mjs +165 -0
  34. package/dist/Dialect-CzVH38uo.mjs.map +1 -0
  35. package/dist/DocumentLoader-D3lZREnB.mjs +346 -0
  36. package/dist/DocumentLoader-D3lZREnB.mjs.map +1 -0
  37. package/dist/DocumentLoader-ZUCEVrW5.d.mts +178 -0
  38. package/dist/DublinCore-AnwqxRx6.mjs +137 -0
  39. package/dist/DublinCore-AnwqxRx6.mjs.map +1 -0
  40. package/dist/DublinCore-Dq-tVA5d.d.mts +119 -0
  41. package/dist/Engine-CYPB3g4h.d.mts +144 -0
  42. package/dist/Engine-DF8PN7g-.mjs +872 -0
  43. package/dist/Engine-DF8PN7g-.mjs.map +1 -0
  44. package/dist/Errors-CMdV1RhU.mjs +37 -0
  45. package/dist/Errors-CMdV1RhU.mjs.map +1 -0
  46. package/dist/Errors-u2bDjUeM.d.mts +30 -0
  47. package/dist/Evaluator-DEsU4d1o.d.mts +50 -0
  48. package/dist/Evaluator-DOY2-guC.mjs +281 -0
  49. package/dist/Evaluator-DOY2-guC.mjs.map +1 -0
  50. package/dist/Exif-CpqniYQd.d.mts +78 -0
  51. package/dist/Exif-DjkCfEB-.mjs +89 -0
  52. package/dist/Exif-DjkCfEB-.mjs.map +1 -0
  53. package/dist/Expression-BIMMvduQ.mjs +230 -0
  54. package/dist/Expression-BIMMvduQ.mjs.map +1 -0
  55. package/dist/Expression-W8A9cyX7.d.mts +89 -0
  56. package/dist/Extract-Bop85jES.mjs +220 -0
  57. package/dist/Extract-Bop85jES.mjs.map +1 -0
  58. package/dist/Extract-C_pCvuTC.d.mts +88 -0
  59. package/dist/Federation-BcNrozc1.d.mts +52 -0
  60. package/dist/Federation-CHScBkaK.mjs +130 -0
  61. package/dist/Federation-CHScBkaK.mjs.map +1 -0
  62. package/dist/Functions-AvjNcId_.mjs +1346 -0
  63. package/dist/Functions-AvjNcId_.mjs.map +1 -0
  64. package/dist/Functions-OQJZP-F6.d.mts +111 -0
  65. package/dist/Generator-C52mmq01.d.mts +50 -0
  66. package/dist/Generator-D3utCJC7.mjs +205 -0
  67. package/dist/Generator-D3utCJC7.mjs.map +1 -0
  68. package/dist/Grammar-CPlSxGH6.mjs +200 -0
  69. package/dist/Grammar-CPlSxGH6.mjs.map +1 -0
  70. package/dist/Grammar-CRhnRoCu.d.mts +148 -0
  71. package/dist/Grammar-D6jZdJQ3.d.mts +169 -0
  72. package/dist/Grammar-mQ8CgE1M.mjs +258 -0
  73. package/dist/Grammar-mQ8CgE1M.mjs.map +1 -0
  74. package/dist/GraphStore-DhUa4nFO.d.mts +45 -0
  75. package/dist/GraphStore-DvbqwKy1.mjs +212 -0
  76. package/dist/GraphStore-DvbqwKy1.mjs.map +1 -0
  77. package/dist/Iri-BB8Ks-BF.d.mts +84 -0
  78. package/dist/Iri-Cr92gsQV.mjs +232 -0
  79. package/dist/Iri-Cr92gsQV.mjs.map +1 -0
  80. package/dist/Iri.d.mts +2 -0
  81. package/dist/Iri.mjs +1 -0
  82. package/dist/Isomorphism-B6qTY-xy.d.mts +28 -0
  83. package/dist/Isomorphism-BmqxhKyJ.mjs +232 -0
  84. package/dist/Isomorphism-BmqxhKyJ.mjs.map +1 -0
  85. package/dist/Isomorphism-DInKnWIy.mjs +390 -0
  86. package/dist/Isomorphism-DInKnWIy.mjs.map +1 -0
  87. package/dist/Isomorphism-DvSfZXLR.d.mts +37 -0
  88. package/dist/Isomorphism.d.mts +2 -0
  89. package/dist/Isomorphism.mjs +1 -0
  90. package/dist/JsonLd-DOwS2FHg.mjs +2836 -0
  91. package/dist/JsonLd-DOwS2FHg.mjs.map +1 -0
  92. package/dist/JsonLd-DobFrQNa.d.mts +242 -0
  93. package/dist/JsonLdError-C9J-BxfG.mjs +106 -0
  94. package/dist/JsonLdError-C9J-BxfG.mjs.map +1 -0
  95. package/dist/JsonLdError-DSOfHkOy.d.mts +43 -0
  96. package/dist/LanguageTag-BCu8rcmX.d.mts +38 -0
  97. package/dist/LanguageTag-BcOpMGZ4.mjs +89 -0
  98. package/dist/LanguageTag-BcOpMGZ4.mjs.map +1 -0
  99. package/dist/LanguageTag.d.mts +2 -0
  100. package/dist/LanguageTag.mjs +1 -0
  101. package/dist/Lexical-Cg0JHrf6.mjs +243 -0
  102. package/dist/Lexical-Cg0JHrf6.mjs.map +1 -0
  103. package/dist/Lexical-D3zySYVS.d.mts +163 -0
  104. package/dist/Manifest-B6xf6mUc.mjs +148 -0
  105. package/dist/Manifest-B6xf6mUc.mjs.map +1 -0
  106. package/dist/Manifest-DQUMRBFr.d.mts +98 -0
  107. package/dist/Mapping-BgG74x3U.d.mts +63 -0
  108. package/dist/Mapping-Dc1ZGirk.mjs +259 -0
  109. package/dist/Mapping-Dc1ZGirk.mjs.map +1 -0
  110. package/dist/Mcp-1DwTTKOK.mjs +48 -0
  111. package/dist/Mcp-1DwTTKOK.mjs.map +1 -0
  112. package/dist/Mcp-d-pX9erx.d.mts +25 -0
  113. package/dist/Message-CK_DGQuu.mjs +95 -0
  114. package/dist/Message-CK_DGQuu.mjs.map +1 -0
  115. package/dist/Message-fIvTJSXM.d.mts +49 -0
  116. package/dist/Metadata-BbffwGDZ.mjs +445 -0
  117. package/dist/Metadata-BbffwGDZ.mjs.map +1 -0
  118. package/dist/Metadata-CEt3yc1-.d.mts +901 -0
  119. package/dist/Metadata-D4HsQNXr.mjs +1273 -0
  120. package/dist/Metadata-D4HsQNXr.mjs.map +1 -0
  121. package/dist/Metadata-nzS7QKRj.d.mts +350 -0
  122. package/dist/Model-DaMmxurZ.d.mts +293 -0
  123. package/dist/Model-DlPzRYhF.mjs +528 -0
  124. package/dist/Model-DlPzRYhF.mjs.map +1 -0
  125. package/dist/N3-BzyV-KIH.mjs +974 -0
  126. package/dist/N3-BzyV-KIH.mjs.map +1 -0
  127. package/dist/N3-DSLnonDn.d.mts +100 -0
  128. package/dist/NQuads-C1VBOJ3-.mjs +117 -0
  129. package/dist/NQuads-C1VBOJ3-.mjs.map +1 -0
  130. package/dist/NQuads-FjFZikFg.d.mts +76 -0
  131. package/dist/NTriples-D-kLSEAf.mjs +120 -0
  132. package/dist/NTriples-D-kLSEAf.mjs.map +1 -0
  133. package/dist/NTriples-D34OspfW.d.mts +76 -0
  134. package/dist/Namespace-ClTUO_ya.d.mts +42 -0
  135. package/dist/Namespace-DHFIsORd.mjs +75 -0
  136. package/dist/Namespace-DHFIsORd.mjs.map +1 -0
  137. package/dist/Namespace.d.mts +2 -0
  138. package/dist/Namespace.mjs +1 -0
  139. package/dist/Namespaces-Bz85vpjW.mjs +235 -0
  140. package/dist/Namespaces-Bz85vpjW.mjs.map +1 -0
  141. package/dist/Namespaces-C1jW6EDo.d.mts +178 -0
  142. package/dist/Optimizer-BWWQRQPY.mjs +168 -0
  143. package/dist/Optimizer-BWWQRQPY.mjs.map +1 -0
  144. package/dist/Optimizer-DUpUow6k.d.mts +26 -0
  145. package/dist/Options-BLBGL4HE.mjs +72 -0
  146. package/dist/Options-BLBGL4HE.mjs.map +1 -0
  147. package/dist/Options-BommQwP9.d.mts +127 -0
  148. package/dist/Packet-CA76wVX3.mjs +197 -0
  149. package/dist/Packet-CA76wVX3.mjs.map +1 -0
  150. package/dist/Packet-Cy5g6XuW.d.mts +130 -0
  151. package/dist/Parser-p1Gyr0QN.d.mts +85 -0
  152. package/dist/Parser-xARsOWXk.mjs +1317 -0
  153. package/dist/Parser-xARsOWXk.mjs.map +1 -0
  154. package/dist/Path-B20Yp8KE.mjs +284 -0
  155. package/dist/Path-B20Yp8KE.mjs.map +1 -0
  156. package/dist/Path-DRB8e-Zn.d.mts +181 -0
  157. package/dist/Pdf-B9RZn--8.d.mts +77 -0
  158. package/dist/Pdf-CKK6dkZn.mjs +89 -0
  159. package/dist/Pdf-CKK6dkZn.mjs.map +1 -0
  160. package/dist/PdfA-CUjSuntT.d.mts +109 -0
  161. package/dist/PdfA-CleOhuL6.mjs +124 -0
  162. package/dist/PdfA-CleOhuL6.mjs.map +1 -0
  163. package/dist/Photoshop-CiHo80qV.mjs +233 -0
  164. package/dist/Photoshop-CiHo80qV.mjs.map +1 -0
  165. package/dist/Photoshop-udKZ0I3N.d.mts +204 -0
  166. package/dist/PrefixMap-BHFPDLO-.mjs +103 -0
  167. package/dist/PrefixMap-BHFPDLO-.mjs.map +1 -0
  168. package/dist/PrefixMap-BMlXrWNd.d.mts +64 -0
  169. package/dist/PrefixMap.d.mts +2 -0
  170. package/dist/PrefixMap.mjs +1 -0
  171. package/dist/Processor-CMmecFsb.mjs +198 -0
  172. package/dist/Processor-CMmecFsb.mjs.map +1 -0
  173. package/dist/Processor-FJPvO0CB.d.mts +435 -0
  174. package/dist/Protocol-BA7OkNgp.mjs +202 -0
  175. package/dist/Protocol-BA7OkNgp.mjs.map +1 -0
  176. package/dist/Protocol-DlSoiIKB.d.mts +108 -0
  177. package/dist/ProtocolErrors-Ckfejnl6.d.mts +84 -0
  178. package/dist/ProtocolErrors-czDPVcX5.mjs +129 -0
  179. package/dist/ProtocolErrors-czDPVcX5.mjs.map +1 -0
  180. package/dist/Query-D9A6LOpP.mjs +364 -0
  181. package/dist/Query-D9A6LOpP.mjs.map +1 -0
  182. package/dist/Query-KqSkTYyG.d.mts +96 -0
  183. package/dist/QueryBuilder-BLK9Vfbz.mjs +674 -0
  184. package/dist/QueryBuilder-BLK9Vfbz.mjs.map +1 -0
  185. package/dist/QueryBuilder-DUqh4ECe.d.mts +529 -0
  186. package/dist/RdfJs-D6-49XB5.mjs +86 -0
  187. package/dist/RdfJs-D6-49XB5.mjs.map +1 -0
  188. package/dist/RdfJs-DXFqdWrd.d.mts +76 -0
  189. package/dist/RdfJs.d.mts +2 -0
  190. package/dist/RdfJs.mjs +1 -0
  191. package/dist/RdfJson-5aKPDlPS.d.mts +297 -0
  192. package/dist/RdfJson-BNocgW4f.mjs +548 -0
  193. package/dist/RdfJson-BNocgW4f.mjs.map +1 -0
  194. package/dist/RdfXml-DJ9G4gOq.mjs +1014 -0
  195. package/dist/RdfXml-DJ9G4gOq.mjs.map +1 -0
  196. package/dist/RdfXml-_N7yxWEp.d.mts +149 -0
  197. package/dist/Rdfs-BRVPsAjc.d.mts +84 -0
  198. package/dist/Rdfs-CyY2wVHI.mjs +167 -0
  199. package/dist/Rdfs-CyY2wVHI.mjs.map +1 -0
  200. package/dist/Rdfs.d.mts +2 -0
  201. package/dist/Rdfs.mjs +1 -0
  202. package/dist/Reasoner-BxYhx9s7.d.mts +94 -0
  203. package/dist/Reasoner-Cw90zrjv.mjs +798 -0
  204. package/dist/Reasoner-Cw90zrjv.mjs.map +1 -0
  205. package/dist/Reification-BmN4EfnM.d.mts +40 -0
  206. package/dist/Reification-dpqQvjDE.mjs +82 -0
  207. package/dist/Reification-dpqQvjDE.mjs.map +1 -0
  208. package/dist/Reification.d.mts +2 -0
  209. package/dist/Reification.mjs +1 -0
  210. package/dist/Report-XHs8htzK.mjs +121 -0
  211. package/dist/Report-XHs8htzK.mjs.map +1 -0
  212. package/dist/Report-hcXsc_19.d.mts +115 -0
  213. package/dist/Resource-B1cI8FyJ.d.mts +218 -0
  214. package/dist/Resource-sIEeNh15.mjs +374 -0
  215. package/dist/Resource-sIEeNh15.mjs.map +1 -0
  216. package/dist/Resource.d.mts +2 -0
  217. package/dist/Resource.mjs +1 -0
  218. package/dist/Results-BVNEC-Wt.d.mts +277 -0
  219. package/dist/Results-peJjGA2V.mjs +621 -0
  220. package/dist/Results-peJjGA2V.mjs.map +1 -0
  221. package/dist/ResultsStream-BhF0OcNn.mjs +639 -0
  222. package/dist/ResultsStream-BhF0OcNn.mjs.map +1 -0
  223. package/dist/ResultsStream-toYMXrOe.d.mts +100 -0
  224. package/dist/SemAct-CDchtk49.d.mts +96 -0
  225. package/dist/SemAct-CmyT4yI8.mjs +118 -0
  226. package/dist/SemAct-CmyT4yI8.mjs.map +1 -0
  227. package/dist/Serialization-DP9H5bYQ.mjs +514 -0
  228. package/dist/Serialization-DP9H5bYQ.mjs.map +1 -0
  229. package/dist/Serialization-Df8iVauT.d.mts +392 -0
  230. package/dist/Serialization.d.mts +2 -0
  231. package/dist/Serialization.mjs +1 -0
  232. package/dist/ServerFormats-CrRnjjf6.mjs +64 -0
  233. package/dist/ServerFormats-CrRnjjf6.mjs.map +1 -0
  234. package/dist/ServerFormats-DjC9NsO_.d.mts +36 -0
  235. package/dist/ServiceDescription-BERsvg-k.d.mts +54 -0
  236. package/dist/ServiceDescription-Dq5hxeGy.mjs +97 -0
  237. package/dist/ServiceDescription-Dq5hxeGy.mjs.map +1 -0
  238. package/dist/ShExC-Ctf-06CL.d.mts +67 -0
  239. package/dist/ShExC-DJbB19i3.mjs +1181 -0
  240. package/dist/ShExC-DJbB19i3.mjs.map +1 -0
  241. package/dist/ShExError-BeYFD9rv.d.mts +61 -0
  242. package/dist/ShExError-CVtq2NnM.mjs +64 -0
  243. package/dist/ShExError-CVtq2NnM.mjs.map +1 -0
  244. package/dist/ShExJ-C1u_wgyD.d.mts +531 -0
  245. package/dist/ShExJ-COiPd9Xw.mjs +532 -0
  246. package/dist/ShExJ-COiPd9Xw.mjs.map +1 -0
  247. package/dist/ShExR-ByR4mCh-.mjs +480 -0
  248. package/dist/ShExR-ByR4mCh-.mjs.map +1 -0
  249. package/dist/ShExR-CqONNNBz.d.mts +40 -0
  250. package/dist/ShapeMap-C422pxKl.d.mts +140 -0
  251. package/dist/ShapeMap-CsgKxB2G.mjs +181 -0
  252. package/dist/ShapeMap-CsgKxB2G.mjs.map +1 -0
  253. package/dist/Shapes-18GjfyBV.mjs +597 -0
  254. package/dist/Shapes-18GjfyBV.mjs.map +1 -0
  255. package/dist/Shapes-BQFDFc4Q.d.mts +306 -0
  256. package/dist/Sparql-BiYFMSpl.mjs +29 -0
  257. package/dist/Sparql-BiYFMSpl.mjs.map +1 -0
  258. package/dist/Sparql-DecpWTPh.d.mts +97 -0
  259. package/dist/SparqlClient-C_CjXbwj.mjs +333 -0
  260. package/dist/SparqlClient-C_CjXbwj.mjs.map +1 -0
  261. package/dist/SparqlClient-UPc88SHy.d.mts +290 -0
  262. package/dist/SparqlServer-CbUW-pVj.d.mts +71 -0
  263. package/dist/SparqlServer-Db7w7Cll.mjs +308 -0
  264. package/dist/SparqlServer-Db7w7Cll.mjs.map +1 -0
  265. package/dist/SqlStore-BBqFx-Wl.mjs +684 -0
  266. package/dist/SqlStore-BBqFx-Wl.mjs.map +1 -0
  267. package/dist/SqlStore-CCJo5I-e.d.mts +117 -0
  268. package/dist/Store-D0jmVfb3.d.mts +166 -0
  269. package/dist/Store-DH3Ow1GO.mjs +162 -0
  270. package/dist/Store-DH3Ow1GO.mjs.map +1 -0
  271. package/dist/Structure-BjMo4Bkc.mjs +334 -0
  272. package/dist/Structure-BjMo4Bkc.mjs.map +1 -0
  273. package/dist/Structure-n4CmDiab.d.mts +63 -0
  274. package/dist/Table-BGgm9nbM.mjs +538 -0
  275. package/dist/Table-BGgm9nbM.mjs.map +1 -0
  276. package/dist/Table-C3reiVrb.d.mts +261 -0
  277. package/dist/Term-B_4ezyXq.d.mts +619 -0
  278. package/dist/Term-CFLG1qu4.mjs +991 -0
  279. package/dist/Term-CFLG1qu4.mjs.map +1 -0
  280. package/dist/Term.d.mts +2 -0
  281. package/dist/Term.mjs +1 -0
  282. package/dist/TermCodec-CDZiEztW.mjs +397 -0
  283. package/dist/TermCodec-CDZiEztW.mjs.map +1 -0
  284. package/dist/TermCodec-_03bDXr0.d.mts +209 -0
  285. package/dist/TermCodec.d.mts +2 -0
  286. package/dist/TermCodec.mjs +1 -0
  287. package/dist/TermJson-BOPe0yve.d.mts +232 -0
  288. package/dist/TermJson-FAg3lvjl.mjs +142 -0
  289. package/dist/TermJson-FAg3lvjl.mjs.map +1 -0
  290. package/dist/TermJson.d.mts +2 -0
  291. package/dist/TermJson.mjs +1 -0
  292. package/dist/TermSchema-Bn9CoaYA.mjs +30 -0
  293. package/dist/TermSchema-Bn9CoaYA.mjs.map +1 -0
  294. package/dist/TermSchema-DBO6MroX.d.mts +6 -0
  295. package/dist/Testing-B-aPCpmu.mjs +63 -0
  296. package/dist/Testing-B-aPCpmu.mjs.map +1 -0
  297. package/dist/Testing-CF6s8P3O.d.mts +53 -0
  298. package/dist/Tiff-CR-2QeSI.d.mts +148 -0
  299. package/dist/Tiff-DlsOlZPM.mjs +169 -0
  300. package/dist/Tiff-DlsOlZPM.mjs.map +1 -0
  301. package/dist/Tools-7MdRKccr.d.mts +583 -0
  302. package/dist/Tools-ryxphZbI.mjs +329 -0
  303. package/dist/Tools-ryxphZbI.mjs.map +1 -0
  304. package/dist/Translate-B0YKKANS.d.mts +37 -0
  305. package/dist/Translate-gzWPq0Ia.mjs +308 -0
  306. package/dist/Translate-gzWPq0Ia.mjs.map +1 -0
  307. package/dist/Translation-DC9rLm2y.mjs +1535 -0
  308. package/dist/Translation-DC9rLm2y.mjs.map +1 -0
  309. package/dist/Translation-K4L9XBqX.d.mts +103 -0
  310. package/dist/TriG-BkqtqZ6Y.mjs +125 -0
  311. package/dist/TriG-BkqtqZ6Y.mjs.map +1 -0
  312. package/dist/TriG-xDjdDatp.d.mts +86 -0
  313. package/dist/Turtle-Bv1_bisW.d.mts +85 -0
  314. package/dist/Turtle-CZVUOtwl.mjs +133 -0
  315. package/dist/Turtle-CZVUOtwl.mjs.map +1 -0
  316. package/dist/ValidationReport-DBdJshW5.mjs +285 -0
  317. package/dist/ValidationReport-DBdJshW5.mjs.map +1 -0
  318. package/dist/ValidationReport-Dx3WxY0h.d.mts +192 -0
  319. package/dist/Validator-227ff9Rn.d.mts +179 -0
  320. package/dist/Validator-BzXMKDeJ.mjs +1162 -0
  321. package/dist/Validator-BzXMKDeJ.mjs.map +1 -0
  322. package/dist/Validator-Dl8cih5V.d.mts +207 -0
  323. package/dist/Validator-DmUfMbpL.mjs +568 -0
  324. package/dist/Validator-DmUfMbpL.mjs.map +1 -0
  325. package/dist/Vocab-DceKywpq.mjs +168 -0
  326. package/dist/Vocab-DceKywpq.mjs.map +1 -0
  327. package/dist/Vocab-Df-L-IwZ.d.mts +46 -0
  328. package/dist/Vocab-Do56UgBX.d.mts +81 -0
  329. package/dist/Vocab-TfpWG1Ne.mjs +118 -0
  330. package/dist/Vocab-TfpWG1Ne.mjs.map +1 -0
  331. package/dist/Vocab.d.mts +2 -0
  332. package/dist/Vocab.mjs +1 -0
  333. package/dist/Vocabulary-C6-GPOy4.d.mts +22 -0
  334. package/dist/Vocabulary-CnQl-_Z4.mjs +179 -0
  335. package/dist/Vocabulary-CnQl-_Z4.mjs.map +1 -0
  336. package/dist/Xml-B7iihYCS.mjs +976 -0
  337. package/dist/Xml-B7iihYCS.mjs.map +1 -0
  338. package/dist/Xml-CmfHS7SW.d.mts +267 -0
  339. package/dist/Xmp-D7CKA7MZ.d.mts +111 -0
  340. package/dist/Xmp-DCGN9pOD.mjs +307 -0
  341. package/dist/Xmp-DCGN9pOD.mjs.map +1 -0
  342. package/dist/XmpBasic-CP38jpR_.mjs +73 -0
  343. package/dist/XmpBasic-CP38jpR_.mjs.map +1 -0
  344. package/dist/XmpBasic-Cmbe53rQ.d.mts +64 -0
  345. package/dist/XmpMM-BfL_TgJF.mjs +73 -0
  346. package/dist/XmpMM-BfL_TgJF.mjs.map +1 -0
  347. package/dist/XmpMM-Dqm3IX8p.d.mts +63 -0
  348. package/dist/XmpRights-BKPtoI5L.mjs +73 -0
  349. package/dist/XmpRights-BKPtoI5L.mjs.map +1 -0
  350. package/dist/XmpRights-CaALZQz2.d.mts +63 -0
  351. package/dist/Xsd-Cdgx5zRB.mjs +405 -0
  352. package/dist/Xsd-Cdgx5zRB.mjs.map +1 -0
  353. package/dist/Xsd-D2elNhSm.d.mts +187 -0
  354. package/dist/Xsd.d.mts +2 -0
  355. package/dist/Xsd.mjs +1 -0
  356. package/dist/accessors-CEjaojuQ.mjs +28 -0
  357. package/dist/accessors-CEjaojuQ.mjs.map +1 -0
  358. package/dist/codec-Lb2T96rv.d.mts +20 -0
  359. package/dist/codec-QNo7IdJF.mjs +389 -0
  360. package/dist/codec-QNo7IdJF.mjs.map +1 -0
  361. package/dist/csvw/Csv2Json.d.mts +2 -0
  362. package/dist/csvw/Csv2Json.mjs +1 -0
  363. package/dist/csvw/Csv2Rdf.d.mts +2 -0
  364. package/dist/csvw/Csv2Rdf.mjs +1 -0
  365. package/dist/csvw/Datatype.d.mts +2 -0
  366. package/dist/csvw/Datatype.mjs +1 -0
  367. package/dist/csvw/Metadata.d.mts +2 -0
  368. package/dist/csvw/Metadata.mjs +1 -0
  369. package/dist/csvw/Processor.d.mts +2 -0
  370. package/dist/csvw/Processor.mjs +1 -0
  371. package/dist/csvw/Report.d.mts +2 -0
  372. package/dist/csvw/Report.mjs +1 -0
  373. package/dist/csvw/Table.d.mts +2 -0
  374. package/dist/csvw/Table.mjs +1 -0
  375. package/dist/csvw/index.d.mts +8 -0
  376. package/dist/csvw/index.mjs +8 -0
  377. package/dist/index-Cms2TYfC.d.mts +16 -0
  378. package/dist/index-Ct8xFmRs.d.mts +7 -0
  379. package/dist/index-D4QFwOjm.d.mts +28 -0
  380. package/dist/index.d.mts +18 -0
  381. package/dist/index.mjs +17 -0
  382. package/dist/jsonld/DocumentLoader.d.mts +2 -0
  383. package/dist/jsonld/DocumentLoader.mjs +1 -0
  384. package/dist/jsonld/JsonLd.d.mts +2 -0
  385. package/dist/jsonld/JsonLd.mjs +1 -0
  386. package/dist/jsonld/JsonLdError.d.mts +2 -0
  387. package/dist/jsonld/JsonLdError.mjs +1 -0
  388. package/dist/jsonld/index.d.mts +4 -0
  389. package/dist/jsonld/index.mjs +4 -0
  390. package/dist/lists-D-OpQ0Ir.mjs +77 -0
  391. package/dist/lists-D-OpQ0Ir.mjs.map +1 -0
  392. package/dist/loop-D5-gNx9K.mjs +21 -0
  393. package/dist/loop-D5-gNx9K.mjs.map +1 -0
  394. package/dist/loop-DZ2hXlr_.mjs +21 -0
  395. package/dist/loop-DZ2hXlr_.mjs.map +1 -0
  396. package/dist/n3/Isomorphism.d.mts +2 -0
  397. package/dist/n3/Isomorphism.mjs +1 -0
  398. package/dist/n3/Mapping.d.mts +2 -0
  399. package/dist/n3/Mapping.mjs +1 -0
  400. package/dist/n3/Model.d.mts +2 -0
  401. package/dist/n3/Model.mjs +1 -0
  402. package/dist/n3/N3.d.mts +2 -0
  403. package/dist/n3/N3.mjs +1 -0
  404. package/dist/n3/Reasoner.d.mts +2 -0
  405. package/dist/n3/Reasoner.mjs +2 -0
  406. package/dist/n3/Vocab.d.mts +2 -0
  407. package/dist/n3/Vocab.mjs +1 -0
  408. package/dist/n3/index.d.mts +7 -0
  409. package/dist/n3/index.mjs +7 -0
  410. package/dist/ntriples/Grammar.d.mts +2 -0
  411. package/dist/ntriples/Grammar.mjs +1 -0
  412. package/dist/ntriples/Lexical.d.mts +2 -0
  413. package/dist/ntriples/Lexical.mjs +1 -0
  414. package/dist/ntriples/NQuads.d.mts +2 -0
  415. package/dist/ntriples/NQuads.mjs +1 -0
  416. package/dist/ntriples/NTriples.d.mts +2 -0
  417. package/dist/ntriples/NTriples.mjs +1 -0
  418. package/dist/ntriples/index.d.mts +5 -0
  419. package/dist/ntriples/index.mjs +5 -0
  420. package/dist/rdfjson/RdfJson.d.mts +2 -0
  421. package/dist/rdfjson/RdfJson.mjs +1 -0
  422. package/dist/rdfjson/index.d.mts +2 -0
  423. package/dist/rdfjson/index.mjs +1 -0
  424. package/dist/rdfxml/RdfXml.d.mts +2 -0
  425. package/dist/rdfxml/RdfXml.mjs +1 -0
  426. package/dist/rdfxml/Xml.d.mts +2 -0
  427. package/dist/rdfxml/Xml.mjs +1 -0
  428. package/dist/rdfxml/index.d.mts +3 -0
  429. package/dist/rdfxml/index.mjs +3 -0
  430. package/dist/rolldown-runtime-D8jQpDG7.mjs +12 -0
  431. package/dist/serialize-96KBvKaf.mjs +1463 -0
  432. package/dist/serialize-96KBvKaf.mjs.map +1 -0
  433. package/dist/serialize-DsoXdM6n.d.mts +37 -0
  434. package/dist/shacl/Message.d.mts +2 -0
  435. package/dist/shacl/Message.mjs +1 -0
  436. package/dist/shacl/Path.d.mts +2 -0
  437. package/dist/shacl/Path.mjs +1 -0
  438. package/dist/shacl/Shapes.d.mts +2 -0
  439. package/dist/shacl/Shapes.mjs +1 -0
  440. package/dist/shacl/Sparql.d.mts +2 -0
  441. package/dist/shacl/Sparql.mjs +1 -0
  442. package/dist/shacl/ValidationReport.d.mts +2 -0
  443. package/dist/shacl/ValidationReport.mjs +1 -0
  444. package/dist/shacl/Validator.d.mts +2 -0
  445. package/dist/shacl/Validator.mjs +1 -0
  446. package/dist/shacl/Vocabulary.d.mts +2 -0
  447. package/dist/shacl/Vocabulary.mjs +1 -0
  448. package/dist/shacl/index.d.mts +9 -0
  449. package/dist/shacl/index.mjs +8 -0
  450. package/dist/shex/SemAct.d.mts +2 -0
  451. package/dist/shex/SemAct.mjs +1 -0
  452. package/dist/shex/ShExC.d.mts +2 -0
  453. package/dist/shex/ShExC.mjs +1 -0
  454. package/dist/shex/ShExError.d.mts +2 -0
  455. package/dist/shex/ShExError.mjs +1 -0
  456. package/dist/shex/ShExJ.d.mts +2 -0
  457. package/dist/shex/ShExJ.mjs +2 -0
  458. package/dist/shex/ShExR.d.mts +2 -0
  459. package/dist/shex/ShExR.mjs +1 -0
  460. package/dist/shex/ShapeMap.d.mts +2 -0
  461. package/dist/shex/ShapeMap.mjs +1 -0
  462. package/dist/shex/Structure.d.mts +2 -0
  463. package/dist/shex/Structure.mjs +2 -0
  464. package/dist/shex/Validator.d.mts +2 -0
  465. package/dist/shex/Validator.mjs +4 -0
  466. package/dist/shex/index.d.mts +9 -0
  467. package/dist/shex/index.mjs +9 -0
  468. package/dist/sparql/Algebra.d.mts +2 -0
  469. package/dist/sparql/Algebra.mjs +1 -0
  470. package/dist/sparql/Ast.d.mts +2 -0
  471. package/dist/sparql/Ast.mjs +1 -0
  472. package/dist/sparql/Bindings.d.mts +2 -0
  473. package/dist/sparql/Bindings.mjs +1 -0
  474. package/dist/sparql/ClientErrors.d.mts +2 -0
  475. package/dist/sparql/ClientErrors.mjs +1 -0
  476. package/dist/sparql/ClientFormats.d.mts +2 -0
  477. package/dist/sparql/ClientFormats.mjs +1 -0
  478. package/dist/sparql/Engine.d.mts +2 -0
  479. package/dist/sparql/Engine.mjs +1 -0
  480. package/dist/sparql/Errors.d.mts +2 -0
  481. package/dist/sparql/Errors.mjs +1 -0
  482. package/dist/sparql/Expression.d.mts +2 -0
  483. package/dist/sparql/Expression.mjs +1 -0
  484. package/dist/sparql/Federation.d.mts +2 -0
  485. package/dist/sparql/Federation.mjs +1 -0
  486. package/dist/sparql/Functions.d.mts +2 -0
  487. package/dist/sparql/Functions.mjs +1 -0
  488. package/dist/sparql/Generator.d.mts +2 -0
  489. package/dist/sparql/Generator.mjs +1 -0
  490. package/dist/sparql/GraphStore.d.mts +2 -0
  491. package/dist/sparql/GraphStore.mjs +1 -0
  492. package/dist/sparql/Optimizer.d.mts +2 -0
  493. package/dist/sparql/Optimizer.mjs +1 -0
  494. package/dist/sparql/Options.d.mts +2 -0
  495. package/dist/sparql/Options.mjs +1 -0
  496. package/dist/sparql/Parser.d.mts +2 -0
  497. package/dist/sparql/Parser.mjs +1 -0
  498. package/dist/sparql/Protocol.d.mts +2 -0
  499. package/dist/sparql/Protocol.mjs +1 -0
  500. package/dist/sparql/ProtocolErrors.d.mts +2 -0
  501. package/dist/sparql/ProtocolErrors.mjs +1 -0
  502. package/dist/sparql/QueryBuilder.d.mts +2 -0
  503. package/dist/sparql/QueryBuilder.mjs +1 -0
  504. package/dist/sparql/Results.d.mts +2 -0
  505. package/dist/sparql/Results.mjs +1 -0
  506. package/dist/sparql/ResultsStream.d.mts +2 -0
  507. package/dist/sparql/ResultsStream.mjs +1 -0
  508. package/dist/sparql/ServerFormats.d.mts +2 -0
  509. package/dist/sparql/ServerFormats.mjs +1 -0
  510. package/dist/sparql/ServiceDescription.d.mts +2 -0
  511. package/dist/sparql/ServiceDescription.mjs +1 -0
  512. package/dist/sparql/SparqlClient.d.mts +2 -0
  513. package/dist/sparql/SparqlClient.mjs +1 -0
  514. package/dist/sparql/SparqlServer.d.mts +2 -0
  515. package/dist/sparql/SparqlServer.mjs +1 -0
  516. package/dist/sparql/Store.d.mts +2 -0
  517. package/dist/sparql/Store.mjs +1 -0
  518. package/dist/sparql/TermSchema.d.mts +3 -0
  519. package/dist/sparql/TermSchema.mjs +2 -0
  520. package/dist/sparql/Testing.d.mts +2 -0
  521. package/dist/sparql/Testing.mjs +1 -0
  522. package/dist/sparql/Translate.d.mts +2 -0
  523. package/dist/sparql/Translate.mjs +1 -0
  524. package/dist/sparql/ai/Mcp.d.mts +2 -0
  525. package/dist/sparql/ai/Mcp.mjs +1 -0
  526. package/dist/sparql/ai/Tools.d.mts +2 -0
  527. package/dist/sparql/ai/Tools.mjs +1 -0
  528. package/dist/sparql/ai/index.d.mts +3 -0
  529. package/dist/sparql/ai/index.mjs +3 -0
  530. package/dist/sparql/index.d.mts +30 -0
  531. package/dist/sparql/index.mjs +29 -0
  532. package/dist/sparql/shacl/Evaluator.d.mts +2 -0
  533. package/dist/sparql/shacl/Evaluator.mjs +1 -0
  534. package/dist/sparql/shacl/Query.d.mts +2 -0
  535. package/dist/sparql/shacl/Query.mjs +1 -0
  536. package/dist/sparql/shacl/index.d.mts +3 -0
  537. package/dist/sparql/shacl/index.mjs +3 -0
  538. package/dist/sparql/sql/Dialect.d.mts +2 -0
  539. package/dist/sparql/sql/Dialect.mjs +1 -0
  540. package/dist/sparql/sql/SqlStore.d.mts +2 -0
  541. package/dist/sparql/sql/SqlStore.mjs +1 -0
  542. package/dist/sparql/sql/Translation.d.mts +2 -0
  543. package/dist/sparql/sql/Translation.mjs +1 -0
  544. package/dist/sparql/sql/index.d.mts +4 -0
  545. package/dist/sparql/sql/index.mjs +4 -0
  546. package/dist/stringMap-D5YIpYtR.mjs +38 -0
  547. package/dist/stringMap-D5YIpYtR.mjs.map +1 -0
  548. package/dist/tokens-C6BzeodQ.mjs +24 -0
  549. package/dist/tokens-C6BzeodQ.mjs.map +1 -0
  550. package/dist/turtle/Grammar.d.mts +3 -0
  551. package/dist/turtle/Grammar.mjs +3 -0
  552. package/dist/turtle/Manifest.d.mts +2 -0
  553. package/dist/turtle/Manifest.mjs +1 -0
  554. package/dist/turtle/TriG.d.mts +2 -0
  555. package/dist/turtle/TriG.mjs +1 -0
  556. package/dist/turtle/Turtle.d.mts +2 -0
  557. package/dist/turtle/Turtle.mjs +1 -0
  558. package/dist/turtle/index.d.mts +5 -0
  559. package/dist/turtle/index.mjs +5 -0
  560. package/dist/xmp/DublinCore.d.mts +2 -0
  561. package/dist/xmp/DublinCore.mjs +1 -0
  562. package/dist/xmp/Exif.d.mts +2 -0
  563. package/dist/xmp/Exif.mjs +1 -0
  564. package/dist/xmp/Extract.d.mts +2 -0
  565. package/dist/xmp/Extract.mjs +1 -0
  566. package/dist/xmp/Metadata.d.mts +2 -0
  567. package/dist/xmp/Metadata.mjs +1 -0
  568. package/dist/xmp/Namespaces.d.mts +2 -0
  569. package/dist/xmp/Namespaces.mjs +1 -0
  570. package/dist/xmp/Packet.d.mts +2 -0
  571. package/dist/xmp/Packet.mjs +1 -0
  572. package/dist/xmp/Pdf.d.mts +2 -0
  573. package/dist/xmp/Pdf.mjs +1 -0
  574. package/dist/xmp/PdfA.d.mts +2 -0
  575. package/dist/xmp/PdfA.mjs +1 -0
  576. package/dist/xmp/Photoshop.d.mts +2 -0
  577. package/dist/xmp/Photoshop.mjs +1 -0
  578. package/dist/xmp/Tiff.d.mts +2 -0
  579. package/dist/xmp/Tiff.mjs +1 -0
  580. package/dist/xmp/Xmp.d.mts +2 -0
  581. package/dist/xmp/Xmp.mjs +3 -0
  582. package/dist/xmp/XmpBasic.d.mts +2 -0
  583. package/dist/xmp/XmpBasic.mjs +1 -0
  584. package/dist/xmp/XmpMM.d.mts +2 -0
  585. package/dist/xmp/XmpMM.mjs +1 -0
  586. package/dist/xmp/XmpRights.d.mts +2 -0
  587. package/dist/xmp/XmpRights.mjs +1 -0
  588. package/dist/xmp/index.d.mts +15 -0
  589. package/dist/xmp/index.mjs +15 -0
  590. package/package.json +187 -3
  591. package/src/Dataset.ts +1085 -0
  592. package/src/Iri.ts +322 -0
  593. package/src/Isomorphism.ts +319 -0
  594. package/src/LanguageTag.ts +98 -0
  595. package/src/Namespace.ts +100 -0
  596. package/src/PrefixMap.ts +117 -0
  597. package/src/RdfJs.ts +146 -0
  598. package/src/Rdfs.ts +241 -0
  599. package/src/Reification.ts +103 -0
  600. package/src/Resource.ts +873 -0
  601. package/src/Serialization.ts +775 -0
  602. package/src/Term.ts +1300 -0
  603. package/src/TermCodec.ts +505 -0
  604. package/src/TermJson.ts +313 -0
  605. package/src/Vocab.ts +161 -0
  606. package/src/Xsd.ts +573 -0
  607. package/src/csvw/Csv2Json.ts +265 -0
  608. package/src/csvw/Csv2Rdf.ts +268 -0
  609. package/src/csvw/Datatype.ts +589 -0
  610. package/src/csvw/Metadata.ts +1523 -0
  611. package/src/csvw/Processor.ts +276 -0
  612. package/src/csvw/Report.ts +135 -0
  613. package/src/csvw/Table.ts +759 -0
  614. package/src/csvw/index.ts +22 -0
  615. package/src/csvw/internal/dateFormat.ts +202 -0
  616. package/src/csvw/internal/json.ts +104 -0
  617. package/src/csvw/internal/numberFormat.ts +369 -0
  618. package/src/csvw/internal/object.ts +23 -0
  619. package/src/csvw/internal/uriTemplate.ts +130 -0
  620. package/src/csvw/internal/url.ts +93 -0
  621. package/src/csvw/internal/vocab.ts +282 -0
  622. package/src/index.ts +40 -0
  623. package/src/internal/arbitrary.ts +148 -0
  624. package/src/internal/loop.ts +24 -0
  625. package/src/internal/pending.ts +20 -0
  626. package/src/internal/textDecoder.ts +183 -0
  627. package/src/jsonld/DocumentLoader.ts +512 -0
  628. package/src/jsonld/JsonLd.ts +847 -0
  629. package/src/jsonld/JsonLdError.ts +105 -0
  630. package/src/jsonld/index.ts +13 -0
  631. package/src/jsonld/internal/compact.ts +852 -0
  632. package/src/jsonld/internal/context.ts +781 -0
  633. package/src/jsonld/internal/expand.ts +614 -0
  634. package/src/jsonld/internal/flatten.ts +243 -0
  635. package/src/jsonld/internal/frame.ts +559 -0
  636. package/src/jsonld/internal/iri.ts +52 -0
  637. package/src/jsonld/internal/json.ts +19 -0
  638. package/src/jsonld/internal/linkHeader.ts +60 -0
  639. package/src/jsonld/internal/rdf.ts +481 -0
  640. package/src/jsonld/internal/util.ts +367 -0
  641. package/src/n3/Isomorphism.ts +476 -0
  642. package/src/n3/Mapping.ts +332 -0
  643. package/src/n3/Model.ts +696 -0
  644. package/src/n3/N3.ts +237 -0
  645. package/src/n3/Reasoner.ts +568 -0
  646. package/src/n3/Vocab.ts +111 -0
  647. package/src/n3/index.ts +21 -0
  648. package/src/n3/internal/builtins.ts +668 -0
  649. package/src/n3/internal/guards.ts +23 -0
  650. package/src/n3/internal/lists.ts +106 -0
  651. package/src/n3/internal/loop.ts +28 -0
  652. package/src/n3/internal/parser.ts +1032 -0
  653. package/src/n3/internal/writer.ts +344 -0
  654. package/src/ntriples/Grammar.ts +317 -0
  655. package/src/ntriples/Lexical.ts +321 -0
  656. package/src/ntriples/NQuads.ts +140 -0
  657. package/src/ntriples/NTriples.ts +143 -0
  658. package/src/ntriples/index.ts +14 -0
  659. package/src/ntriples/internal/codec.ts +229 -0
  660. package/src/ntriples/internal/lineParser.ts +326 -0
  661. package/src/ntriples/internal/loop.ts +14 -0
  662. package/src/rdfjson/RdfJson.ts +806 -0
  663. package/src/rdfjson/index.ts +9 -0
  664. package/src/rdfjson/internal/json.ts +19 -0
  665. package/src/rdfxml/RdfXml.ts +1408 -0
  666. package/src/rdfxml/Xml.ts +419 -0
  667. package/src/rdfxml/index.ts +11 -0
  668. package/src/rdfxml/internal/chars.ts +73 -0
  669. package/src/rdfxml/internal/loop.ts +24 -0
  670. package/src/rdfxml/internal/parser.ts +1265 -0
  671. package/src/shacl/Message.ts +102 -0
  672. package/src/shacl/Path.ts +483 -0
  673. package/src/shacl/Shapes.ts +913 -0
  674. package/src/shacl/Sparql.ts +128 -0
  675. package/src/shacl/ValidationReport.ts +438 -0
  676. package/src/shacl/Validator.ts +1156 -0
  677. package/src/shacl/Vocabulary.ts +180 -0
  678. package/src/shacl/index.ts +22 -0
  679. package/src/shacl/internal/compare.ts +128 -0
  680. package/src/shacl/internal/regex.ts +56 -0
  681. package/src/shex/SemAct.ts +197 -0
  682. package/src/shex/ShExC.ts +415 -0
  683. package/src/shex/ShExError.ts +71 -0
  684. package/src/shex/ShExJ.ts +893 -0
  685. package/src/shex/ShExR.ts +640 -0
  686. package/src/shex/ShapeMap.ts +385 -0
  687. package/src/shex/Structure.ts +467 -0
  688. package/src/shex/Validator.ts +395 -0
  689. package/src/shex/index.ts +24 -0
  690. package/src/shex/internal/engine.ts +682 -0
  691. package/src/shex/internal/iterate.ts +14 -0
  692. package/src/shex/internal/json.ts +12 -0
  693. package/src/shex/internal/lexer.ts +56 -0
  694. package/src/shex/internal/nodeConstraint.ts +368 -0
  695. package/src/shex/internal/shexcLexer.ts +387 -0
  696. package/src/shex/internal/shexcParser.ts +1312 -0
  697. package/src/shex/internal/tokens.ts +41 -0
  698. package/src/shex/internal/xpathRegex.ts +290 -0
  699. package/src/sparql/Algebra.ts +914 -0
  700. package/src/sparql/Ast.ts +1344 -0
  701. package/src/sparql/Bindings.ts +272 -0
  702. package/src/sparql/ClientErrors.ts +156 -0
  703. package/src/sparql/ClientFormats.ts +70 -0
  704. package/src/sparql/Engine.ts +699 -0
  705. package/src/sparql/Errors.ts +30 -0
  706. package/src/sparql/Expression.ts +360 -0
  707. package/src/sparql/Federation.ts +187 -0
  708. package/src/sparql/Functions.ts +689 -0
  709. package/src/sparql/Generator.ts +426 -0
  710. package/src/sparql/GraphStore.ts +341 -0
  711. package/src/sparql/Optimizer.ts +230 -0
  712. package/src/sparql/Options.ts +191 -0
  713. package/src/sparql/Parser.ts +173 -0
  714. package/src/sparql/Protocol.ts +342 -0
  715. package/src/sparql/ProtocolErrors.ts +113 -0
  716. package/src/sparql/QueryBuilder.ts +953 -0
  717. package/src/sparql/Results.ts +844 -0
  718. package/src/sparql/ResultsStream.ts +771 -0
  719. package/src/sparql/ServerFormats.ts +75 -0
  720. package/src/sparql/ServiceDescription.ts +130 -0
  721. package/src/sparql/SparqlClient.ts +947 -0
  722. package/src/sparql/SparqlServer.ts +528 -0
  723. package/src/sparql/Store.ts +311 -0
  724. package/src/sparql/TermSchema.ts +20 -0
  725. package/src/sparql/Testing.ts +114 -0
  726. package/src/sparql/Translate.ts +407 -0
  727. package/src/sparql/ai/Mcp.ts +43 -0
  728. package/src/sparql/ai/Tools.ts +481 -0
  729. package/src/sparql/ai/index.ts +11 -0
  730. package/src/sparql/index.ts +64 -0
  731. package/src/sparql/internal/evaluator.ts +947 -0
  732. package/src/sparql/internal/hash.ts +418 -0
  733. package/src/sparql/internal/json.ts +12 -0
  734. package/src/sparql/internal/lexer.ts +488 -0
  735. package/src/sparql/internal/loop.ts +24 -0
  736. package/src/sparql/internal/parser.ts +2215 -0
  737. package/src/sparql/internal/prefetch.ts +46 -0
  738. package/src/sparql/internal/stringMap.ts +39 -0
  739. package/src/sparql/internal/textResults.ts +226 -0
  740. package/src/sparql/internal/values.ts +550 -0
  741. package/src/sparql/shacl/Evaluator.ts +478 -0
  742. package/src/sparql/shacl/Query.ts +574 -0
  743. package/src/sparql/shacl/index.ts +12 -0
  744. package/src/sparql/sql/Dialect.ts +246 -0
  745. package/src/sparql/sql/SqlStore.ts +1080 -0
  746. package/src/sparql/sql/Translation.ts +1898 -0
  747. package/src/sparql/sql/index.ts +17 -0
  748. package/src/sparql/sql/internal/encoding.ts +313 -0
  749. package/src/turtle/Grammar.ts +286 -0
  750. package/src/turtle/Manifest.ts +215 -0
  751. package/src/turtle/TriG.ts +158 -0
  752. package/src/turtle/Turtle.ts +166 -0
  753. package/src/turtle/index.ts +14 -0
  754. package/src/turtle/internal/loop.ts +23 -0
  755. package/src/turtle/internal/parse.ts +112 -0
  756. package/src/turtle/internal/parser.ts +1303 -0
  757. package/src/turtle/internal/serialize.ts +79 -0
  758. package/src/turtle/internal/streamWriter.ts +128 -0
  759. package/src/turtle/internal/writer.ts +898 -0
  760. package/src/xmp/DublinCore.ts +147 -0
  761. package/src/xmp/Exif.ts +109 -0
  762. package/src/xmp/Extract.ts +351 -0
  763. package/src/xmp/Metadata.ts +703 -0
  764. package/src/xmp/Namespaces.ts +228 -0
  765. package/src/xmp/Packet.ts +297 -0
  766. package/src/xmp/Pdf.ts +99 -0
  767. package/src/xmp/PdfA.ts +280 -0
  768. package/src/xmp/Photoshop.ts +275 -0
  769. package/src/xmp/Tiff.ts +196 -0
  770. package/src/xmp/Xmp.ts +430 -0
  771. package/src/xmp/XmpBasic.ts +90 -0
  772. package/src/xmp/XmpMM.ts +82 -0
  773. package/src/xmp/XmpRights.ts +83 -0
  774. package/src/xmp/index.ts +36 -0
  775. package/src/xmp/internal/accessors.ts +72 -0
  776. package/src/xmp/internal/xml.ts +63 -0
package/README.md CHANGED
@@ -1,3 +1,2252 @@
1
1
  # @thomasfosterau/effect-rdf
2
2
 
3
- Placeholder reserved for npm trusted publishing; the first real release is 0.1.0, from https://github.com/thomasfosterau/effect-rdf.
3
+ The [RDF 1.2](https://www.w3.org/TR/rdf12-concepts/) / RDF-star toolkit for [Effect](https://effect.website) 4, in one package. The root entry is the data model: terms with value equality, an immutable indexed `Dataset`, IRIs, prefix maps, typed vocabularies, XSD datatypes, RDF Schema entailment, RDF 1.2 reification, graph isomorphism and the serialisation interfaces every syntax implements. Everything else is a nested subpath, none of it on the root barrel, so a bundle carries only what it imports:
4
+
5
+ | Subpath | What it holds |
6
+ | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
7
+ | [`/ntriples`](#n-triples-and-n-quads-thomasfosteraueffect-rdfntriples) | N-Triples and N-Quads: strict and streaming parsers, canonical serialisers |
8
+ | [`/turtle`](#turtle-and-trig-thomasfosteraueffect-rdfturtle) | Turtle and TriG parsers and pretty-printers; the W3C test-manifest reader |
9
+ | [`/n3`](#notation3-thomasfosteraueffect-rdfn3) | Notation3: model, parser, serialiser, dataset mapping and a forward-chaining reasoner |
10
+ | [`/jsonld`](#json-ld-thomasfosteraueffect-rdfjsonld) | JSON-LD 1.1: expansion, compaction, flattening, framing, RDF conversion, document loaders |
11
+ | [`/rdfxml`](#rdfxml-thomasfosteraueffect-rdfrdfxml) | RDF/XML on a small namespace-aware XML parser, with Exclusive XML Canonicalization |
12
+ | [`/rdfjson`](#rdfjson-thomasfosteraueffect-rdfrdfjson) | RDF/JSON on Effect `Schema` |
13
+ | [`/xmp`](#xmp-thomasfosteraueffect-rdfxmp) | XMP packets as RDF, typed accessors for the common schemas, JPEG and PNG extraction |
14
+ | [`/csvw`](#csv-on-the-web-thomasfosteraueffect-rdfcsvw) | CSV on the Web over already-parsed tables: metadata, the annotated tabular model, CSV2RDF and CSV2JSON |
15
+ | [`/shacl`](#shacl-thomasfosteraueffect-rdfshacl) | The SHACL Core validator |
16
+ | [`/sparql`](#sparql-thomasfosteraueffect-rdfsparql) | SPARQL 1.1 / 1.2: parser, algebra, engine, results formats, typed builder, platform-neutral Protocol / Graph Store handler, client |
17
+ | [`/sparql/shacl`](#shacl-sparql-thomasfosteraueffect-rdfsparqlshacl) | SHACL-SPARQL for `/shacl` |
18
+ | [`/sparql/ai`](#knowledge-graph-tools-thomasfosteraueffect-rdfsparqlai) | Knowledge-graph tools for `effect/ai` and an MCP server registration |
19
+ | [`/sparql/sql`](#sparql-over-sql-thomasfosteraueffect-rdfsparqlsql) | The driver-agnostic SQL store and SPARQL-to-SQL translation (SQLite and PostgreSQL dialects, `effect/sql` only) |
20
+ | [`/shex`](#shex-thomasfosteraueffect-rdfshex) | ShEx: ShExC, ShExJ, ShExR, validation with shape maps and semantic actions |
21
+
22
+ Each subpath is `@thomasfosterau/effect-rdf/<subpath>` (its barrel, re-exporting its modules as namespaces) and `@thomasfosterau/effect-rdf/<subpath>/<Module>`. The Node server, its CLI and the typed `SparqlApi` live in [`@thomasfosterau/effect-sparql-node`](https://github.com/thomasfosterau/effect-rdf/tree/main/packages/sparql-node).
23
+
24
+ ```sh
25
+ pnpm add effect @thomasfosterau/effect-rdf
26
+ ```
27
+
28
+ `effect` (`^4.0.0`) is a peer dependency. [`@thomasfosterau/effect-parse`](https://www.npmjs.com/package/@thomasfosterau/effect-parse) (`^0.4.0`) is an optional peer, needed by the syntax and SPARQL-parser subpaths (`/ntriples`, `/turtle`, `/n3`, `/jsonld`, `/rdfxml`, `/xmp`, `/shex`, `/sparql`, `/sparql/shacl`, `/sparql/ai`); the root entry, `/rdfjson`, `/csvw`, `/shacl` and `/sparql/sql` never import it:
29
+
30
+ ```sh
31
+ pnpm add @thomasfosterau/effect-parse
32
+ ```
33
+
34
+ ## Modules
35
+
36
+ | Module | What it holds |
37
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
38
+ | `Term` | `NamedNode`, `BlankNode`, `Literal` (language tags with base direction), `Variable`, `DefaultGraph`, `Triple` (triple terms) and `Quad`; constructors (`namedNode`, `blankNode`, `literal`, `triple`, `quad`), guards, `compare` / `compareQuads` and their Effect instances (`Order`, `QuadOrder`, `Equivalence`, `QuadEquivalence`), JSON conversion (`toJson`, `fromJson`, `quadToJson`, `quadFromJson`, `isWellFormed`) and schemas with JSON codecs. Every term implements `Equal` / `Hash`. |
39
+ | `TermJson` | The JSON shape of terms and quads: the SPARQL 1.2 JSON results term (`{ "type": "uri", "value": … }`, triple terms, `xml:lang`, `its:dir`) extended with `{ "type": "variable", "value" }` and `{ "type": "default-graph" }`, and `{ subject, predicate, object, graph? }` quads. |
40
+ | `TermCodec` | `Schema`s between ground terms and JS values: `string`, `localized`, `langString`, `iri`, `namedNode`, `resource`, `literal`, `lexical`, `number`, `integer`, `bigint`, `decimal`, `double`, `float`, `boolean`, `date`, `dateTime` (Effect `DateTime.Utc` / `Zoned`), `value`, `typed`, `typedOneOf`, and `make` for your own. Decode a `Dataset`'s objects or SPARQL solutions into typed structs. |
41
+ | `Resource` | Typed records over a dataset's resources: `property(predicate, codec, { inverse?, languages? })` maps a `Schema.Struct` field to a predicate, `describe({ type?, id? })` gives the struct an `rdf:type` and an id field, `list(codec)` reads an RDF collection; `decode`, `decodeAll` (every resource of the described type) and `encode` (to quads), and `schema(struct)`, the same mapping as a `Schema` from `{ dataset, subject }`. |
42
+ | `Dataset` | An immutable, indexed set of quads: `make`, `fromIterable`, `add`, `remove`, `match`, `objectsOf`, `subjects`, `graphs`, `list` (RDF collections), set operations, `filter` / `map` / `reduce`, `toNQuads`, `toGraph` / `fromGraph` (`effect/Graph`) and `DatasetSchema` (JSON: sorted array of quads). |
43
+ | `Iri` | RFC 3987 resolution (`resolve`, `removeDotSegments`), `relativize`, `isAbsolute`, `isValid`, `isValidReference`, and `IriSchema` / `IriReferenceSchema` (JSON Schema `format: "iri"` / `"iri-reference"`) for validating IRIs from `Config`, CLI flags or HTTP parameters. |
44
+ | `LanguageTag` | BCP 47 language tags as RDF 1.2 requires them: `isWellFormed` (the §2.1 ABNF), `normalize` (lower case) and `LanguageTagSchema`. |
45
+ | `PrefixMap` | Prefix ⇄ namespace maps: `make`, `set`, `merge`, `expand` (CURIE → IRI), `compact` (IRI → prefix and local name). |
46
+ | `Namespace`, `Vocab` | `Namespace.make(base)` builds IRIs; `Namespace.vocabulary(base, terms)` a typed vocabulary. `Vocab` ships `rdf`, `rdfs`, `xsd`, `owl` and `commonPrefixes`. |
47
+ | `Xsd` | Lexical validation, canonicalisation and value mapping for the XSD datatypes, literal constructors (`integer`, `decimal`, `dateTime`, …) and `fromJs` / `toJs`. |
48
+ | `Rdfs` | Class and property hierarchies (`superClasses`, `instancesOf`, …) and the RDFS `closure`. |
49
+ | `Reification` | RDF 1.2 reifiers and annotations: `annotate`, `reifiers`, `annotations`, `toStandardReification`. |
50
+ | `Isomorphism` | Blank-node-aware graph comparison: `isomorphic`, `bijection`, deterministic `relabel`. |
51
+ | `RdfJs` | Conversion to and from the [RDF/JS data model](https://rdf.js.org/data-model-spec/). |
52
+ | `Serialization` | The `Format`, `Parser`, `Serializer` and `Codec` interfaces the syntax subpaths implement, their typed errors (`RdfParseError` with `line` / `column`, `RdfSerializeError`), `toSchema`, the `Formats` service with content negotiation (`negotiate`, `parserFor`, `serializerFor`, `parserForExtension`, `serializerForExtension`), byte entry points (`parseBytes`, `parseBytesDocument`, `serializeBytes`: strict UTF-8 or a `charset`, BOM stripped) and files over Effect's `FileSystem` (`readFile`, `writeFile`). |
53
+
54
+ Every module is also its own entry point (`@thomasfosterau/effect-rdf/Term`, …). The RDF/JSON, CSVW and SHACL modules are nested under their own subpath (`@thomasfosterau/effect-rdf/shacl`, `@thomasfosterau/effect-rdf/shacl/Validator`, …) and are not on the root barrel; their sections below list them.
55
+
56
+ ### Resources
57
+
58
+ `Resource` maps the resources of a dataset to typed records. Each field of a `Schema.Struct` names its predicate with `Resource.property` (an annotation only: the field's type is unchanged), and its schema sets the cardinality:
59
+
60
+ | Field schema | Values read |
61
+ | ----------------------------------------------------- | ------------------------------------------------------- |
62
+ | `property(p, codec)` | exactly one (fails on none or several) |
63
+ | `Schema.optional(property(p, codec))` / `optionalKey` | at most one |
64
+ | `property(p, Schema.Array(codec))` | any number, in `Term.Order` |
65
+ | `property(p, Resource.list(codec))` | one RDF collection (`rdf:first` / `rdf:rest`) |
66
+ | `property(p, Struct)` | a nested resource (IRI or blank node), read recursively |
67
+
68
+ Values decode through the field's own codec (the `TermCodec` schemas, or any `Schema.Codec<A, Term.GroundTerm>`), and a failure is an ordinary `SchemaError` at the field's path. `{ inverse: true }` reads `?x p resource` instead; `{ languages: ['en', 'fr', ''] }` takes the first value whose language tag matches, in that order (`''` is untagged). `Resource.describe` adds the `rdf:type` decoding requires and encoding writes, and an `id` field holding the resource itself:
69
+
70
+ ```ts
71
+ import { Effect, Schema } from 'effect';
72
+ import { Dataset, Namespace, Resource, TermCodec } from '@thomasfosterau/effect-rdf';
73
+
74
+ const foaf = Namespace.make('http://xmlns.com/foaf/0.1/');
75
+ const ex = Namespace.make('http://example.org/');
76
+
77
+ const Address = Schema.Struct({
78
+ city: Resource.property(ex('city'), TermCodec.string),
79
+ });
80
+
81
+ const Person = Schema.Struct({
82
+ id: TermCodec.iri,
83
+ name: Resource.property(foaf('name'), TermCodec.string, { languages: ['en', ''] }),
84
+ age: Schema.optional(Resource.property(foaf('age'), TermCodec.integer)),
85
+ knows: Resource.property(foaf('knows'), Schema.Array(TermCodec.namedNode)),
86
+ address: Schema.optional(Resource.property(ex('address'), Address)),
87
+ }).pipe(Resource.describe({ type: foaf('Person'), id: 'id' }));
88
+
89
+ const program = Effect.gen(function* () {
90
+ const alice = yield* Resource.decode(Person)(data, ex('alice')); // { id, name, age?, knows, address? }
91
+ const people = yield* Resource.decodeAll(Person)(data, { graph: ex('people') }); // [{ subject, value }]
92
+ // A nested value without an id is written as a fresh blank node.
93
+ const quads = yield* Resource.encode(Person)(ex('alice'), alice);
94
+ return Dataset.fromIterable(quads);
95
+ });
96
+ ```
97
+
98
+ `decode` and `decodeAll` read every graph unless given `{ graph }`; `encode` writes into `{ graph }` (the default graph otherwise). A cycle through a recursive schema (`Schema.suspend`) fails rather than looping. `Resource.schema(Person)` is the same mapping as a `Schema` whose encoded side is `{ dataset, subject }` (`Resource.InputSchema`), so it composes with `Schema.decodeTo` and the rest of the `Schema` API; encoding gives the dataset of the value's quads.
99
+
100
+ ### Bytes and files
101
+
102
+ The `Parser` interface reads text; `Serialization.parseBytes` reads the `Stream<Uint8Array>` that files, HTTP bodies, sockets and child processes produce. It decodes UTF-8 across chunk boundaries (or `options.charset`), drops a byte order mark, and fails on ill-formed bytes with an `RdfParseError` at the line and column of the bad byte. `readFile` / `writeFile` go through Effect's platform-agnostic `FileSystem` and `Path` services (provide `NodeFileSystem.layer` / `NodePath.layer` from `@effect/platform-node`, or Bun's), choosing the syntax by file extension from the `Formats` service unless given a parser or media type:
103
+
104
+ ```ts
105
+ import { Effect } from 'effect';
106
+ import { Serialization } from '@thomasfosterau/effect-rdf';
107
+ import { NQuads } from '@thomasfosterau/effect-rdf/ntriples';
108
+
109
+ const program = Effect.gen(function* () {
110
+ // Relative IRIs resolve against the file's `file:` URL.
111
+ const { dataset } = yield* Serialization.readFile('data.ttl');
112
+ yield* Serialization.writeFile('data.nq', dataset, { serializer: NQuads.serializer });
113
+ });
114
+ ```
115
+
116
+ ### Graph algorithms
117
+
118
+ `Dataset.toGraph` turns a dataset into an `effect/Graph` directed graph — subjects and objects are nodes, each quad an edge labelled with its predicate — with the term → node-index map algorithms start from. Restrict it to some predicates, one named graph, or include literal objects:
119
+
120
+ ```ts
121
+ import { Graph, HashMap, Option } from 'effect';
122
+ import { Dataset, Vocab } from '@thomasfosterau/effect-rdf';
123
+
124
+ const classes = Dataset.toGraph(data, { predicates: [Vocab.rdfs.subClassOf] });
125
+ Graph.isAcyclic(classes.graph); // false if the hierarchy has a cycle
126
+ const order = Array.from(Graph.indices(Graph.topo()(classes.graph))); // subclasses first
127
+
128
+ const people = Dataset.toGraph(data, { predicates: [foaf('knows')] });
129
+ const index = (t: Term.NamedNode) => Option.getOrElse(HashMap.get(people.nodes, t), () => -1);
130
+ Graph.dijkstra({ source: index(alice), target: index(dave), cost: () => 1 })(people.graph);
131
+ ```
132
+
133
+ `Dataset.fromGraph(graph, graphName?)` turns such a graph back into quads.
134
+
135
+ ### JSON
136
+
137
+ Every term, quad and dataset schema carries a `toCodecJson` link, so RDF values pass through anything that serialises with `Schema.toCodecJson` (RPC, `HttpApi` payloads, `KeyValueStore`, `effect/ai` tools) and `Schema.toJsonSchemaDocument` describes them:
138
+
139
+ ```ts
140
+ import { Schema } from 'effect';
141
+ import { Dataset, Term } from '@thomasfosterau/effect-rdf';
142
+
143
+ const codec = Schema.toCodecJson(Term.TermSchema);
144
+ Schema.encodeSync(codec)(Term.literal('hi', 'en')); // { type: 'literal', value: 'hi', 'xml:lang': 'en' }
145
+
146
+ // A dataset encodes as its quads in `Dataset.toSortedArray` order.
147
+ Schema.encodeSync(Schema.toCodecJson(Dataset.DatasetSchema))(Dataset.make()); // []
148
+ ```
149
+
150
+ Decoding rejects triple terms whose positions don't fit RDF (a literal subject, a non-IRI predicate).
151
+
152
+ ### Property testing
153
+
154
+ The same schemas derive arbitraries for Effect's native property testing (`effect/Arbitrary`, `@effect/vitest`'s `it.prop`): `Term.TermSchema`, `GroundTermSchema`, each term kind, `QuadSchema` and `Dataset.DatasetSchema` generate realistic RDF 1.2 values — IRIs from a small pool (so quads share subjects, objects and graphs), BCP 47 language tags, both base directions, valid lexical forms for common XSD datatypes, triple terms (as objects) nested up to two deep, and datasets of up to a dozen quads with named graphs:
155
+
156
+ ```ts
157
+ import { it } from '@effect/vitest';
158
+ import { Dataset, Isomorphism } from '@thomasfosterau/effect-rdf';
159
+
160
+ it.prop('my transform keeps the graph', { ds: Dataset.DatasetSchema }, ({ ds }) =>
161
+ Isomorphism.isomorphic(myTransform(ds), ds),
162
+ );
163
+ ```
164
+
165
+ ## Example
166
+
167
+ ```ts
168
+ import { Option } from 'effect';
169
+ import { Dataset, Isomorphism, Namespace, PrefixMap, Term, Xsd } from '@thomasfosterau/effect-rdf';
170
+
171
+ const ex = Namespace.make('http://example.org/');
172
+
173
+ const alice = ex('alice');
174
+ const data = Dataset.make(
175
+ Term.quad(alice, ex('name'), Term.literal('Alice', 'en')),
176
+ Term.quad(alice, ex('age'), Xsd.integer(42)),
177
+ Term.quad(alice, ex('knows'), Term.blankNode()),
178
+ );
179
+
180
+ Dataset.objectsOf(data, alice, ex('name')); // [Literal "Alice"@en]
181
+ Dataset.match(data, { predicate: ex('age') }).length; // 1
182
+
183
+ // Blank-node labels don't matter to isomorphism.
184
+ const same = Dataset.make(
185
+ Term.quad(alice, ex('knows'), Term.blankNode('someone')),
186
+ Term.quad(alice, ex('age'), Xsd.integer(42)),
187
+ Term.quad(alice, ex('name'), Term.literal('Alice', 'en')),
188
+ );
189
+ Isomorphism.isomorphic(data, same); // true
190
+
191
+ const prefixes = PrefixMap.make({ ex: 'http://example.org/' });
192
+ Option.getOrUndefined(PrefixMap.compact(prefixes, alice)); // ['ex', 'alice']
193
+ ```
194
+
195
+ To read and write documents, import a syntax subpath: [`/ntriples`](#n-triples-and-n-quads-thomasfosteraueffect-rdfntriples), [`/turtle`](#turtle-and-trig-thomasfosteraueffect-rdfturtle), [`/jsonld`](#json-ld-thomasfosteraueffect-rdfjsonld), [`/rdfxml`](#rdfxml-thomasfosteraueffect-rdfrdfxml), [`/n3`](#notation3-thomasfosteraueffect-rdfn3) or [`/rdfjson`](#rdfjson-thomasfosteraueffect-rdfrdfjson).
196
+
197
+ ## RDF/JSON (`@thomasfosterau/effect-rdf/rdfjson`)
198
+
199
+ `@thomasfosterau/effect-rdf/rdfjson` is [RDF/JSON](https://www.w3.org/TR/rdf-json/)
200
+ (`application/rdf+json`), built on Effect `Schema`. Its one module is also
201
+ `@thomasfosterau/effect-rdf/rdfjson/RdfJson`.
202
+
203
+ ### Schemas
204
+
205
+ | Schema | Type |
206
+ | ---------------------------------------- | ---------------------------------------- |
207
+ | `RdfJson.Document` / `StrictDocument` | `Document` (validates the JSON shape) |
208
+ | `RdfJson.ObjectValue`, `LiteralValue`, … | individual value objects |
209
+ | `RdfJson.RdfJsonFromString` | `string ⇄ Document` |
210
+ | `RdfJson.DatasetFromRdfJson` | `Document ⇄ Dataset` |
211
+ | `RdfJson.DatasetFromString` | `string ⇄ Dataset` (also `codec.schema`) |
212
+ | `RdfJson.makeDatasetSchema(options)` | `Document ⇄ Dataset` with options |
213
+
214
+ ```ts
215
+ import { Effect, Schema } from 'effect';
216
+ import { RdfJson } from '@thomasfosterau/effect-rdf/rdfjson';
217
+
218
+ const program = Effect.gen(function* () {
219
+ const dataset = yield* Schema.decodeEffect(RdfJson.DatasetFromString)(`{
220
+ "http://example.org/about": {
221
+ "http://purl.org/dc/terms/title": [
222
+ { "type": "literal", "value": "Anna's Homepage", "lang": "en" }
223
+ ]
224
+ }
225
+ }`);
226
+ const text = yield* Schema.encodeEffect(RdfJson.DatasetFromString)(dataset);
227
+ });
228
+ ```
229
+
230
+ Invalid documents fail with precise messages, e.g.
231
+ `Expected an RDF/JSON value ("type" must be "uri", "bnode", "literal" or "triple") at ["s"]["p"][0]`.
232
+
233
+ ### Parser and serializer
234
+
235
+ `RdfJson.parse(text, options?)` / `RdfJson.serialize(quads, options?)` implement the core
236
+ `Serialization` interfaces (`parser`, `serializer`, `codec`, `format`). Parse errors are
237
+ `RdfParseError`s (JSON syntax errors carry `line`/`column`); serialisation errors are
238
+ `RdfSerializeError`s.
239
+
240
+ - `parse` options: `blankNodePrefix`, `graph`, `strict` (reject the RDF 1.2 extensions).
241
+ - `serialize` options: `extensions` (default `false`), `mergeGraphs` (default `false`),
242
+ `space` (JSON indentation, default 2).
243
+
244
+ RDF/JSON describes a single graph, so quads in named graphs are rejected unless
245
+ `mergeGraphs: true`.
246
+
247
+ ### RDF 1.2 extensions
248
+
249
+ Output is strict RDF/JSON by default; data that needs the extensions fails unless
250
+ `extensions: true`. Parsing accepts them unless `strict: true`.
251
+
252
+ - **Base direction**: a literal with `"lang"` may carry `"direction": "ltr" | "rtl"`.
253
+ - **Triple terms**: `{ "type": "triple", "value": { "subject": …, "predicate": …, "object": … } }`,
254
+ where `subject` is a `uri` or `bnode` value, `predicate` a `uri` value and `object` any value
255
+ (including another triple term). Triple terms cannot be subjects.
256
+
257
+ ```json
258
+ {
259
+ "http://example.org/s": {
260
+ "http://example.org/claims": [
261
+ {
262
+ "type": "triple",
263
+ "value": {
264
+ "subject": { "type": "uri", "value": "http://example.org/a" },
265
+ "predicate": { "type": "uri", "value": "http://example.org/b" },
266
+ "object": { "type": "literal", "value": "مرحبا", "lang": "ar", "direction": "rtl" }
267
+ }
268
+ }
269
+ ]
270
+ }
271
+ }
272
+ ```
273
+
274
+ ## N-Triples and N-Quads (`@thomasfosterau/effect-rdf/ntriples`)
275
+
276
+ RDF 1.2 [N-Triples](https://www.w3.org/TR/rdf12-n-triples/) and
277
+ [N-Quads](https://www.w3.org/TR/rdf12-n-quads/) for
278
+ [`@thomasfosterau/effect-rdf`](#modules).
279
+
280
+ - Strict parsers: absolute IRIs only, validated escapes (no surrogates),
281
+ language tags with base direction (`"x"@en--ltr`), triple terms
282
+ `<<( s p o )>>`, `VERSION` directives, and the RDF 1.1 subset.
283
+ - Streaming: `parseStream` consumes a `Stream<string>` line by line (chunk
284
+ boundaries may fall anywhere), so large files are never buffered.
285
+ - Canonical serializers (RDF 1.2 canonical escapes, lower-case language tags)
286
+ with a true `serializeStream`.
287
+ - Passes the full W3C rdf11/rdf12 N-Triples and N-Quads suites (syntax and
288
+ canonicalisation).
289
+
290
+ ```ts
291
+ import { Effect, Stream } from 'effect';
292
+ import { NQuads, NTriples } from '@thomasfosterau/effect-rdf/ntriples';
293
+
294
+ const program = Effect.gen(function* () {
295
+ const { dataset } = yield* NTriples.parse(
296
+ '<http://example.org/s> <http://example.org/p> "chat"@en--ltr .\n',
297
+ );
298
+ const text = yield* NQuads.serialize(dataset, { sort: true });
299
+
300
+ // Incremental parsing of a text stream
301
+ const quads = yield* Stream.runCollect(
302
+ NQuads.parseStream(Stream.make(text.slice(0, 10), text.slice(10))),
303
+ );
304
+ return quads;
305
+ });
306
+ ```
307
+
308
+ Each module exports `format`, `parser`, `serializer`, `codec` (with a
309
+ `Schema` from `string` to `Dataset`), `parse`, `parseQuads` (document order),
310
+ `parseStream`, `serialize` and `serializeStream`. The `Lexical` module holds
311
+ the character classes, escape decoding, language-tag validation and canonical
312
+ term formatting shared with the Turtle package.
313
+
314
+ `Grammar` holds the N-Triples terminals (`iriRef`, `blankNodeLabel`, `langTag`,
315
+ `langDir`, `stringLiteralQuote`, `uchar`, `echar`) and character classes
316
+ (`pnCharsBase`, `pnCharsU`, `pnChars`, `hex`, …) as
317
+ [`@thomasfosterau/effect-parse`](https://www.npmjs.com/package/@thomasfosterau/effect-parse)
318
+ parsers and token sets (an optional peer dependency of effect-rdf, needed by this subpath). The token sets
319
+ accept exactly the code points of the `Lexical` predicates. Turtle's `Grammar`
320
+ re-exports them and adds its own terminals. The N-Triples and N-Quads parsers
321
+ themselves stay hand-written line parsers, which are simpler and faster for a
322
+ one-statement-per-line format.
323
+
324
+ ## Turtle and TriG (`@thomasfosterau/effect-rdf/turtle`)
325
+
326
+ RDF 1.2 [Turtle](https://www.w3.org/TR/rdf12-turtle/) and
327
+ [TriG](https://www.w3.org/TR/rdf12-trig/) for
328
+ [`@thomasfosterau/effect-rdf`](#modules).
329
+
330
+ - Full RDF 1.2 grammar: triple terms `<<( s p o )>>`, reified triples
331
+ `<< s p o ~ r >>`, annotations `{| ... |}`, `VERSION`, base direction, plus
332
+ RDF 1.1 documents.
333
+ - `parse` returns the dataset, the declared prefixes and the final base IRI;
334
+ `parseQuads` returns statements in document order.
335
+ - `parseStream` is incremental: statements are emitted as soon as they are
336
+ complete, whatever the chunk boundaries.
337
+ - The parsers are a compiled
338
+ [`@thomasfosterau/effect-parse`](https://www.npmjs.com/package/@thomasfosterau/effect-parse)
339
+ grammar built on the `Grammar` terminals below, emitting quads as it reads.
340
+ `vp run --filter ./packages/core bench:turtle` times them on the W3C suites (7.6 MB) and on synthetic Turtle,
341
+ TriG and N-Triples-like documents (10–29 MB), whole and in 64 kB chunks:
342
+ 15–32 MB/s on Node 22, 1.1–1.25× the time of the hand-written parser they
343
+ replaced, with the same quads, errors and error positions on every suite
344
+ file, on 6,000 mutated copies of them, and when streamed in chunks of 1 to
345
+ 1,000 characters. Nesting is limited to about 900 levels of `[ … ]`.
346
+ - Pretty serializers: prefixed names (from `prefixes`, optionally
347
+ `autoPrefixes`), `@base` relativisation, `;`/`,` grouping, `a`, inline `[]`
348
+ blank nodes, `( ... )` collections, numeric/boolean shorthand, annotation
349
+ syntax for reified asserted triples, deterministic order.
350
+ `serializeStream` writes in constant memory (grouping consecutive
351
+ statements only).
352
+ - Passes every W3C rdf11/rdf12 Turtle and TriG syntax and evaluation test, and
353
+ every positive test round-trips through the serializers.
354
+
355
+ ```ts
356
+ import { Effect } from 'effect';
357
+ import { Turtle, TriG } from '@thomasfosterau/effect-rdf/turtle';
358
+
359
+ const program = Effect.gen(function* () {
360
+ const { dataset, prefixes, baseIri } = yield* Turtle.parse(`
361
+ PREFIX ex: <http://example.org/>
362
+ ex:alice a ex:Person ;
363
+ ex:knows [ ex:name "Bob" ] ;
364
+ ex:age 42 {| ex:source ex:census |} .
365
+ `);
366
+ return yield* Turtle.serialize(dataset, { prefixes, autoPrefixes: true });
367
+ });
368
+ ```
369
+
370
+ ### Grammar
371
+
372
+ `Grammar` exposes the Turtle grammar's terminals as
373
+ [`@thomasfosterau/effect-parse`](https://www.npmjs.com/package/@thomasfosterau/effect-parse)
374
+ parsers (an optional peer dependency of effect-rdf, needed by this subpath), for the grammars that include
375
+ them: N3 (`/n3`) and ShExC (`/shex`) are built on them. The
376
+ terminals Turtle shares with N-Triples (`iriRef`, `blankNodeLabel`, `langTag`,
377
+ `langDir`, `stringLiteralQuote`, `uchar`, `echar` and the character classes)
378
+ come from `/ntriples`' `Grammar` and are re-exported here.
379
+
380
+ | Parser | Terminal | Value |
381
+ | ------------------------------------------------ | ---------------------------------- | -------------------------------------------- |
382
+ | `iriRef` | `IRIREF` | the IRI, `UCHAR`s decoded (not resolved) |
383
+ | `prefixedName` | `PNAME_LN`, `PNAME_NS` | `{ prefix, local }`, `PN_LOCAL_ESC`s decoded |
384
+ | `pnPrefix`, `pnLocal`, `plx` | `PN_PREFIX`, `PN_LOCAL`, `PLX` | the text |
385
+ | `blankNodeLabel` | `BLANK_NODE_LABEL` | the label after `_:` |
386
+ | `langTag`, `langDir` | `LANGTAG`, `LANG_DIR` | the tag, or `{ language, direction }` |
387
+ | `stringLiteral` (and the four forms) | `STRING_LITERAL_QUOTE`, … | the value, escapes decoded |
388
+ | `integer`, `decimal`, `double`, `numericLiteral` | `INTEGER`, `DECIMAL`, `DOUBLE` | the lexical form (and datatype) |
389
+ | `uchar`, `echar`, `booleanLiteral` | `UCHAR`, `ECHAR`, `true` / `false` | the character or keyword |
390
+ | `ws`, `comment`, `skip` | `WS`, comments | — |
391
+
392
+ The character classes (`pnCharsBase`, `pnCharsU`, `pnChars`, `hex`, …) are
393
+ token sets for `Token.oneOf` / `Token.takeWhile`. The parsers neither skip
394
+ whitespace (except `skip`) nor resolve IRIs: a grammar adds its own
395
+ whitespace and resolves names, typically after parsing so that it can be
396
+ compiled.
397
+
398
+ ```ts
399
+ import * as Parser from '@thomasfosterau/effect-parse/Parser';
400
+ import { Grammar } from '@thomasfosterau/effect-rdf/turtle';
401
+
402
+ const iri = Parser.preceded(Grammar.skip, Parser.alt(Grammar.iriRef, Grammar.prefixedName));
403
+ Parser.parse(iri, ' ex:alice'); // Result.succeed({ prefix: 'ex', local: 'alice' })
404
+ ```
405
+
406
+ ### Test manifests
407
+
408
+ `Manifest` reads W3C test manifests (following `mf:include`) and is meant for
409
+ other packages' conformance tests:
410
+
411
+ ```ts
412
+ import * as fs from 'node:fs';
413
+ import { Effect } from 'effect';
414
+ import { Manifest } from '@thomasfosterau/effect-rdf/turtle';
415
+
416
+ const read = Manifest.fileReader('https://w3c.github.io/rdf-tests/', '/path/to/rdf-tests/', (p) =>
417
+ Effect.sync(() => fs.readFileSync(p, 'utf8')),
418
+ );
419
+ const entries = Effect.runSync(
420
+ Manifest.load('https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-turtle/manifest.ttl', read),
421
+ );
422
+ // entries: { id, kind: 'TestTurtleEval', name, action: Option<iri>, result: Option<iri>, dataset, ... }
423
+ ```
424
+
425
+ ## Notation3 (`@thomasfosterau/effect-rdf/n3`)
426
+
427
+ Effect-idiomatic [Notation3](https://w3c.github.io/N3/spec/) for
428
+ [`@thomasfosterau/effect-rdf`](#modules):
429
+
430
+ - **`Model`** — the N3 data model: core IRIs, blank nodes, literals and
431
+ variables plus N3 `Collection`s (lists) and `Formula`s (graph terms), with
432
+ statements allowing any term in any position. Its schemas carry JSON codecs
433
+ (`Schema.toCodecJson`): core's JSON terms plus `{ "type": "collection", "items" }`
434
+ and `{ "type": "formula", "statements" }`.
435
+ - **`N3`** — a conformant parser (`parse` → `Model.Document`), a
436
+ pretty-printing serializer (`serializeDocument`), and the core
437
+ `Serialization` `parser` / `serializer` / `codec` over RDF `Dataset`s.
438
+ - **`Mapping`** — the documented mapping between N3 and RDF datasets
439
+ (formulas ⇄ named graphs identified by blank nodes, collections ⇄
440
+ `rdf:first`/`rdf:rest` lists).
441
+ - **`Isomorphism`** — blank-node- and variable-aware comparison of formulas.
442
+ - **`Reasoner`** — a forward-chaining reasoner for `{ … } => { … }` rules
443
+ with a few `math:`, `string:`, `list:` and `log:` builtins.
444
+
445
+ ### Usage
446
+
447
+ ```ts
448
+ import { Effect } from 'effect';
449
+ import { N3, Model } from '@thomasfosterau/effect-rdf/n3';
450
+
451
+ const program = Effect.gen(function* () {
452
+ const doc = yield* N3.parse(`
453
+ @prefix : <http://example.org/> .
454
+ :socrates a :Man .
455
+ { ?x a :Man } => { ?x a :Mortal } .
456
+ `);
457
+ doc.formula.statements.length; // 2
458
+ Model.variables(doc.formula); // [?x]
459
+
460
+ // Pretty-print with the document's prefixes
461
+ const text = N3.serializeDocument(doc);
462
+
463
+ // RDF-compatible documents can be read as core datasets; formulas become
464
+ // named graphs identified by blank nodes.
465
+ const { dataset } = yield* N3.parser.parse(
466
+ '@prefix : <http://example.org/> . :alice :believes { :bob :knows :carol } .',
467
+ );
468
+ // …and datasets serialise back to N3 with nested formulas.
469
+ const n3 = yield* N3.serialize(dataset);
470
+ });
471
+ ```
472
+
473
+ #### Reasoning
474
+
475
+ ```ts
476
+ import { Effect } from 'effect';
477
+ import { N3, Reasoner } from '@thomasfosterau/effect-rdf/n3';
478
+
479
+ const program = Effect.gen(function* () {
480
+ const doc = yield* N3.parse(`
481
+ @prefix : <http://example.org/> .
482
+ @prefix math: <http://www.w3.org/2000/10/swap/math#> .
483
+ :socrates a :Man ; :age 70 .
484
+ { ?x a :Man } => { ?x a :Mortal } .
485
+ { ?x :age ?a . (?a 1) math:sum ?b } => { ?x :nextAge ?b } .
486
+ `);
487
+ const { closure, inferred } = yield* Reasoner.reason(doc);
488
+ return N3.serializeDocument(inferred, { prefixes: doc.prefixes });
489
+ // :socrates a :Mortal ; :nextAge 71 .
490
+ });
491
+ ```
492
+
493
+ Builtins: a pragmatic subset of `math:`, `string:`, `list:` and `log:`
494
+ (`includes`, `notIncludes`, `conclusion`, `collectAllIn`, `forAllIn`, …).
495
+ `Reasoner.query(formula, pattern)` answers conjunctive queries. On the CG
496
+ reasoner suite 76 of 89 tests pass; the rest need document dereferencing
497
+ (`log:semantics`), `time:` builtins or have broken expected results (listed
498
+ in `test/n3/reasoner-conformance.test.ts`).
499
+
500
+ ### Mapping to RDF datasets
501
+
502
+ | N3 | RDF dataset |
503
+ | ------------------------------------------------- | ----------------------------------------------------- |
504
+ | top-level statement | quad in the default graph |
505
+ | formula `{ … }` (each occurrence) | fresh blank node `_:g`; contents in named graph `_:g` |
506
+ | collection `( a b )` / `()` | `rdf:first`/`rdf:rest` chain / `rdf:nil` |
507
+ | variable `?x`, literal subject, non-IRI predicate | `RdfParseError` (not representable) |
508
+
509
+ Serialising a dataset writes graphs named by blank nodes that are used as
510
+ terms as nested formulas and folds well-formed lists into collections; graphs
511
+ named by IRIs, or unreferenced blank-node graphs, fail with
512
+ `RdfSerializeError`.
513
+
514
+ ### Syntax notes
515
+
516
+ Following the current CG specification and its test suite:
517
+
518
+ - `<=` is `log:isImpliedBy`, `=` is `owl:sameAs`, `=>` is `log:implies`.
519
+ - `{}` (an empty formula) is the literal `true`.
520
+ - An undeclared empty prefix `:` means `<#>`.
521
+ - Blank node labels are scoped to the whole document.
522
+ - Legacy `@forAll` / `@forSome` are accepted (mapped to variables / fresh
523
+ blank nodes); `@keywords` is rejected.
524
+ - `"…"^^i18n:ar-eg_rtl` is read (and written) as a directional
525
+ language-tagged string.
526
+
527
+ The parser is a compiled
528
+ [`@thomasfosterau/effect-parse`](https://www.npmjs.com/package/@thomasfosterau/effect-parse)
529
+ grammar (an optional peer dependency of effect-rdf, needed by this subpath) that includes the Turtle
530
+ grammar's terminals from `@thomasfosterau/effect-rdf/turtle` (`Grammar`), as the
531
+ N3 grammar is defined on Turtle's. `vp run --filter ./packages/core bench:n3` times it on every `.n3` and `.ttl` file of the N3
532
+ suites and on a large synthetic document.
533
+
534
+ ## JSON-LD (`@thomasfosterau/effect-rdf/jsonld`)
535
+
536
+ A self-contained, Effect-idiomatic [JSON-LD 1.1](https://www.w3.org/TR/json-ld11-api/) processor
537
+ for [`@thomasfosterau/effect-rdf`](#modules): context processing, expansion, compaction,
538
+ flattening, [framing](https://www.w3.org/TR/json-ld11-framing/), conversion to and from RDF, and a
539
+ `Serialization.Codec` for `application/ld+json`.
540
+
541
+ It passes the W3C JSON-LD 1.1 test suites for expand, compact, flatten, toRdf, fromRdf,
542
+ remote-doc and framing. The only tests it skips are JSON-LD 1.0-only tests and the ones that need
543
+ generalized RDF.
544
+
545
+ ### Usage
546
+
547
+ ```ts
548
+ import { Effect } from 'effect';
549
+ import { DocumentLoader, JsonLd } from '@thomasfosterau/effect-rdf/jsonld';
550
+
551
+ const doc = {
552
+ '@context': { '@vocab': 'http://schema.org/', knows: { '@type': '@id' } },
553
+ '@id': 'http://example.org/alice',
554
+ name: 'Alice',
555
+ knows: 'http://example.org/bob',
556
+ };
557
+
558
+ const program = Effect.gen(function* () {
559
+ const expanded = yield* JsonLd.expand(doc);
560
+ const compacted = yield* JsonLd.compact(doc, { schema: 'http://schema.org/' });
561
+ const flattened = yield* JsonLd.flatten(doc);
562
+ const framed = yield* JsonLd.frame(doc, { '@context': doc['@context'], name: {} });
563
+ const dataset = yield* JsonLd.toRdf(doc, { rdfDirection: 'i18n-datatype' });
564
+ const json = yield* JsonLd.fromRdf(dataset, { useNativeTypes: true });
565
+ });
566
+ ```
567
+
568
+ Every algorithm returns an `Effect` that fails with a `JsonLdError`. The error's `code` is one of
569
+ the JSON-LD error codes, such as `"invalid @id value"` or `"loading remote context failed"`.
570
+
571
+ ### Remote documents and contexts
572
+
573
+ Remote contexts and documents (a string input is treated as a URL) are loaded through the
574
+ `DocumentLoader` service:
575
+
576
+ ```ts
577
+ import { FetchHttpClient } from 'effect/http';
578
+
579
+ // Over HTTP. This follows the JSON-LD content-type and Link-header rules, extracts JSON-LD from
580
+ // HTML, and caches documents.
581
+ const live = DocumentLoader.layerHttp.pipe(Layer.provide(FetchHttpClient.layer));
582
+
583
+ // From memory, useful for tests and for pinning well-known contexts.
584
+ const pinned = DocumentLoader.layerStatic({
585
+ 'https://schema.org/': { '@context': { '@vocab': 'https://schema.org/' } },
586
+ });
587
+
588
+ JsonLd.expand('https://example.org/data.jsonld').pipe(Effect.provide(live));
589
+ ```
590
+
591
+ Without a `DocumentLoader` in the environment, any remote reference fails with
592
+ `loading document failed` or `loading remote context failed`. You can also pass a loader for a
593
+ single call with `options.documentLoader`. `DocumentLoader.fromFetch` builds a loader from any
594
+ function that returns raw responses.
595
+
596
+ ### Codec
597
+
598
+ `JsonLd.codec` implements `Serialization.Codec` for the `application/ld+json` media type and the
599
+ `.jsonld` extension.
600
+
601
+ - **Parsing** runs toRdf.
602
+ - **Serialising** runs fromRdf. When you pass a `context` option, or `prefixes` (which become term
603
+ definitions), the result is then compacted.
604
+
605
+ ```ts
606
+ const roundTrip = (text: string) =>
607
+ Effect.gen(function* () {
608
+ const { dataset } = yield* JsonLd.parse(text, { baseIri: 'http://example.org/' });
609
+ return yield* JsonLd.serialize(dataset, {
610
+ prefixes: PrefixMap.make({ ex: 'http://example.org/' }),
611
+ });
612
+ });
613
+ ```
614
+
615
+ ### Notes
616
+
617
+ - Generalized RDF, where a blank node is used as a predicate, cannot be represented by the core
618
+ term model, so `produceGeneralizedRdf` has no effect.
619
+ - When `rdfDirection` is not set, `@direction` is dropped and the value becomes a plain
620
+ language-tagged literal. `fromRdf` turns RDF 1.2 directional language-tagged literals into
621
+ `@direction`.
622
+
623
+ ## RDF/XML (`@thomasfosterau/effect-rdf/rdfxml`)
624
+
625
+ [RDF 1.2 XML Syntax](https://www.w3.org/TR/rdf12-xml/) (RDF/XML, `application/rdf+xml`)
626
+ for [`@thomasfosterau/effect-rdf`](#modules).
627
+
628
+ - `RdfXml` — parser and serializer implementing the core `Serialization` interfaces.
629
+ - `Xml` — the small namespace-aware, non-validating XML 1.0 parser it is built on
630
+ (internal DTD entities and attribute defaults, CDATA, comments, PIs) plus Exclusive XML
631
+ Canonicalization, usable on its own.
632
+
633
+ The XML parser is a compiled
634
+ [`@thomasfosterau/effect-parse`](https://www.npmjs.com/package/@thomasfosterau/effect-parse)
635
+ grammar (an optional peer dependency of effect-rdf, needed by this subpath). It never loads external entities, caps entity
636
+ expansion at 1,000,000 characters (defeating "billion laughs"), and limits entity references to
637
+ 64 levels of nesting and elements to 1,024, reporting an `XmlParseError` rather than exhausting
638
+ the stack. `vp run --filter ./packages/core bench:rdfxml` times it on every RDF/XML and SPARQL XML results file of the W3C suites
639
+ (about 38 MB/s) and on a 10.8 MB synthetic RDF/XML document (about 15 MB/s).
640
+
641
+ The parser passes every test of the W3C RDF 1.1 and RDF 1.2 RDF/XML test suites, including
642
+ `rdf:version`, base direction via `its:dir`, triple terms via `rdf:parseType="Triple"` and
643
+ annotations via `rdf:annotation` / `rdf:annotationNodeID`.
644
+
645
+ ### Parsing
646
+
647
+ ```ts
648
+ import { Effect } from 'effect';
649
+ import { Dataset } from '@thomasfosterau/effect-rdf';
650
+ import { RdfXml } from '@thomasfosterau/effect-rdf/rdfxml';
651
+
652
+ const program = Effect.gen(function* () {
653
+ const { dataset, prefixes } = yield* RdfXml.parse(
654
+ `<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
655
+ xmlns:foaf="http://xmlns.com/foaf/0.1/">
656
+ <foaf:Person rdf:about="#me" foaf:name="Alice"/>
657
+ </rdf:RDF>`,
658
+ { baseIri: 'http://example.org/profile' },
659
+ );
660
+ console.log(Dataset.toNQuads(dataset));
661
+ });
662
+ ```
663
+
664
+ Errors are `Serialization.RdfParseError`s with `line` and `column`.
665
+
666
+ `RdfXml.parseQuads(input, options?)` returns the statements in document order (duplicates kept)
667
+ with the prefixes and base, without building a dataset: the fastest way to stream a document into
668
+ another store.
669
+
670
+ `RdfXml.parseDocument(doc, options?)` reads an already-parsed `Xml.Document` instead, for formats
671
+ that embed RDF/XML in a larger XML document (XMP, SVG metadata): pass a document whose root is
672
+ the embedded `rdf:RDF` element.
673
+
674
+ ### Serializing
675
+
676
+ ```ts
677
+ import { PrefixMap } from '@thomasfosterau/effect-rdf';
678
+
679
+ const xml = RdfXml.serialize(dataset, {
680
+ prefixes: PrefixMap.make({ foaf: 'http://xmlns.com/foaf/0.1/' }),
681
+ baseIri: 'http://example.org/profile', // written as xml:base; IRIs are relativised
682
+ });
683
+ ```
684
+
685
+ The output uses typed node elements, nests blank nodes that are referenced exactly once
686
+ (`rdf:parseType="Resource"` when untyped), declares only the namespaces it uses and generates
687
+ `ns1`, `ns2`, … for predicates outside the configured prefixes. Directional literals and triple
688
+ terms are written with `its:dir` / `rdf:parseType="Triple"` and an `rdf:version="1.2"`
689
+ announcement. Serialisation fails with `RdfSerializeError` for quads in named graphs, triple
690
+ terms in the subject position and predicates that cannot be written as XML QNames.
691
+
692
+ Options: `prefixes`, `baseIri`, `indent`, `nestBlankNodes`, `typedNodes`, `xmlDeclaration`.
693
+
694
+ ### Schema
695
+
696
+ `RdfXml.codec.schema` is a `Schema.Codec<Dataset, string>`.
697
+
698
+ ## XMP (`@thomasfosterau/effect-rdf/xmp`)
699
+
700
+ [XMP](https://www.iso.org/standard/75163.html) (Extensible Metadata Platform, ISO 16684-1) for
701
+ [`@thomasfosterau/effect-rdf`](#modules): the metadata format of PDF (the catalog's `/Metadata`
702
+ stream, required by PDF/A and PDF/UA), JPEG, PNG, TIFF, WebP, SVG and MP4. XMP is a constrained
703
+ profile of RDF/XML, so the parser is
704
+ [`@thomasfosterau/effect-rdf/rdfxml`](#rdfxml-thomasfosteraueffect-rdfrdfxml); this package adds the packet wrapper, the XMP data
705
+ model, typed accessors for the core schemas and a serializer that writes the canonical form XMP
706
+ consumers expect.
707
+
708
+ | Module | Contents |
709
+ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
710
+ | `Xmp` | `parse` / `serialize` (the core `Serialization` interfaces), `parseMetadata`, `serializeMetadata` |
711
+ | `Metadata` | the data model: `Simple`, `Struct`, `ArrayValue`, qualifiers; `fromDataset` / `toDataset` |
712
+ | `Packet` | `find` / `findAll` packets in bytes (UTF-8, UTF-16, UTF-32), `unwrap`, `wrap`, `wrapToSize` |
713
+ | `Extract` | `jpeg` (APP1, with Extended XMP) and `png` (`iTXt`, stored or compressed) |
714
+ | `DublinCore`, `XmpBasic`, `XmpRights`, `XmpMM`, `Pdf`, `PdfA` | typed getters and setters |
715
+ | `Photoshop`, `Exif`, `Tiff` | typed getters and setters for the image schemas' common properties |
716
+ | `Namespaces` | the schema namespace IRIs and their conventional prefixes |
717
+
718
+ It needs the optional peer `@thomasfosterau/effect-parse` (the XML grammar behind `/rdfxml`).
719
+
720
+ ### Reading
721
+
722
+ ```ts
723
+ import { Effect, Option } from 'effect';
724
+ import { DublinCore, PdfA, Xmp, XmpBasic } from '@thomasfosterau/effect-rdf/xmp';
725
+
726
+ const program = (bytes: Uint8Array) =>
727
+ Effect.gen(function* () {
728
+ // The first packet in any file, or the whole input when it has none.
729
+ const meta = yield* Xmp.parseMetadata(bytes);
730
+ const title = DublinCore.title(meta); // Option<string>, the x-default alternative
731
+ const german = DublinCore.title(meta, 'de');
732
+ const authors = DublinCore.creator(meta); // Option<ReadonlyArray<string>>
733
+ const created = XmpBasic.createDate(meta); // the ISO 8601 string XMP stores
734
+ const pdfa = PdfA.identification(meta); // Option<{ part, conformance, amendment, revision }>
735
+ });
736
+ ```
737
+
738
+ `Xmp.parse(text)` returns the RDF itself (a `Serialization.ParseResult`). It accepts a packet,
739
+ a bare `<x:xmpmeta>` (or the older `<x:xapmeta>`), a bare `rdf:RDF`, or any XML document with
740
+ an `rdf:RDF` inside (an SVG's `<metadata>`), and the forms ISO 16684-1 still requires readers to
741
+ accept: descriptions with differing or no `rdf:about` (merged into one resource) and property
742
+ attributes. Errors are `RdfParseError`s with `line` / `column`; RDF that does not fit the XMP
743
+ data model (a cycle, a triple term, a property given twice) fails too.
744
+
745
+ `Metadata.fromDataset` reads the data model from RDF: literals and `rdf:resource` URIs are
746
+ `Simple` values (`xml:lang` is `Simple.language`), `rdf:parseType="Resource"` nodes are
747
+ `Struct`s, `rdf:Bag` / `rdf:Seq` / `rdf:Alt` are `ArrayValue`s, and an `rdf:value` node carries
748
+ its other properties as qualifiers. Entry order carries no meaning in XMP; `fromDataset` sorts
749
+ entries by IRI so serialization is deterministic.
750
+
751
+ ### Writing
752
+
753
+ ```ts
754
+ import { DateTime, Effect, Option, pipe } from 'effect';
755
+ import { DublinCore, Metadata, PdfA, Xmp, XmpBasic } from '@thomasfosterau/effect-rdf/xmp';
756
+
757
+ const meta = pipe(
758
+ Metadata.empty,
759
+ DublinCore.setTitle('Annual report'),
760
+ DublinCore.setTitle('Jahresbericht', 'de'),
761
+ DublinCore.setCreator(['Ada Lovelace']),
762
+ XmpBasic.setCreateDate(DateTime.makeUnsafe('2026-10-01T09:30:00Z')),
763
+ PdfA.setIdentification({
764
+ part: 2,
765
+ conformance: Option.some('B'),
766
+ amendment: Option.none(),
767
+ revision: Option.none(),
768
+ }),
769
+ );
770
+
771
+ const packet = Xmp.serializeMetadata(meta, { toolkit: 'my-app 1.0' });
772
+ ```
773
+
774
+ The serializer writes the canonical XMP form: an `<x:xmpmeta>` wrapper, one `rdf:Description`
775
+ per schema namespace with that namespace declared on it, property elements only (never
776
+ attributes or typed nodes), structures as `rdf:parseType="Resource"` and qualifiers in the
777
+ `rdf:value` form, wrapped in an `<?xpacket?>` packet. Options:
778
+
779
+ - `packet` (default `true`) — wrap in a packet.
780
+ - `writable` (default `true`) — `end="w"` or `end="r"`.
781
+ - `padding` (default 2048 bytes for a writable packet) — whitespace for in-place edits.
782
+ - `size` — the exact byte length of the packet, for rewriting a packet in place; fails when the
783
+ XMP does not fit.
784
+ - `toolkit` — the `x:xmptk` attribute; `prefixes` — prefixes to prefer over the conventional
785
+ ones (`Namespaces.prefixes`); `indent` (default one space).
786
+
787
+ `PdfA.setExtensionSchemas` writes the `pdfaExtension:schemas` descriptions PDF/A-1 to PDF/A-3
788
+ require for properties outside the predefined schemas; `PdfA.extensionSchemas` reads them.
789
+
790
+ ### Packets and image files
791
+
792
+ ```ts
793
+ import { Extract, Packet } from '@thomasfosterau/effect-rdf/xmp';
794
+
795
+ Packet.find(bytes); // Option<Packet>: offset, length, encoding, writable, text
796
+ Packet.findAll(bytes);
797
+ Extract.jpeg(bytes); // Effect<Option<{ standard, extended }>, XmpExtractError>
798
+ Extract.png(bytes); // Effect<Option<string>, XmpExtractError>
799
+ ```
800
+
801
+ `Packet.find` scans any file (TIFF, WebP, GIF, MP4, PDF, …) for a UTF-8, UTF-16 or UTF-32
802
+ packet (either byte order) without parsing it. `Extract.jpeg` reads the APP1 packet and
803
+ reassembles Extended XMP; `Extract.png` reads the `iTXt` chunk with the keyword
804
+ `XML:com.adobe.xmp`, inflating a compressed one with the platform's `DecompressionStream` up to `maxXmpBytes` (4 MiB by default). Each
805
+ has a synchronous `…Result` variant; `Extract.pngResult` fails on a compressed chunk, since
806
+ inflating is asynchronous.
807
+
808
+ ## CSV on the Web (`@thomasfosterau/effect-rdf/csvw`)
809
+
810
+ [CSV on the Web](https://www.w3.org/TR/tabular-data-primer/) (CSVW), on `@thomasfosterau/effect-rdf/csvw`
811
+ (one subpath per module: `…/csvw/Processor`, `…/csvw/Metadata`, …): the
812
+ [metadata vocabulary](https://www.w3.org/TR/tabular-metadata/), the
813
+ [annotated tabular data model](https://www.w3.org/TR/tabular-data-model/) with
814
+ validation, and the [CSV2RDF](https://www.w3.org/TR/csv2rdf/) and
815
+ [CSV2JSON](https://www.w3.org/TR/csv2json/) conversions.
816
+
817
+ The subpath works on **already-parsed tables**: a `Table.Source` is a URL and
818
+ its records as cell strings, in file order. Splitting CSV text into records is
819
+ the job of a CSV scanner (`@thomasfosterau/effect-csv`, which will plug in
820
+ here); everything after that — the dialect's skipped rows, header rows, comment
821
+ rows, blank rows, skipped columns and trimming, embedded metadata, datatypes,
822
+ keys and the conversions — is done here.
823
+
824
+ ### Usage
825
+
826
+ ```ts
827
+ import * as Effect from 'effect/Effect';
828
+ import { Csv2Json, Csv2Rdf, Processor, Report } from '@thomasfosterau/effect-rdf/csvw';
829
+
830
+ const metadata = `{
831
+ "@context": "http://www.w3.org/ns/csvw",
832
+ "url": "tree-ops.csv",
833
+ "tableSchema": {
834
+ "columns": [
835
+ {"name": "GID", "titles": "GID", "datatype": "integer"},
836
+ {"name": "on_street", "titles": "On Street"},
837
+ {"name": "inventory_date", "titles": "Inventory Date",
838
+ "datatype": {"base": "date", "format": "M/d/yyyy"}}
839
+ ],
840
+ "aboutUrl": "#gid-{GID}"
841
+ }
842
+ }`;
843
+
844
+ const program = Effect.gen(function* () {
845
+ const { group, report } = yield* Processor.fromMetadata(
846
+ 'http://example.org/tree-ops.csv-metadata.json',
847
+ metadata,
848
+ { validate: true },
849
+ );
850
+ return {
851
+ valid: Report.isValid(report),
852
+ dataset: Csv2Rdf.toDataset(group), // standard mode
853
+ json: Csv2Json.toJson(group, { mode: 'minimal' }),
854
+ };
855
+ }).pipe(
856
+ Effect.provideService(
857
+ Processor.Loader,
858
+ Processor.makeLoader([
859
+ {
860
+ url: 'http://example.org/tree-ops.csv',
861
+ rows: [
862
+ ['GID', 'On Street', 'Inventory Date'],
863
+ ['1', 'ADDISON AV', '10/18/2010'],
864
+ ['2', 'EMERSON ST', '6/2/2010'],
865
+ ],
866
+ },
867
+ ]),
868
+ ),
869
+ );
870
+ // json: [{ "@id": "http://example.org/tree-ops.csv#gid-1", "GID": 1,
871
+ // "on_street": "ADDISON AV", "inventory_date": "2010-10-18" }, …]
872
+ ```
873
+
874
+ This example is run by [`test/csvw/readme-example.test.ts`](test/csvw/readme-example.test.ts).
875
+
876
+ ### Modules
877
+
878
+ | Module | Contents |
879
+ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
880
+ | `Processor` | `fromMetadata(url, text, options)` and `fromTabular(url, options)` → `Processed` (`group`, `metadata`, `report`); the `Loader` service (`table(url, dialect)`, `document(url)`) with `makeLoader(sources, documents)` and `layerSources`; `defaultLocations(url)`; `LoadError` |
881
+ | `Metadata` | The normalized description model as `Schema`s (`TableGroup`, `Table`, `TableSchema`, `Column`, `Dialect`, `ForeignKey`, `Reference`, `Transformation`, `Inherited`, `LanguageMap`, `CommonProperties`); `decode` / `decodeJson`; `defaultDialect`; inheritance and compatibility helpers; `MetadataError` |
882
+ | `Table` | `Source` (the parsed input); the annotated model (`TableGroup`, `Table`, `Column`, `Row`, `Cell`, `CellValue`); `read` (dialect row flags and embedded metadata), `annotate`, `parseCell`, `validateKeys` |
883
+ | `Datatype` | The built-in datatypes (`builtIn`, `iri`, `isNumeric`, …), the `Datatype` description schema, `parse` (formats, length and value constraints) and `checkFormat` |
884
+ | `Report` | `Issue` (`level`, `message`, `table`, `row`, `column`) and `Report` schemas; `errors`, `warnings`, `isValid` |
885
+ | `Csv2Rdf` | `toDataset(group, { mode })` → core `Dataset` (`standard` or `minimal`); `convert` (an Effect with a span) |
886
+ | `Csv2Json` | `toJson(group, { mode })` → JSON (`standard` or `minimal`, with nested objects); `convert` |
887
+
888
+ #### Processing
889
+
890
+ `fromMetadata` starts from a metadata document: it is decoded (schemas and
891
+ dialects referenced by URL are fetched through the `Loader`), and each table
892
+ it describes is read through the `Loader` with the table's dialect. Every
893
+ table's embedded metadata (the titles in its header rows) is checked for
894
+ compatibility with its schema.
895
+
896
+ `fromTabular` starts from a tabular data file and takes its metadata from the
897
+ `metadata` option (user-supplied, overriding metadata), else the first
898
+ document at a default location (`{+url}-metadata.json`, `csv-metadata.json`)
899
+ that describes the file, else the file's embedded metadata (column titles from
900
+ the header row).
901
+
902
+ With `validate: true` the processor is a validator: metadata incompatible with
903
+ the data and invalid cells are **errors**, and primary keys and foreign keys
904
+ (across the group's tables) are checked. Without it, those are **warnings**
905
+ and keys are not checked. Metadata problems the specification calls errors
906
+ (invalid JSON, a bad `@context`, a blank-node `@id`, conflicting datatype
907
+ constraints, a foreign key to a missing table, …) fail with `MetadataError`;
908
+ properties with invalid values are dropped with a warning. Every issue carries
909
+ the table URL and, for cells and rows, the source row and column numbers.
910
+
911
+ #### Metadata and datatypes
912
+
913
+ Decoding implements the metadata vocabulary's property checks and
914
+ normalization: link properties are resolved against `@base` (or the document
915
+ URL), natural-language properties become language maps (`und` when no
916
+ `@language`), common properties are checked against the JSON-LD restrictions
917
+ and normalized (`@id` expanded and resolved, strings to value objects), and
918
+ inherited properties are resolved column → schema → table → group.
919
+
920
+ Cells are parsed per the tabular data model: whitespace normalization,
921
+ `default`, `null`, `separator` lists, `required`, `lang`, `textDirection` and
922
+ virtual columns. Datatypes cover every built-in XSD type and the CSVW
923
+ aliases; `format` patterns for numbers (the UAX 35 subset: `0`, `#`, grouping,
924
+ `E`, `+`, `-`, `%`, `‰`, and `decimalChar` / `groupChar`), dates and times
925
+ (`yyyy`, `MM`, `M`, `dd`, `d`, `HH`, `mm`, `ss`, `S…`, `X…`, `x…`), booleans
926
+ (`true|false`) and regular expressions for other types, with `length`,
927
+ `minLength`, `maxLength` and `minimum` / `maximum` / `min|maxInclusive` /
928
+ `min|maxExclusive` constraints. URI templates (`aboutUrl`, `propertyUrl`,
929
+ `valueUrl`) are expanded with RFC 6570 level 4.
930
+
931
+ ### Not yet supported
932
+
933
+ - **Locating metadata over HTTP**: `Link: rel="describedby"` headers on a
934
+ tabular data file and a site-wide `/.well-known/csvm` configuration are not
935
+ consulted (the default locations are, through the `Loader`). A caller that
936
+ has fetched such metadata can pass it as `metadata`.
937
+ - **Parsing CSV text**: the `Loader` hands back parsed records; a CSV scanner
938
+ (`@thomasfosterau/effect-csv`) will provide them.
939
+ - **Provenance**: the optional PROV triples of CSV2RDF are not generated.
940
+ - **Transformations** are decoded and annotated but not run.
941
+
942
+ ## SHACL (`@thomasfosterau/effect-rdf/shacl`)
943
+
944
+ An Effect-native [SHACL Core](https://www.w3.org/TR/shacl/) validator, on `@thomasfosterau/effect-rdf/shacl` (one subpath per module:
945
+ `…/shacl/Validator`, `…/shacl/Shapes`, …).
946
+
947
+ - **`Vocabulary`**: the `sh:` namespace as a typed vocabulary (`sh.NodeShape`, `sh.minCount`, …).
948
+ - **`Shapes`**: parses a shapes graph (`Dataset`) into a typed model of node and
949
+ property shapes, targets and constraints. Ill-formed shapes graphs fail with a
950
+ `ShapesGraphError`.
951
+ - **`Path`**: SHACL property paths (predicate, sequence, alternative, inverse,
952
+ zero-or-more, one-or-more, zero-or-one): model, evaluation, RDF encoding.
953
+ - **`Validator`**: `validate(data, shapes, options)`, a `Validator` service and
954
+ layer that pre-compiles a shapes graph, and a `Schema` that only accepts
955
+ conforming datasets.
956
+ - **`ValidationReport`**: the typed report model, convertible to and from the
957
+ standard RDF validation report graph; `ValidationReportSchema` has a JSON
958
+ codec (`Schema.toCodecJson`) that mirrors the model.
959
+ - **`Sparql`**: the `SparqlConstraintEvaluator` service interface, the extension
960
+ point for SHACL-SPARQL (`sh:sparql`, SPARQL-based constraint components,
961
+ `sh:target`), implemented by `@thomasfosterau/effect-rdf/sparql/shacl`.
962
+ - **`Message`**: `{?var}` / `{$var}` message template substitution.
963
+
964
+ All 98 SHACL Core tests of the W3C test suite pass (see [Conformance](#conformance)).
965
+ The 22 `sparql/` tests need a SPARQL evaluator and are run (and pass) by
966
+ [`@thomasfosterau/effect-rdf/sparql/shacl`](#shacl-sparql-thomasfosteraueffect-rdfsparqlshacl), which implements
967
+ `Sparql.SparqlConstraintEvaluator` on `@thomasfosterau/effect-rdf/sparql`.
968
+
969
+ ### Validating a data graph
970
+
971
+ ```ts
972
+ import { Effect } from 'effect';
973
+ import { Validator, ValidationReport } from '@thomasfosterau/effect-rdf/shacl';
974
+
975
+ const program = Effect.gen(function* () {
976
+ // shapesGraph and dataGraph are core `Dataset`s, e.g. parsed from Turtle.
977
+ const report = yield* Validator.validate(dataGraph, shapesGraph);
978
+ if (!report.conforms) {
979
+ console.log(ValidationReport.format(report));
980
+ for (const result of report.results) {
981
+ result.focusNode; // Term
982
+ result.resultPath; // Option<Path>
983
+ result.value; // Option<Term>
984
+ result.sourceShape; // NamedNode | BlankNode
985
+ result.sourceConstraintComponent; // e.g. sh:MinCountConstraintComponent
986
+ result.severity; // sh:Violation | sh:Warning | sh:Info | custom
987
+ result.messages; // sh:message values, or a default English message
988
+ }
989
+ }
990
+ // The standard RDF validation report:
991
+ const reportGraph = ValidationReport.toDataset(report);
992
+ });
993
+ ```
994
+
995
+ `validate` fails with a typed error when validation cannot produce a report:
996
+ `ShapesGraphError` (ill-formed shapes graph), `RecursiveShapeError`,
997
+ `UnsupportedFeatureError` (SHACL-SPARQL without an evaluator) or
998
+ `SparqlEvaluationError`.
999
+
1000
+ #### Options
1001
+
1002
+ ```ts
1003
+ Validator.validate(dataGraph, shapesGraph, {
1004
+ // Validate data ∪ shapes graph (e.g. when the data graph declares sh:shapesGraph,
1005
+ // or shapes and data live in the same document).
1006
+ includeShapesGraph: true,
1007
+ // SHACL-SPARQL constructs without a SparqlConstraintEvaluator: 'fail' (default) | 'ignore'.
1008
+ sparql: 'ignore',
1009
+ // A shape reached again for the same focus node while validating it:
1010
+ // 'fail' (default, RecursiveShapeError) | 'ignore' (treat as conforming).
1011
+ recursion: 'fail',
1012
+ // Attach nested results of sh:node / sh:not / sh:and / sh:or / sh:xone as sh:detail.
1013
+ details: true,
1014
+ });
1015
+ ```
1016
+
1017
+ Class-based targets (`sh:targetClass`, implicit class targets) and `sh:class`
1018
+ use `rdfs:subClassOf` from the data graph, as the specification requires.
1019
+
1020
+ ### A pre-compiled validator service
1021
+
1022
+ ```ts
1023
+ import { Effect } from 'effect';
1024
+ import { Validator } from '@thomasfosterau/effect-rdf/shacl';
1025
+
1026
+ const program = Effect.gen(function* () {
1027
+ const validator = yield* Validator.Validator;
1028
+ const report = yield* validator.validate(dataGraph);
1029
+ const ok = yield* validator.conforms(otherGraph);
1030
+ // Fails with ConformanceError (carrying the report) unless the graph conforms.
1031
+ const checked = yield* validator.check(dataGraph);
1032
+ }).pipe(Effect.provide(Validator.layer(shapesGraph)));
1033
+ ```
1034
+
1035
+ `Validator.layer` parses the shapes graph once (failing the layer with a
1036
+ `ShapesGraphError` if it is ill-formed).
1037
+
1038
+ ### As a Schema
1039
+
1040
+ ```ts
1041
+ import { Effect, Schema } from 'effect';
1042
+ import { Shapes, Validator } from '@thomasfosterau/effect-rdf/shacl';
1043
+
1044
+ const program = Effect.gen(function* () {
1045
+ const shapes = yield* Shapes.parse(shapesGraph);
1046
+ const PersonData = Validator.schema(shapes); // Schema<Dataset, Dataset>
1047
+ const valid = yield* Schema.decodeEffect(PersonData)(dataGraph);
1048
+ });
1049
+ ```
1050
+
1051
+ By default only `sh:Violation` results fail decoding; `{ failOn: 'warning' }` also fails on
1052
+ `sh:Warning`, and `{ failOn: 'info' }` on any result (the report's own `conforms`, which is false
1053
+ whenever there is any result at all). A failure is a `SchemaIssue.Composite` with one
1054
+ `InvalidValue` per failing result: its `message` annotation is the one-line
1055
+ `ValidationReport.formatResult(result)` and its `result` annotation the `ValidationResult` (focus
1056
+ node, path, constraint component, severity, messages).
1057
+
1058
+ To parse and validate in one decode, compose it with a syntax codec:
1059
+
1060
+ ```ts
1061
+ import { Turtle } from '@thomasfosterau/effect-rdf/turtle';
1062
+
1063
+ const PersonDocument = Turtle.codec.schema.pipe(Schema.decodeTo(Validator.schema(shapes)));
1064
+ const data = yield * Schema.decodeEffect(PersonDocument)(turtleText); // Dataset, parsed and valid
1065
+ ```
1066
+
1067
+ ### Inspecting a shapes graph
1068
+
1069
+ ```ts
1070
+ const shapes = yield * Shapes.parse(shapesGraph);
1071
+ for (const shape of shapes.shapes) {
1072
+ shape._tag; // 'NodeShape' | 'PropertyShape'
1073
+ shape.targets; // TargetNode | TargetClass | TargetSubjectsOf | TargetObjectsOf | SparqlTarget
1074
+ shape.constraints; // tagged union: MinCount, Datatype, Pattern, Node, Or, QualifiedMinCount, …
1075
+ }
1076
+ ```
1077
+
1078
+ ### SHACL-SPARQL
1079
+
1080
+ The core validator does not execute SPARQL. Provide a
1081
+ `Sparql.SparqlConstraintEvaluator` to evaluate `sh:sparql` constraints,
1082
+ SPARQL-based constraint components and `sh:target` — normally the one from
1083
+ `@thomasfosterau/effect-rdf/sparql/shacl`:
1084
+
1085
+ ```ts
1086
+ import { Evaluator } from '@thomasfosterau/effect-rdf/sparql/shacl';
1087
+
1088
+ Validator.validate(dataGraph, shapesGraph).pipe(Effect.provide(Evaluator.layer()));
1089
+ ```
1090
+
1091
+ or a custom implementation:
1092
+
1093
+ ```ts
1094
+ import { Effect, Layer } from 'effect';
1095
+ import { Sparql, Validator } from '@thomasfosterau/effect-rdf/shacl';
1096
+
1097
+ const EvaluatorLive = Layer.succeed(Sparql.SparqlConstraintEvaluator, {
1098
+ // One call per (shape, constraint, focus node); return the solutions as violations.
1099
+ evaluateConstraint: ({ dataGraph, shapesGraph, shape, constraint, focusNode, valueNodes }) =>
1100
+ Effect.succeed([{ value: focusNode }]),
1101
+ selectTargets: ({ dataGraph, target }) => Effect.succeed([]),
1102
+ });
1103
+
1104
+ Validator.validate(dataGraph, shapesGraph).pipe(Effect.provide(EvaluatorLive));
1105
+ ```
1106
+
1107
+ Without an evaluator, shapes graphs using these features fail with
1108
+ `UnsupportedFeatureError`, or the constructs are skipped with `{ sparql: 'ignore' }`.
1109
+
1110
+ ### Notes
1111
+
1112
+ - Validating recursive shapes is undefined in SHACL; this validator detects it.
1113
+ - `sh:message` templates are substituted: `{?var}` / `{$var}` refer to
1114
+ `this`, `value`, `path` and the constraint's parameters by local name
1115
+ (`{$minCount}`, `{$datatype}`, …); for SPARQL-based constraints also to
1116
+ the solution variables (`Violation.bindings`). Unknown placeholders are
1117
+ left as they are.
1118
+ - Literal comparisons (`sh:minInclusive`, `sh:lessThan`, …) follow SPARQL `<`:
1119
+ numerics across types, strings, booleans, date/time values (with the 14-hour
1120
+ timezone rule) and durations; incomparable values are violations.
1121
+
1122
+ ## SPARQL (`@thomasfosterau/effect-rdf/sparql`)
1123
+
1124
+ SPARQL 1.1 (and SPARQL 1.2) for [Effect](https://effect.website), built on
1125
+ [`@thomasfosterau/effect-rdf`](#modules): a complete parser and generator, the
1126
+ SPARQL algebra with an optimizer, a streaming query and update engine over a
1127
+ pluggable storage service, the standard results formats, a strongly-typed
1128
+ query builder, and — on `effect/http` — a [SPARQL 1.1
1129
+ Protocol](https://www.w3.org/TR/sparql11-protocol/) and [Graph Store HTTP
1130
+ Protocol](https://www.w3.org/TR/sparql11-http-rdf-update/)
1131
+ [server](#protocol-server) and [client](#protocol-client). Three subpaths build on the engine:
1132
+ [SHACL-SPARQL](#shacl-sparql-thomasfosteraueffect-rdfsparqlshacl) for the SHACL validator
1133
+ (`/sparql/shacl`), [knowledge-graph tools for `effect/ai`](#knowledge-graph-tools-thomasfosteraueffect-rdfsparqlai)
1134
+ (`/sparql/ai`) and the [SQL store](#sparql-over-sql-thomasfosteraueffect-rdfsparqlsql) (`/sparql/sql`).
1135
+ Everything here is platform-neutral; the Node server, the `effect-sparql-server`
1136
+ command and the typed `SparqlApi` `HttpApi` definition are
1137
+ [`@thomasfosterau/effect-sparql-node`](https://github.com/thomasfosterau/effect-rdf/tree/main/packages/sparql-node).
1138
+
1139
+ - **`Parser`** — a compiled LL(1) grammar (on
1140
+ [`@thomasfosterau/effect-parse`](https://www.npmjs.com/package/@thomasfosterau/effect-parse),
1141
+ reusing the Turtle terminals of `@thomasfosterau/effect-rdf/turtle/Grammar`) for
1142
+ SPARQL 1.1 Query and Update plus SPARQL 1.2 (triple terms `<<( s p o )>>`, reified triples
1143
+ `<< s p o ~ r >>`, annotations `{| … |}`, `VERSION`, `@lang--dir`), with all
1144
+ static checks (blank node scoping, grouping, `BIND`/`AS` scoping, `VALUES`
1145
+ arity, `DELETE` blank nodes, …) and `SparqlParseError`s carrying
1146
+ line/column.
1147
+ - **`Ast`** — typed AST (`Select`/`Construct`/`Ask`/`Describe`, all update
1148
+ operations) over core `Term`s, with constructors.
1149
+ - **`Generator`** — prints any AST back to SPARQL; parse → print → parse is
1150
+ stable.
1151
+ - **`Algebra`**, **`Translate`** — translation to the SPARQL algebra exactly
1152
+ per §18 (BGP, Path, Join, LeftJoin, Filter, Union, Graph, Extend, Minus,
1153
+ Group/aggregation, OrderBy, Project, Distinct, Reduced, Slice, Values,
1154
+ Service), S-expression formatting and tree utilities.
1155
+ - **`Optimizer`** — semantics-preserving filter pushdown and BGP join
1156
+ ordering by selectivity (using store estimates when available).
1157
+ - **`Expression`**, **`Functions`** — every SPARQL 1.1 operator and function
1158
+ (string, numeric, date/time, hashes, `RAND`/`NOW`/`UUID`/`STRUUID`/`BNODE`
1159
+ with per-query semantics), effective boolean value, XSD casts, the
1160
+ `ORDER BY` ordering, SPARQL 1.2 `TRIPLE`/`SUBJECT`/`PREDICATE`/`OBJECT`/
1161
+ `isTRIPLE`/`LANGDIR`/`hasLANG`/`hasLANGDIR`/`STRLANGDIR`, and extension
1162
+ functions registered by IRI.
1163
+ - **`Store`** — the `QuadStore` service implemented by backends, and an
1164
+ in-memory `QuadStore` (`Store.layerMemory`) on the core `Dataset`.
1165
+ - **`Engine`** — the `SparqlEngine` service: streaming `SELECT`, `ASK`,
1166
+ `CONSTRUCT`, `DESCRIBE` and atomic updates.
1167
+ - **`Results`** — SPARQL results in JSON (via Effect `Schema`), XML, CSV and
1168
+ TSV: streaming serialisers and parsers, including triple terms and base
1169
+ directions; media-type lookup and `Accept` negotiation.
1170
+ - **`QueryBuilder`**, **`TermSchema`** — an immutable, typed builder whose
1171
+ `SELECT` rows are typed from the projection, and schemas decoding terms to
1172
+ JS values (`TermSchema` re-exports core's `TermCodec`, so code that only
1173
+ decodes terms can depend on `@thomasfosterau/effect-rdf` alone).
1174
+
1175
+ ### Querying
1176
+
1177
+ ```ts
1178
+ import { Effect, Layer, Stream } from 'effect';
1179
+ import { Dataset, Term } from '@thomasfosterau/effect-rdf';
1180
+ import { Bindings, Engine, Store } from '@thomasfosterau/effect-rdf/sparql';
1181
+
1182
+ const data = Dataset.make(/* … quads … */);
1183
+ const SparqlLive = Engine.layer({ baseIri: 'http://example.org/' }).pipe(
1184
+ Layer.provideMerge(Store.layerMemory(data)),
1185
+ );
1186
+
1187
+ const program = Effect.gen(function* () {
1188
+ const engine = yield* Engine.SparqlEngine;
1189
+
1190
+ // SELECT: a lazy Stream of solutions (LIMIT stops evaluation early).
1191
+ const result = yield* engine.select(`
1192
+ PREFIX foaf: <http://xmlns.com/foaf/0.1/>
1193
+ SELECT ?name WHERE { ?p foaf:name ?name } ORDER BY ?name LIMIT 10`);
1194
+ yield* Stream.runForEach(result.bindings, (b) => Effect.log(Bindings.get(b, 'name')));
1195
+
1196
+ const known = yield* engine.ask('ASK { ?s ?p ?o }');
1197
+ const triples = yield* engine.construct('CONSTRUCT WHERE { ?s ?p ?o }');
1198
+
1199
+ yield* engine.update(`
1200
+ PREFIX ex: <http://example.org/>
1201
+ INSERT { ?s ex:seen true } WHERE { ?s ?p ?o }`);
1202
+ });
1203
+
1204
+ Effect.runPromise(program.pipe(Effect.provide(SparqlLive)));
1205
+ ```
1206
+
1207
+ `EngineOptions`: `unionDefaultGraph` (the default graph is the union of all
1208
+ graphs when there is no `FROM`), `baseIri`, `functions` (extension functions
1209
+ by IRI), `service` (federation), `load` (`LOAD` support) and `optimize`.
1210
+ `RequestOptions.dataset` overrides the dataset per request (the protocol's
1211
+ `default-graph-uri` / `named-graph-uri`); `FROM`/`FROM NAMED` in the query
1212
+ take precedence.
1213
+
1214
+ #### Pre-bound variables
1215
+
1216
+ `RequestOptions.bindings` pre-binds variables (initial bindings) with the
1217
+ "values insertion" semantics of SHACL-SPARQL and SPARQL `EXISTS`
1218
+ (`Algebra.prebind`): the solution is joined into every BGP, property path and
1219
+ `GRAPH ?g` pattern, so it is visible to every `FILTER` and `BIND` — unlike a
1220
+ trailing `VALUES` clause.
1221
+
1222
+ ```ts
1223
+ const rows =
1224
+ yield *
1225
+ engine.select('SELECT $this WHERE { FILTER (isIRI($this)) }', {
1226
+ bindings: Bindings.make({ this: Term.namedNode('http://example.org/a') }),
1227
+ });
1228
+ ```
1229
+
1230
+ ### Typed query builder
1231
+
1232
+ ```ts
1233
+ import { Schema, Stream } from 'effect';
1234
+ import { Namespace } from '@thomasfosterau/effect-rdf';
1235
+ import { QueryBuilder as Q, TermSchema } from '@thomasfosterau/effect-rdf/sparql';
1236
+
1237
+ const foaf = Namespace.make('http://xmlns.com/foaf/0.1/');
1238
+ const v = Q.vars('person', 'name', 'email'); // or the untyped proxy Q.v
1239
+
1240
+ const query = Q.select('person', 'name', Q.maybe('email'), Q.as(Q.strlen(v.name), 'length'))
1241
+ .where(Q.triple(v.person, foaf('name'), v.name))
1242
+ .optional(Q.triple(v.person, foaf('mbox'), v.email))
1243
+ .filter(Q.gt(Q.strlen(v.name), 3))
1244
+ .orderBy(Q.desc(v.name))
1245
+ .limit(10);
1246
+
1247
+ query.build(); // Ast.SelectQuery
1248
+ query.toString(); // SPARQL text
1249
+
1250
+ // Stream<{ readonly person: GroundTerm; readonly name: GroundTerm;
1251
+ // readonly length: GroundTerm; readonly email?: GroundTerm }>
1252
+ const rows = Q.execute(query);
1253
+
1254
+ // Decoding rows to JS values with schemas.
1255
+ const people = Q.decode(
1256
+ query,
1257
+ Schema.Struct({ name: TermSchema.string, email: Schema.optional(TermSchema.iri) }),
1258
+ );
1259
+ ```
1260
+
1261
+ ### Results formats
1262
+
1263
+ ```ts
1264
+ import { Results } from '@thomasfosterau/effect-rdf/sparql';
1265
+
1266
+ const codec = Results.negotiate(request.headers.accept); // Option<ResultsCodec>
1267
+ const body = Results.Json.serialize(selectResult); // Stream<string>
1268
+ const parsed = yield * Results.Xml.parse(text); // ResultSet | BooleanResult
1269
+ ```
1270
+
1271
+ The TSV term syntax and CSV rows are parsed with
1272
+ [`@thomasfosterau/effect-parse`](https://www.npmjs.com/package/@thomasfosterau/effect-parse)
1273
+ (an optional peer dependency of effect-rdf, needed by this subpath), using compiled grammars. A
1274
+ `ResultsParseError` from the TSV or CSV parsers carries the 1-based `line`
1275
+ and `column` of the error. `vp run --filter ./packages/core bench:sparql` times both parsers on every
1276
+ SRX/SRJ result in the conformance suites, re-serialised as TSV and CSV,
1277
+ plus a synthetic 20,000-row document.
1278
+
1279
+ `vp run --filter ./packages/core bench:sparql` also times the query and update parser on every `.rq` and `.ru`
1280
+ file of the conformance suites (188 kB) and on two synthetic documents (a
1281
+ 222 kB query and a 117 kB update). On Node 22 it reads 4–6 MB/s (the suite in
1282
+ about 40–50 ms, the query in about 55 ms and the update in about 20 ms),
1283
+ 1.4–1.8× slower than the hand-written parser it replaced, with identical
1284
+ ASTs, errors and error positions on every suite file and on 8,000 mutated
1285
+ copies of them.
1286
+
1287
+ ### Extension points
1288
+
1289
+ These are the seams for the packages built on top of this one.
1290
+
1291
+ #### Storage backends (`Store.QuadStore`)
1292
+
1293
+ A backend provides the `QuadStore` service:
1294
+
1295
+ | member | purpose |
1296
+ | ----------------------------------- | --------------------------------------------------------------------------------------------------- |
1297
+ | `match(pattern)` | stream the quads matching a quad pattern (`undefined` = wildcard; `graph` may be the default graph) |
1298
+ | `estimate?(pattern)` | cheap cardinality estimate used by the optimizer to order joins |
1299
+ | `graphs`, `hasGraph` | named graph listing (including empty graphs the backend tracks) |
1300
+ | `insert`, `delete` | quad updates |
1301
+ | `createGraph`, `dropGraph`, `clear` | graph management for `CREATE`, `DROP`, `CLEAR`, `ADD`, `MOVE`, `COPY` |
1302
+ | `transaction(effect)` | run an effect atomically (each update request runs in one transaction) |
1303
+ | `query?` | optional native algebra evaluation (`QueryCapability`) |
1304
+
1305
+ #### Native algebra evaluation (SPARQL-to-SQL)
1306
+
1307
+ `QueryCapability.evaluate(op, context)` is offered **every algebra node,
1308
+ top-down**, before the generic evaluator handles it. Returning
1309
+ `Option.some(stream)` takes over the whole subtree; `Option.none()` lets the
1310
+ engine evaluate the node itself, offering its children again. A SQL backend
1311
+ can therefore translate the largest subtrees it supports (BGPs, joins,
1312
+ filters, …) to SQL and leave the rest (e.g. property paths, exotic functions)
1313
+ to the engine. The `context` carries the `DatasetSpec` (default and named
1314
+ graphs), the active graph, and `evaluate(op, graph?)` to delegate children
1315
+ back to the engine. The algebra types live in `Algebra` (with `format`,
1316
+ `transform`, `variables`, `certainVariables` helpers); internal variables
1317
+ (blank nodes, path steps, aggregates) start with `@`.
1318
+
1319
+ #### Federation (`SERVICE`) and `LOAD`
1320
+
1321
+ `EngineOptions.service` receives the endpoint IRI and the `Algebra.Service`
1322
+ node (whose `pattern` holds the original syntax, printable with
1323
+ `Generator.printPattern`) and returns the remote solutions — the hook for an
1324
+ HTTP client (implemented by [`Federation`](#federation)).
1325
+ `SERVICE ?endpoint { … }` is evaluated once per solution after `?endpoint`
1326
+ has been bound by an earlier pattern. `EngineOptions.load` fetches documents
1327
+ for `LOAD`.
1328
+
1329
+ #### Protocol servers and clients
1330
+
1331
+ The [server](#protocol-server) and [client](#protocol-client) below are built
1332
+ on these:
1333
+
1334
+ - `Parser.parse` accepts a query or an update; errors carry positions.
1335
+ - `Results.codecs`, `Results.codecFor`, `Results.negotiate` and the streaming
1336
+ `serialize` functions implement the results side of the protocol; the
1337
+ parsers (`Results.Json.parse`, …) are what a client needs.
1338
+ - `Engine.RequestOptions.dataset` maps the protocol's dataset parameters.
1339
+ - `Generator.print` turns builder or parsed ASTs into query text for remote
1340
+ endpoints.
1341
+
1342
+ ### Protocol server
1343
+
1344
+ A SPARQL 1.1 Protocol and Graph Store HTTP Protocol server built with
1345
+ `effect/http` on top of the engine (`SparqlServer`, with `Protocol`,
1346
+ `GraphStore`, `ServiceDescription`, `Options`, `ServerFormats` and
1347
+ `ProtocolErrors`):
1348
+
1349
+ - **Queries** with `GET` (`?query=`), `POST` form-encoded and `POST`
1350
+ `application/sparql-query`; **updates** with `POST` form-encoded and `POST`
1351
+ `application/sparql-update`.
1352
+ - The protocol's dataset parameters: `default-graph-uri` / `named-graph-uri`
1353
+ (overriding `FROM` / `FROM NAMED`) and `using-graph-uri` /
1354
+ `using-named-graph-uri` (rejected with `400` when the update has `USING` or
1355
+ `WITH`).
1356
+ - Errors as specified: `400` for syntax errors, repeated or conflicting
1357
+ parameters; `405` (with `Allow`) for other methods; `415` for missing or
1358
+ unsupported content types and non-UTF-8 charsets; `406` when no acceptable
1359
+ result format exists; `403` for updates in read-only mode; `503` when a
1360
+ query exceeds its time limit; `500` for evaluation failures.
1361
+ - **Content negotiation**: SPARQL results as JSON, XML, CSV or TSV;
1362
+ `CONSTRUCT` / `DESCRIBE` results and graphs in every syntax of the core
1363
+ `Serialization.Formats` service — by default Turtle, N-Triples, RDF/XML,
1364
+ JSON-LD, N3, TriG, N-Quads and RDF/JSON (`ServerFormats.layer`). Responses are
1365
+ **streamed** as the engine produces solutions; the server waits for the
1366
+ first solution before sending the status, so errors (and timeouts) raised
1367
+ before it get a proper status code.
1368
+ - **Graph Store Protocol**: `GET`, `HEAD`, `PUT`, `POST` and `DELETE` on
1369
+ `?default`, `?graph=<iri>` (indirect identification) and on any URL below
1370
+ the graph store (direct identification); `POST` to the graph store creates
1371
+ a graph (`201 Created` with `Location`); bodies are parsed by
1372
+ `Content-Type`, including `multipart/form-data`.
1373
+ - **Service description** (SPARQL 1.1 Service Description) on `GET` without
1374
+ a query, in any RDF syntax.
1375
+ - **Configurable**: endpoint paths, read-only mode, query and update
1376
+ timeouts, maximum result size, CORS, request logging and tracing spans
1377
+ (`SparqlServer.query`, `SparqlServer.update`, `SparqlServer.graphStore`).
1378
+ - Usable as a plain HTTP app (`handler`), as `HttpRouter` routes to mount in
1379
+ your own server (`layerRoutes`), or served on whichever `HttpServer` the
1380
+ context provides (`layerServe`). The complete Node server
1381
+ (`NodeSparqlServer`), its CLI and an `effect/http-api` definition of both
1382
+ protocols (`SparqlApi`, with an OpenAPI document and a derived client) are
1383
+ in `@thomasfosterau/effect-sparql-node`.
1384
+
1385
+ #### Mounting into an existing server
1386
+
1387
+ `SparqlServer.layerRoutes(options)` registers the routes on the
1388
+ `HttpRouter`, and `SparqlServer.handler(options)` is the whole server as one
1389
+ HTTP app (`Effect<HttpServerResponse, never, HttpServerRequest | …>`) that
1390
+ dispatches on the configured paths:
1391
+
1392
+ ```ts
1393
+ import { Layer } from 'effect';
1394
+ import { HttpRouter, HttpServerResponse } from 'effect/http';
1395
+ import { NodeHttpServer } from '@effect/platform-node';
1396
+ import { createServer } from 'node:http';
1397
+ import { SparqlServer } from '@thomasfosterau/effect-rdf/sparql';
1398
+
1399
+ const Routes = Layer.mergeAll(
1400
+ HttpRouter.add('GET', '/health', HttpServerResponse.text('ok')),
1401
+ SparqlServer.layerRoutes({ path: '/api/sparql', graphStorePath: '/api/graphs' }),
1402
+ );
1403
+
1404
+ const Main = HttpRouter.serve(Routes).pipe(
1405
+ Layer.provide(SparqlServer.layerMemoryServices()),
1406
+ Layer.provide(NodeHttpServer.layer(createServer, { port: 8080 })),
1407
+ );
1408
+ ```
1409
+
1410
+ `HttpRouter.toWebHandler` turns the same routes into a `fetch`-style handler
1411
+ for serverless runtimes.
1412
+
1413
+ #### Options
1414
+
1415
+ | option | default | meaning |
1416
+ | -------------------- | ------------------ | ------------------------------------------------------------------------------ |
1417
+ | `path` | `/sparql` | query endpoint (also the update endpoint unless `updatePath` is set) |
1418
+ | `updatePath` | `path` | separate update endpoint |
1419
+ | `graphStorePath` | `/rdf-graph-store` | Graph Store Protocol root, or `false` |
1420
+ | `baseUrl` | from `Host` | public origin, for request URLs, base IRIs and directly identified graphs |
1421
+ | `baseIri` | endpoint URL | base IRI of queries and updates |
1422
+ | `readOnly` | `false` | reject updates (`403`) and graph store writes (`405`) |
1423
+ | `queryTimeout` | none | `Duration` limit for queries (`503`; a streaming response is aborted) |
1424
+ | `updateTimeout` | none | `Duration` limit for updates (`503`) |
1425
+ | `maxResults` | none | truncate results to this many solutions / triples |
1426
+ | `cors` | off | `true` or `{ allowedOrigins, allowedHeaders, exposedHeaders, maxAge, … }` |
1427
+ | `logging` | `false` | log requests in `handler` / `layerRoutes` (`layerServe` logs via `HttpRouter`) |
1428
+ | `serviceDescription` | `true` | serve the service description on `GET` without a query |
1429
+ | `unionDefaultGraph` | `false` | advertise `sd:UnionDefaultGraph` |
1430
+ | `federation` | `false` | advertise `sd:BasicFederatedQuery` |
1431
+ | `prefixes` | `{}` | prefixes used when serialising graphs |
1432
+
1433
+ #### Testing
1434
+
1435
+ `Testing.makeClient(options)` / `Testing.layerClient(options)` give an
1436
+ `HttpClient` answered in-process by the server, and `Testing.fromApps`
1437
+ routes requests to several in-process servers by origin — handy for testing
1438
+ clients and federated queries without sockets.
1439
+
1440
+ ```ts
1441
+ import { Effect, Layer } from 'effect';
1442
+ import { HttpClient } from 'effect/http';
1443
+ import { SparqlServer, Testing } from '@thomasfosterau/effect-rdf/sparql';
1444
+
1445
+ const program = Effect.gen(function* () {
1446
+ const client = yield* HttpClient.HttpClient;
1447
+ const response = yield* client.get('http://localhost/sparql?query=ASK%20%7B%7D');
1448
+ return yield* response.text;
1449
+ }).pipe(
1450
+ Effect.provide(Testing.layerClient().pipe(Layer.provide(SparqlServer.layerMemoryServices()))),
1451
+ );
1452
+ ```
1453
+
1454
+ ### Protocol client
1455
+
1456
+ A strongly-typed SPARQL 1.1 Protocol and Graph Store HTTP Protocol client
1457
+ built on the `effect/http` `HttpClient` (`SparqlClient`, with `ResultsStream`,
1458
+ `ClientFormats`, `ClientErrors` and `Federation`):
1459
+
1460
+ - **Queries** as strings, parsed `Ast` values or `QueryBuilder` builders —
1461
+ the typed rows of `QueryBuilder.select(…)` flow through to `select`, and
1462
+ rows can be decoded with an Effect `Schema`.
1463
+ - **Streaming**: `SELECT` results are parsed incrementally (JSON, XML,
1464
+ TSV and CSV) and emitted as a `Stream`; `CONSTRUCT` / `DESCRIBE`
1465
+ results are parsed with the `Serialization.Formats` parser for the response
1466
+ media type (N-Triples is preferred and parsed incrementally).
1467
+ - **Updates** with `using-graph-uri` / `using-named-graph-uri`, and the
1468
+ **Graph Store Protocol**: `getGraph`, `hasGraph`, `putGraph`, `postGraph`,
1469
+ `createGraph`, `deleteGraph`.
1470
+ - **Typed errors** (`SparqlClientError`): `HttpStatusError` (with the
1471
+ response body), `MalformedResponseError`, `UnsupportedMediaTypeError`,
1472
+ `TransportError`, `TimeoutError`, `ConfigurationError`.
1473
+ - **Timeouts and retries** configured with a `Duration` and an Effect
1474
+ `Schedule` (transient failures only, by default).
1475
+ - **Federation**: a `SERVICE` handler and a `LOAD` loader for the in-memory
1476
+ engine, so that it evaluates `SERVICE <url> { … }` remotely.
1477
+ - **A derived client** (`SparqlApiClient`, in `@thomasfosterau/effect-sparql-node`)
1478
+ generated with `HttpApiClient` from the `SparqlApi` definition.
1479
+
1480
+ #### Querying an endpoint
1481
+
1482
+ ```ts
1483
+ import { Effect, Layer, Schedule, Schema, Stream } from 'effect';
1484
+ import { FetchHttpClient } from 'effect/http';
1485
+ import { Namespace } from '@thomasfosterau/effect-rdf';
1486
+ import { QueryBuilder as Q, SparqlClient, TermSchema } from '@thomasfosterau/effect-rdf/sparql';
1487
+
1488
+ const ClientLive = SparqlClient.layer({
1489
+ endpoint: 'https://query.wikidata.org/sparql',
1490
+ headers: { 'user-agent': 'my-app/1.0' },
1491
+ method: 'auto', // GET for short queries, POST otherwise
1492
+ timeout: '30 seconds',
1493
+ retry: Schedule.max([Schedule.exponential('200 millis'), Schedule.recurs(3)]),
1494
+ }).pipe(Layer.provide(FetchHttpClient.layer));
1495
+
1496
+ const foaf = Namespace.make('http://xmlns.com/foaf/0.1/');
1497
+ const v = Q.vars('person', 'name', 'mbox');
1498
+ const query = Q.select('person', 'name', Q.maybe('mbox'))
1499
+ .where(Q.triple(v.person, foaf('name'), v.name))
1500
+ .optional(Q.triple(v.person, foaf('mbox'), v.mbox))
1501
+ .limit(10);
1502
+
1503
+ const program = Effect.gen(function* () {
1504
+ const client = yield* SparqlClient.SparqlClient;
1505
+
1506
+ // Stream<{ person: GroundTerm; name: GroundTerm; mbox?: GroundTerm }>
1507
+ yield* Stream.runForEach(client.select(query), (row) => Effect.log(row.name));
1508
+
1509
+ // Untyped rows from a string (or an Ast.Query).
1510
+ const rows = yield* Stream.runCollect(client.select('SELECT * { ?s ?p ?o } LIMIT 3'));
1511
+
1512
+ // Rows decoded with a schema.
1513
+ const people = yield* Stream.runCollect(
1514
+ SparqlClient.decode(query, Schema.Struct({ name: TermSchema.string })),
1515
+ );
1516
+
1517
+ const exists = yield* client.ask('ASK { ?s ?p ?o }');
1518
+ const graph = yield* client.constructDataset('CONSTRUCT WHERE { ?s ?p ?o } LIMIT 10');
1519
+ const triples = client.describe(Q.describe(foaf('Person'))); // Stream<Quad>
1520
+
1521
+ // Protocol dataset parameters.
1522
+ yield* Stream.runDrain(
1523
+ client.select('SELECT * { ?s ?p ?o }', { defaultGraphs: ['http://example.org/g'] }),
1524
+ );
1525
+ });
1526
+
1527
+ Effect.runPromise(program.pipe(Effect.provide(ClientLive)));
1528
+ ```
1529
+
1530
+ `selectAll` collects a result set with its variables, `results` exposes the
1531
+ raw stream of events (`Variables`, `Solution`, `Boolean`), and `query`
1532
+ evaluates any query form and dispatches on the response media type.
1533
+
1534
+ #### Updates and the Graph Store
1535
+
1536
+ ```ts
1537
+ import { Effect } from 'effect';
1538
+ import { Term } from '@thomasfosterau/effect-rdf';
1539
+ import { SparqlClient } from '@thomasfosterau/effect-rdf/sparql';
1540
+
1541
+ const ClientLive = SparqlClient.layer({
1542
+ endpoint: 'http://localhost:3030/sparql',
1543
+ updateEndpoint: 'http://localhost:3030/sparql',
1544
+ graphStoreEndpoint: 'http://localhost:3030/rdf-graph-store',
1545
+ });
1546
+
1547
+ const program = Effect.gen(function* () {
1548
+ const client = yield* SparqlClient.SparqlClient;
1549
+ yield* client.update('INSERT DATA { <urn:a> <urn:b> "c" }');
1550
+ yield* client.update('DELETE WHERE { ?s ?p ?o }', { usingGraphs: ['urn:g'] });
1551
+
1552
+ const g = Term.namedNode('http://example.org/people');
1553
+ yield* client.putGraph(g, [
1554
+ Term.quad(Term.namedNode('urn:x'), Term.namedNode('urn:p'), Term.literal('v')),
1555
+ ]);
1556
+ yield* client.postGraph(g, [], { mediaType: 'text/turtle' });
1557
+ const dataset = yield* client.getGraphDataset(g);
1558
+ const exists = yield* client.hasGraph(g);
1559
+ const created = yield* client.createGraph([]); // POST to the store: the new graph's IRI
1560
+ yield* client.deleteGraph(g);
1561
+ yield* client.getGraphDataset('default');
1562
+ });
1563
+ ```
1564
+
1565
+ #### Client errors
1566
+
1567
+ ```ts
1568
+ const result = client.ask('ASK {').pipe(
1569
+ Effect.catchTag('HttpStatusError', (e) => Effect.logError(e.status, e.body)),
1570
+ Effect.catchTag('TimeoutError', () => Effect.succeed(false)),
1571
+ );
1572
+ ```
1573
+
1574
+ `ClientErrors.isTransient` decides what `retry` retries (transport errors,
1575
+ timeouts, `408`, `429`, `502`, `503`, `504`); override it with `retryWhile`.
1576
+
1577
+ #### Federation
1578
+
1579
+ ```ts
1580
+ import { Layer } from 'effect';
1581
+ import { FetchHttpClient } from 'effect/http';
1582
+ import { Federation, Store } from '@thomasfosterau/effect-rdf/sparql';
1583
+
1584
+ // SERVICE <url> { … } is sent to the remote endpoint; LOAD <url> fetches
1585
+ // documents over HTTP.
1586
+ const EngineLive = Federation.layerEngine({ baseIri: 'http://example.org/' }).pipe(
1587
+ Layer.provide(Store.layerMemory()),
1588
+ Layer.provide(FetchHttpClient.layer),
1589
+ );
1590
+ ```
1591
+
1592
+ `Federation.serviceHandler(config)` and `Federation.loader(config)` give the
1593
+ `Engine.EngineOptions.service` / `load` functions on their own. Each remote
1594
+ pattern is sent as `SELECT * WHERE { … }` with the prefixes of the original
1595
+ query; `SERVICE SILENT` swallows failures, and `SERVICE ?endpoint` works once
1596
+ `?endpoint` is bound by an earlier pattern.
1597
+
1598
+ #### Client formats
1599
+
1600
+ The client uses the `Serialization.Formats` service when one is provided and
1601
+ falls back to `ClientFormats.defaults` (N-Triples, Turtle, N-Quads, TriG, RDF/XML,
1602
+ JSON-LD, N3, RDF/JSON). `ResultsStream` exposes the incremental results
1603
+ parsers.
1604
+
1605
+ ### SHACL-SPARQL (`@thomasfosterau/effect-rdf/sparql/shacl`)
1606
+
1607
+ [SHACL-SPARQL](https://www.w3.org/TR/shacl/#sparql-constraints) for
1608
+ [`@thomasfosterau/effect-rdf/shacl`](#shacl-thomasfosteraueffect-rdfshacl), on
1609
+ `@thomasfosterau/effect-rdf/sparql/shacl` (and `…/shacl/Evaluator`, `…/shacl/Query`),
1610
+ evaluated with this package's engine. It implements the
1611
+ `Sparql.SparqlConstraintEvaluator` service that the SHACL validator delegates
1612
+ SPARQL features to, so the core's SHACL validator itself stays free of a
1613
+ SPARQL dependency.
1614
+
1615
+ - **`Evaluator`**: `Evaluator.layer(options?)` / `Evaluator.make(options?)`
1616
+ providing the evaluator.
1617
+ - **`Query`**: reading `sh:select` / `sh:ask` queries with their prefix
1618
+ declarations, the pre-binding restrictions and `$PATH` substitution.
1619
+
1620
+ Supported:
1621
+
1622
+ - SPARQL-based constraints (`sh:sparql` with `sh:select`, `sh:message`,
1623
+ `sh:deactivated`) on node and property shapes;
1624
+ - SPARQL-based constraint components: parameters (mandatory and
1625
+ `sh:optional`), `sh:nodeValidator` / `sh:propertyValidator` (SELECT) and
1626
+ `sh:validator` (ASK), validator and component messages;
1627
+ - SPARQL-based targets (`sh:SPARQLTarget`) and target types
1628
+ (`sh:SPARQLTargetType` with parameters) from SHACL Advanced Features;
1629
+ - pre-bound `$this`, `$value` (ASK validators), `$currentShape`,
1630
+ `$shapesGraph` and parameters, with the exact pre-binding semantics of the
1631
+ specification (values insertion, see `Algebra.prebind` in
1632
+ `/sparql`) and its restrictions: `MINUS`, `VALUES`, `SERVICE`,
1633
+ `AS ?prebound` and sub-queries that drop pre-bound variables are failures;
1634
+ - `$PATH` substitution with the property shape's path (any other use of
1635
+ `$PATH` is a failure);
1636
+ - prefixes via `sh:prefixes` / `owl:imports` / `sh:declare` (conflicting
1637
+ declarations are failures);
1638
+ - the result variables `?this`, `?value`, `?path`, `?message` and `?failure`;
1639
+ - `{?var}` / `{$param}` placeholders in messages (substituted by the
1640
+ validator from the solution and parameters).
1641
+
1642
+ #### Usage
1643
+
1644
+ ```ts
1645
+ import { Effect } from 'effect';
1646
+ import { ValidationReport, Validator } from '@thomasfosterau/effect-rdf/shacl';
1647
+ import { Evaluator } from '@thomasfosterau/effect-rdf/sparql/shacl';
1648
+ import { Turtle } from '@thomasfosterau/effect-rdf/turtle';
1649
+
1650
+ const program = Effect.gen(function* () {
1651
+ const { dataset: shapes } = yield* Turtle.parse(`
1652
+ @prefix sh: <http://www.w3.org/ns/shacl#> .
1653
+ @prefix ex: <http://example.org/> .
1654
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
1655
+ ex: sh:declare [ sh:prefix "ex" ; sh:namespace "http://example.org/"^^xsd:anyURI ] .
1656
+
1657
+ ex:PersonShape a sh:NodeShape ;
1658
+ sh:targetClass ex:Person ;
1659
+ sh:sparql [
1660
+ sh:prefixes ex: ;
1661
+ sh:message "{$this} is their own parent" ;
1662
+ sh:select """
1663
+ SELECT $this WHERE { $this ex:parent $this }
1664
+ """ ;
1665
+ ] .
1666
+ `);
1667
+ const { dataset: data } = yield* Turtle.parse(`
1668
+ @prefix ex: <http://example.org/> .
1669
+ ex:alice a ex:Person ; ex:parent ex:alice .
1670
+ `);
1671
+ const report = yield* Validator.validate(data, shapes);
1672
+ console.log(ValidationReport.format(report));
1673
+ }).pipe(Effect.provide(Evaluator.layer()));
1674
+ ```
1675
+
1676
+ ##### Constraint components
1677
+
1678
+ ```turtle
1679
+ ex:MaxWordsComponent a sh:ConstraintComponent ;
1680
+ sh:parameter [ sh:path ex:maxWords ] ;
1681
+ sh:message "Value has more than {$maxWords} words" ;
1682
+ sh:validator [
1683
+ a sh:SPARQLAskValidator ;
1684
+ sh:ask """ASK { FILTER (strlen(replace(str($value), "[^ ]", "")) < $maxWords) }""" ;
1685
+ ] .
1686
+
1687
+ ex:BookShape a sh:NodeShape ;
1688
+ sh:targetClass ex:Book ;
1689
+ sh:property [ sh:path ex:title ; ex:maxWords 8 ] .
1690
+ ```
1691
+
1692
+ ##### Options
1693
+
1694
+ ```ts
1695
+ Evaluator.layer({
1696
+ // The IRI of the named graph holding the shapes graph ($shapesGraph).
1697
+ shapesGraphIri: 'urn:x-shacl:shapesGraph',
1698
+ // Options of the SPARQL engine, e.g. extension functions.
1699
+ engine: { functions },
1700
+ });
1701
+ ```
1702
+
1703
+ The data graph is the default graph of the queried dataset (named graphs of
1704
+ the data are merged into it, as the core validator sees them). Per data graph
1705
+ the evaluator builds one in-memory store and caches compiled queries.
1706
+
1707
+ ### Knowledge-graph tools (`@thomasfosterau/effect-rdf/sparql/ai`)
1708
+
1709
+ On `@thomasfosterau/effect-rdf/sparql/ai` (and `…/ai/Tools`, `…/ai/Mcp`):
1710
+ knowledge-graph tools for [Effect AI](https://effect.website) (`effect/ai`): a
1711
+ `Toolkit` that lets a language model explore and query an RDF dataset through
1712
+ this package's engine, and the same tools as an
1713
+ [MCP](https://modelcontextprotocol.io/specification) server registration for
1714
+ any MCP client.
1715
+
1716
+ ```sh
1717
+ pnpm add effect @thomasfosterau/effect-rdf @thomasfosterau/effect-rdf/sparql
1718
+ ```
1719
+
1720
+ #### Tools
1721
+
1722
+ | Tool | Parameters | Result |
1723
+ | ------------------- | ----------------- | --------------------------------------------------------------------------------------- |
1724
+ | `sparql_query` | `query`, `limit?` | SELECT: SPARQL JSON results; ASK: `isTrue`; CONSTRUCT / DESCRIBE: Turtle; `isTruncated` |
1725
+ | `describe_resource` | `iri`, `limit?` | the triples whose subject is `iri`, as Turtle; `isTruncated` |
1726
+ | `list_classes` | `limit?` | `[{ iri, count }]`: types in use and declared `rdfs:Class` / `owl:Class` |
1727
+ | `list_properties` | `limit?` | `[{ iri, count }]`: predicates in use and declared properties |
1728
+ | `search_labels` | `text`, `limit?` | `[{ iri, label }]`: `rdfs:label` / `skos:prefLabel` containing `text` (any case) |
1729
+ | `sparql_update` | `update` | `{ isApplied }` — only in `Tools.toolkitWithUpdate` |
1730
+
1731
+ `Tools.toolkit` holds the five read-only tools; `Tools.toolkitWithUpdate` adds
1732
+ `sparql_update`. `Tools.layer(options)` / `Tools.layerWithUpdate(options)`
1733
+ provide their handlers over the `SparqlEngine` service.
1734
+
1735
+ #### With a language model
1736
+
1737
+ ```ts
1738
+ import { Effect, Layer } from 'effect';
1739
+ import { LanguageModel } from 'effect/ai';
1740
+ import { Engine } from '@thomasfosterau/effect-rdf/sparql';
1741
+ import { Tools } from '@thomasfosterau/effect-rdf/sparql/ai';
1742
+
1743
+ const answer = (question: string) =>
1744
+ Effect.gen(function* () {
1745
+ const toolkit = yield* Tools.toolkit;
1746
+ const response = yield* LanguageModel.generateText({ prompt: question, toolkit });
1747
+ return response.text;
1748
+ });
1749
+
1750
+ // Provide a `LanguageModel` (e.g. from @effect/ai-openai), the tool handlers and an engine.
1751
+ const ToolsLive = Tools.layer({ maxRows: 50 }).pipe(Layer.provide(Engine.layerMemory()));
1752
+ ```
1753
+
1754
+ #### As an MCP server
1755
+
1756
+ `Mcp.layer(options)` registers the read-only tools with the `McpServer` in
1757
+ context; add a transport and an engine:
1758
+
1759
+ ```ts
1760
+ import { NodeRuntime, NodeServices } from '@effect/platform-node';
1761
+ import { Layer } from 'effect';
1762
+ import { McpProtocol, McpServer } from 'effect/ai';
1763
+ import { Engine } from '@thomasfosterau/effect-rdf/sparql';
1764
+ import { Mcp } from '@thomasfosterau/effect-rdf/sparql/ai';
1765
+
1766
+ Mcp.layer().pipe(
1767
+ Layer.provide(
1768
+ McpServer.layerStdio({
1769
+ name: 'knowledge-graph',
1770
+ version: '1.0.0',
1771
+ protocols: [McpProtocol.v2025_11_25],
1772
+ }),
1773
+ ),
1774
+ Layer.provide(Engine.layerMemory()),
1775
+ Layer.provide(NodeServices.layer),
1776
+ Layer.launch,
1777
+ NodeRuntime.runMain,
1778
+ );
1779
+ ```
1780
+
1781
+ #### Safety
1782
+
1783
+ Tool input is written by a model, so treat it as untrusted:
1784
+
1785
+ - **Read-only by default.** `sparql_query` only accepts queries; updates need
1786
+ the separate `toolkitWithUpdate` (or `Mcp.layerWithUpdate`).
1787
+ - **Bounded.** Every result is capped at `maxRows` (default 100) rows or
1788
+ triples and says whether it was truncated; every call times out after
1789
+ `timeout` (default 10 seconds).
1790
+ - **No federation.** Queries with `SERVICE` are refused unless
1791
+ `federation: true` (and the engine has a federation handler).
1792
+ - **Errors go back to the model** as a `SparqlToolError` (or a parameter
1793
+ validation error), so it can correct its query.
1794
+
1795
+ ## SPARQL over SQL (`@thomasfosterau/effect-rdf/sparql/sql`)
1796
+
1797
+ The driver-agnostic SQL storage layer for
1798
+ [`@thomasfosterau/effect-rdf/sparql`](#sparql-thomasfosteraueffect-rdfsparql), built on Effect's SQL
1799
+ abstraction alone (`effect/sql`: `SqlClient`, `Statement`, `Migrator`). It
1800
+ implements the engine's `QuadStore` service **and** its `QueryCapability`
1801
+ extension point: SPARQL algebra is translated to SQL, so whole queries (or the
1802
+ largest translatable subtrees) run as a single SQL statement, and everything
1803
+ else falls back to the generic engine.
1804
+
1805
+ It runs over the `SqlClient` in the context, in one of two dialects
1806
+ (`Dialect.sqlite`, `Dialect.postgres`, detected by `Dialect.fromClient`):
1807
+
1808
+ - **SQLite** (3.35 or later), e.g. through `@effect/sql-sqlite-node`.
1809
+ - **PostgreSQL**, e.g. through `@effect/sql-pg`.
1810
+
1811
+ No driver is a dependency or a peer of effect-rdf: the application provides
1812
+ the `SqlClient`. A ready backend for one driver (a store layer that also
1813
+ provides that driver's `SqlClient`) is a package of its own,
1814
+ `@thomasfosterau/effect-sparql-<driver>`, with the driver as its dependency or
1815
+ peer; none is published yet.
1816
+
1817
+ ### Usage
1818
+
1819
+ ```ts
1820
+ import { Effect, Layer, Stream } from 'effect';
1821
+ import { Engine } from '@thomasfosterau/effect-rdf/sparql';
1822
+ import { SqlStore } from '@thomasfosterau/effect-rdf/sparql/sql';
1823
+ import { SqliteClient } from '@effect/sql-sqlite-node';
1824
+
1825
+ // The store over a SQLite file (or ':memory:'); provides SqlStore and QuadStore.
1826
+ const SparqlLive = Engine.layer().pipe(
1827
+ Layer.provideMerge(SqlStore.layer()),
1828
+ Layer.provideMerge(SqliteClient.layer({ filename: 'data.db' })),
1829
+ );
1830
+
1831
+ const program = Effect.gen(function* () {
1832
+ const store = yield* SqlStore.SqlStore;
1833
+ yield* store.insert(quads); // batched, interned, one transaction
1834
+
1835
+ const engine = yield* Engine.SparqlEngine;
1836
+ const result = yield* engine.select(`
1837
+ PREFIX foaf: <http://xmlns.com/foaf/0.1/>
1838
+ SELECT ?name (COUNT(?friend) AS ?friends) WHERE {
1839
+ ?p foaf:name ?name OPTIONAL { ?p foaf:knows ?friend }
1840
+ } GROUP BY ?name`);
1841
+ yield* Stream.runForEach(result.bindings, (b) => Effect.log(b.toString()));
1842
+ });
1843
+ ```
1844
+
1845
+ With PostgreSQL, provide any `SqlClient` to `SqlStore.layer`:
1846
+
1847
+ ```ts
1848
+ import { PgClient } from '@effect/sql-pg';
1849
+
1850
+ const StoreLive = SqlStore.layer().pipe(
1851
+ Layer.provide(PgClient.layer({ url: Redacted.make(process.env.DATABASE_URL!) })),
1852
+ );
1853
+ ```
1854
+
1855
+ `SqlStoreOptions`: `dialect` (detected from the client), `tablePrefix`
1856
+ (default `rdf_`), `migrate` (create/migrate the schema with the Effect
1857
+ `Migrator`, default `true`), `translate` (SPARQL-to-SQL, default `true`),
1858
+ `batchSize`, `pageSize` (streaming `match`) and `onQuery`, a debug hook that
1859
+ receives an event for every algebra node offered to the store (`Executed`
1860
+ with the SQL text, or `Declined` with the reason). `store.compile(op)` returns
1861
+ the SQL for an algebra tree without running it; queries are also traced with
1862
+ an `SqlStore.query` span carrying the SQL text.
1863
+
1864
+ ### Schema
1865
+
1866
+ | table | columns |
1867
+ | ------------------ | ----------------------------------------------------------------------------------- |
1868
+ | `rdf_terms` | `id`, `kind`, `value`, `datatype`, `lang`, `dir`, `sub`, `pre`, `obj`, `cls`, `num` |
1869
+ | `rdf_quads` | `s`, `p`, `o`, `g` (term ids; `g = 0` is the default graph) |
1870
+ | `rdf_graphs` | `id` — explicitly created (possibly empty) named graphs |
1871
+ | `rdf_term_classes` | `cls` — the comparison classes present in the dictionary |
1872
+ | `rdf_migrations` | the store's `Migrator` table |
1873
+
1874
+ - **Terms are interned** in an append-only dictionary. `kind` is 1 IRI, 2
1875
+ blank node, 3 literal, 4 triple term. RDF 1.2 **triple terms are
1876
+ first-class**: `sub`, `pre`, `obj` hold the ids of their components (and
1877
+ `value` is `"s p o"`, so `(value, kind, datatype, lang, dir)` is the unique
1878
+ key of every term — indexed on `md5(value)` in PostgreSQL). Triple terms are
1879
+ indexed by component.
1880
+ - `cls` is a **comparison class** (numeric, double, float, string,
1881
+ language-tagged string, boolean, temporal, unknown datatype, ill-formed, …)
1882
+ and `num` the numeric value of numeric and boolean literals; they let SQL
1883
+ evaluate SPARQL comparisons and effective boolean values per row.
1884
+ - Quads are indexed `spog` (primary key), `posg`, `ospg` and `gspo`.
1885
+ - Inserts are batched (terms with `INSERT … ON CONFLICT DO NOTHING
1886
+ RETURNING`, then quads), in one transaction; ids are cached in memory
1887
+ outside transactions. Loading 100,000 triples into SQLite takes about 1.5 s.
1888
+
1889
+ ### QuadStore
1890
+
1891
+ `match` streams by keyset pagination over the index matching the bound
1892
+ positions; `estimate` (bounded count) and `count` are SQL counts; `insert`,
1893
+ `delete`, `createGraph`, `dropGraph`, `clear`, `graphs`, `hasGraph`; and
1894
+ `transaction` maps to `SqlClient.withTransaction` (nested transactions use
1895
+ savepoints).
1896
+
1897
+ ### SPARQL to SQL
1898
+
1899
+ The engine offers every algebra node top-down; the store returns a stream
1900
+ when the whole subtree is **exactly** translatable, otherwise the engine
1901
+ evaluates the node itself and offers its children again. Solutions are rows of
1902
+ term ids (`NULL` = unbound); the final `SELECT` joins the dictionary to decode
1903
+ them.
1904
+
1905
+ | algebra | SQL |
1906
+ | -------------------------------- | ------------------------------------------------------------------------------------ |
1907
+ | BGP | self-joins of `rdf_quads` (triple term patterns join `rdf_terms` on `kind = 4`) |
1908
+ | `Join` | merged `FROM`/`WHERE` with compatibility conditions (`NULL`-aware) |
1909
+ | `LeftJoin` | `LEFT JOIN (…) ON compatible AND filter` (the OPTIONAL filter is in the `ON` clause) |
1910
+ | `Union` | `UNION ALL` with `NULL` padding |
1911
+ | `Minus`, `[NOT] EXISTS` | correlated `NOT EXISTS` / `EXISTS` |
1912
+ | `Filter` | `WHERE` (see expressions) |
1913
+ | `Graph` (IRI or variable) | graph column constraints; `FROM` / `FROM NAMED` datasets, union default graph |
1914
+ | `Extend` | term-valued expressions (variables, constants, `IF`, `COALESCE`, boolean results) |
1915
+ | `Values` | `VALUES` lists (terms not in the store get query-local negative ids) |
1916
+ | `Project`, `Distinct`, `Reduced` | column selection, `SELECT DISTINCT` |
1917
+ | `Slice` | `LIMIT` / `OFFSET` |
1918
+ | `Group` | `GROUP BY` variables with `COUNT(*)`, `COUNT([DISTINCT] ?x)`, `SAMPLE(?x)`; `HAVING` |
1919
+
1920
+ Expressions: `&&`, `||`, `!` (SPARQL's error semantics coincide with SQL's
1921
+ three-valued logic, an error being `NULL`), `=`, `!=`, `<`, `>`, `<=`, `>=`,
1922
+ `IN`, `NOT IN`, `BOUND`, `sameTerm`, `isIRI`, `isBlank`, `isLiteral`,
1923
+ `isNumeric`, `isTRIPLE`, `hasLANG`, `hasLANGDIR`, `STR`, `LANG`, `DATATYPE`,
1924
+ `STRLEN`, `STRSTARTS`, `STRENDS`, `CONTAINS`, `LANGMATCHES`, `IF`,
1925
+ `COALESCE`, `EXISTS`, and effective boolean values.
1926
+
1927
+ **Correctness first.** Comparisons follow the engine exactly: numbers compare
1928
+ by value (`1 = 1.0`), strings by code point (binary collation), language
1929
+ strings only with the same tag, booleans by value, IRIs and blank nodes by
1930
+ identity, incomparable values are errors. Combinations SQL cannot compare
1931
+ exactly — dates and durations, integers beyond 2^53, decimals with more than
1932
+ 15 digits, `NaN`/`INF`, float/double promotion against non-constants, triple
1933
+ term value equality — are recorded at compilation; before running, the store
1934
+ checks which comparison classes the data contains and hands the node back to
1935
+ the engine when such a combination can occur.
1936
+
1937
+ Evaluated by the engine (with their translatable children in SQL): `ORDER BY`
1938
+ (SPARQL's ordering is not a total order SQL can reproduce), property paths,
1939
+ `SERVICE`, `REGEX`, arithmetic, casts, string-building and date/time
1940
+ functions, `SUM`/`AVG`/`MIN`/`MAX`/`GROUP_CONCAT`, group keys that are
1941
+ expressions, and `LIMIT`/`GROUP BY` inside `GRAPH ?g`. Results are fetched
1942
+ with one statement per translated subtree (SQLite has no cursors in
1943
+ `node:sqlite`).
1944
+
1945
+ ### Adding a dialect
1946
+
1947
+ A `Dialect` (see `Dialect.ts`) only produces SQL text; statements run through
1948
+ the `SqlClient`. Implement:
1949
+
1950
+ - `schema(tables)` — idempotent DDL for the tables above (and a unique index
1951
+ on the dictionary key);
1952
+ - `placeholder(i)` and `anonymousParameters` — `?` (positional) or `$1`;
1953
+ - `valueKey` — the dictionary key expression (e.g. `md5(value)`);
1954
+ - typed `NULL`s and casts (`nullId`, `castId`, `castText`, `castDouble`,
1955
+ `castInt`) for databases that type `NULL` and parameters strictly;
1956
+ - `binary(text)` — code point collation; `startsWith`, `endsWith`,
1957
+ `contains`, `length` (in characters) and `hasNonZeroDigit`;
1958
+ - `limitOffset`, `maxParameters`.
1959
+
1960
+ The database must support `INSERT … ON CONFLICT DO NOTHING RETURNING`, row
1961
+ value comparisons, `VALUES` lists and correlated subqueries. Pass the dialect
1962
+ as `SqlStore.layer({ dialect })` (or extend `Dialect.fromClient`).
1963
+
1964
+ ## ShEx (`@thomasfosterau/effect-rdf/shex`)
1965
+
1966
+ [ShEx (Shape Expressions)](https://shex.io/shex-semantics/) for
1967
+ [`@thomasfosterau/effect-rdf`](#modules), written with Effect.
1968
+
1969
+ - **ShExJ** (`ShExJ`): the abstract syntax as plain immutable values, with
1970
+ Effect `Schema`s to decode, validate and encode ShExJ JSON. `resolveIris`
1971
+ resolves the relative IRIs in a ShExJ document.
1972
+ - **ShExC** (`ShExC`): a hand-written parser for the compact syntax. It
1973
+ handles `BASE`/`PREFIX`/`IMPORT`, `start=`, `EXTERNAL`, node kinds,
1974
+ datatypes, every string and numeric facet, regular expressions with flags
1975
+ and escapes, value sets with IRI, literal and language stems, ranges and
1976
+ exclusions, `$label` and `&include`, inverse `^`, every cardinality form,
1977
+ annotations `//`, semantic actions `%iri{ code %}`, and the ShEx 2.2
1978
+ `ABSTRACT`, `EXTENDS` and `RESTRICTS`. Errors are `RdfParseError`s with line
1979
+ and column. The lexer and the grammar (over the lexer's tokens) are
1980
+ compiled `@thomasfosterau/effect-parse` parsers; `vp run --filter ./packages/core bench:shex` times them on
1981
+ the shexTest schemas and a large synthetic schema. `ShExC.print` writes a
1982
+ schema back as ShExC.
1983
+ - **ShExR** (`ShExR`): reads a schema from a core `Dataset` that holds its RDF
1984
+ representation, and writes one (`toQuads` / `toDataset`).
1985
+ - **Structural checks** (`Structure`): every reference resolves, inclusions
1986
+ name triple expressions, no shape refers to itself except through a triple
1987
+ constraint, no reference cycle passes through negation (`NOT` or `EXTRA`),
1988
+ and the extension hierarchy has no cycles.
1989
+ - **ShapeMaps** (`ShapeMap`): the compact syntax for fixed maps
1990
+ (`node@shape`, `node@START`), query maps with `{FOCUS p _}` / `{_ p FOCUS}`
1991
+ selectors, and the JSON form. The compact syntax is parsed with
1992
+ [`@thomasfosterau/effect-parse`](https://www.npmjs.com/package/@thomasfosterau/effect-parse)
1993
+ (an optional peer dependency of effect-rdf, needed by this subpath). Errors are `RdfParseError`s with
1994
+ line and column.
1995
+ - **Validation** (`Validator`): the ShEx 2.1 semantics plus ShEx 2.2
1996
+ `EXTENDS`/`ABSTRACT`/`RESTRICTS`.
1997
+ - Triple expressions are matched by searching for a partition of the
1998
+ neighbourhood, then checking the bag of triple-constraint counts against
1999
+ `EachOf`/`OneOf`/cardinalities with a bag-derivative algorithm.
2000
+ - `CLOSED` and `EXTRA` are supported.
2001
+ - Recursion uses the maximal typing: it is evaluated coinductively and
2002
+ memoised, and stratified negation is guaranteed by the structural checks.
2003
+ - Imports are loaded through the `SchemaLoader` service.
2004
+ - Semantic actions go to a pluggable `SemAct.SemActHandler`. Unknown actions
2005
+ are ignored, and the ShEx Test extension is included.
2006
+
2007
+ ### Validating data
2008
+
2009
+ ```ts
2010
+ import { Effect } from 'effect';
2011
+ import { Dataset, Term } from '@thomasfosterau/effect-rdf';
2012
+ import { ShapeMap, ShExC, Validator } from '@thomasfosterau/effect-rdf/shex';
2013
+
2014
+ const ex = (s: string) => Term.namedNode(`http://example.org/${s}`);
2015
+
2016
+ const program = Effect.gen(function* () {
2017
+ const schema = yield* ShExC.parse(`
2018
+ PREFIX ex: <http://example.org/>
2019
+ PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
2020
+ start = @ex:Person
2021
+ ex:Person CLOSED {
2022
+ ex:name xsd:string MINLENGTH 1 ;
2023
+ ex:age xsd:integer MININCLUSIVE 0 ? ;
2024
+ ex:knows @ex:Person *
2025
+ }
2026
+ `);
2027
+
2028
+ const data = Dataset.make(
2029
+ Term.quad(ex('alice'), ex('name'), Term.literal('Alice')),
2030
+ Term.quad(ex('alice'), ex('knows'), ex('bob')),
2031
+ Term.quad(ex('bob'), ex('name'), Term.literal('Bob')),
2032
+ );
2033
+
2034
+ // A shape map: fixed associations or triple-pattern queries.
2035
+ const map = yield* ShapeMap.parse('{FOCUS ex:name _}@START, ex:alice@ex:Person', {
2036
+ prefixes: { ex: 'http://example.org/' },
2037
+ });
2038
+
2039
+ const report = yield* Validator.validate(data, schema, map);
2040
+ for (const r of report.results) {
2041
+ console.log(r.node.key, ShapeMap.formatShape(r.shape), r.status, r.reason ?? '');
2042
+ }
2043
+ return report.conformant;
2044
+ });
2045
+ ```
2046
+
2047
+ `Validator.validate` returns
2048
+ `Effect<ValidationReport, ShExValidationError | ShExImportError | ShExStructureError>`.
2049
+ The report has one `ShapeResult` (`conformant` or `nonconformant`, with a
2050
+ `reason`) for each association. If you validate many shape maps against the
2051
+ same schema, call `Validator.make(schema)` once and reuse the validator. It
2052
+ loads the imports and runs the structural checks a single time.
2053
+
2054
+ ### As a Schema
2055
+
2056
+ `Validator.schema(validator, shapeMap)` (or a single `{ node, shape }`) is a `Dataset` → `Dataset`
2057
+ schema that accepts only data where every association conforms, so ShEx fits `Schema.decode`
2058
+ pipelines (`HttpApi` payloads, config loading). It takes a prepared validator, so imports are
2059
+ resolved up front and decoding needs no services. A failure is a `SchemaIssue.Composite` with one
2060
+ `InvalidValue` per nonconformant association, whose `message` annotation is
2061
+ `Validator.formatResult(result)` and whose `result` annotation is the `ShapeResult`. Compose it with
2062
+ a syntax codec to parse and validate in one decode:
2063
+
2064
+ ```ts
2065
+ import { Schema } from 'effect';
2066
+ import { Turtle } from '@thomasfosterau/effect-rdf/turtle';
2067
+
2068
+ const validator = yield * Validator.make(schema);
2069
+ const PersonDocument = Turtle.codec.schema.pipe(
2070
+ Schema.decodeTo(Validator.schema(validator, { node: alice, shape: 'http://ex.example/Person' })),
2071
+ );
2072
+ const data = yield * Schema.decodeEffect(PersonDocument)(turtleText);
2073
+ ```
2074
+
2075
+ ### Imports
2076
+
2077
+ `IMPORT` is resolved through the `SchemaLoader` reference. By default it fails
2078
+ on every import, so provide a loader:
2079
+
2080
+ ```ts
2081
+ const loader: Validator.Loader = {
2082
+ load: (iri) =>
2083
+ fetchText(iri).pipe(
2084
+ Effect.flatMap((text) => ShExC.parse(text, { baseIri: iri })),
2085
+ Effect.mapError((e) => new ShExError.ShExImportError({ iri, message: String(e) })),
2086
+ ),
2087
+ };
2088
+
2089
+ Validator.validate(data, schema, map).pipe(Effect.provide(Validator.loaderLayer(loader)));
2090
+ ```
2091
+
2092
+ `Validator.loaderFromRecord({ iri: schema })` serves schemas you already have
2093
+ in memory.
2094
+
2095
+ ### Semantic actions
2096
+
2097
+ ```ts
2098
+ import { Option } from 'effect';
2099
+ import { SemAct } from '@thomasfosterau/effect-rdf/shex';
2100
+
2101
+ // Dispatch on action names (a name ending in / or # covers its namespace).
2102
+ const handler = SemAct.make({
2103
+ 'http://example.org/ext/check': (code, ctx) => ctx.triple !== undefined && code !== 'reject',
2104
+ });
2105
+
2106
+ Validator.validate(data, schema, map).pipe(
2107
+ Effect.provideService(
2108
+ SemAct.SemActHandler,
2109
+ SemAct.combine(
2110
+ handler,
2111
+ SemAct.testExtension((line) => console.log(line)),
2112
+ ),
2113
+ ),
2114
+ );
2115
+ ```
2116
+
2117
+ Pass `externalSemActs` to supply code for actions written as `%<name>%`. Pass
2118
+ `externalShapes` to supply definitions for `EXTERNAL` shapes.
2119
+
2120
+ ### Other representations
2121
+
2122
+ ```ts
2123
+ const json = ShExJ.stringify(schema); // ShExJ text
2124
+ const back = yield * ShExJ.parse(json); // decoded and validated with Effect Schema
2125
+ const shexc = ShExC.print(schema, { prefixes }); // ShExC text
2126
+ const rdf = ShExR.toDataset(schema); // ShExR as a Dataset
2127
+ const fromRdf = yield * ShExR.fromDataset(rdf);
2128
+ ```
2129
+
2130
+ ## Conformance
2131
+
2132
+ The data model has no W3C suite of its own; it is exercised by every syntax subpath's suites (N-Triples, Turtle, TriG, RDF/XML, JSON-LD, N3) and by the SPARQL evaluation suite, which compare graphs with `Isomorphism.isomorphic`. Its unit tests cover terms, datasets, IRIs, XSD, RDFS and isomorphism, and RDF/JSON's (`test/rdfjson/`) its schemas, parser, serialiser and round trips; RDF/JSON has no W3C suite.
2133
+
2134
+ Every suite below runs when it is checked out under `.test-suites` (`scripts/fetch-test-suites.sh`, or set `RDF_TEST_SUITES`), and skips otherwise. Each subpath's suites live in `test/<subpath>/`; manifests and expected results are read with `/turtle`, `/ntriples` and `/rdfxml`.
2135
+
2136
+ **CSV on the Web**: the [W3C CSVW test suite](https://github.com/w3c/csvw/tree/gh-pages/tests) (`.test-suites/csvw`). The suite's CSV files are split into records by a test-only stand-in for `effect-csv` (`test/csvw/support/csv.ts`).
2137
+
2138
+ | Manifest | Result |
2139
+ | -------------------------- | ------------------------- |
2140
+ | `manifest-validation.ttl` | 276 / 276 run (6 skipped) |
2141
+ | `manifest-rdf.ttl` (RDF) | 264 / 264 run (6 skipped) |
2142
+ | `manifest-json.ttl` (JSON) | 264 / 264 run (6 skipped) |
2143
+
2144
+ RDF results are compared with `Isomorphism.isomorphic`, JSON results structurally. The skipped tests need metadata discovery over HTTP, which the CSVW subpath does not do: `test014`, `test016`, `test120` and `test122` (an HTTP `Link` header) and `test259` and `test260` (a site-wide `/.well-known/csvm` file).
2145
+
2146
+ **SHACL**: the W3C SHACL test suite (`.test-suites/data-shapes`): all 98 SHACL Core tests pass. The 22 SHACL-SPARQL tests (`sparql/`) need a SPARQL evaluator; they run in `@thomasfosterau/effect-rdf/sparql`'s suite, where all 22 pass, and all 98 Core tests pass with the evaluator installed.
2147
+
2148
+ **N-Triples and N-Quads** (`/ntriples`): The full W3C rdf11 / rdf12 N-Triples and N-Quads suites pass (syntax and canonicalisation): `test/ntriples/conformance.test.ts`.
2149
+
2150
+ **Turtle and TriG** (`/turtle`): Every W3C rdf11 / rdf12 Turtle and TriG syntax and evaluation test passes, and every positive test round-trips through the serializer: `test/turtle/conformance.test.ts`.
2151
+
2152
+ **Notation3** (`/n3`): `test/n3/conformance.test.ts` runs the W3C N3 CG suite (`.test-suites/N3/tests`):
2153
+ the Turtle, N3 parser and extended N3 parser manifests, positive and
2154
+ negative syntax and evaluation tests. Every positive test is additionally
2155
+ round-tripped through the serializer (and, where RDF-compatible, through a
2156
+ core dataset). See the test file for the documented skip list.
2157
+
2158
+ **JSON-LD** (`/jsonld`): The W3C JSON-LD 1.1 expand, compact, flatten, toRdf, fromRdf, remote-doc and framing suites pass (`test/jsonld/`); the only tests skipped are JSON-LD 1.0-only ones and the ones that need generalized RDF.
2159
+
2160
+ **RDF/XML** (`/rdfxml`): Every test of the W3C RDF 1.1 and RDF 1.2 RDF/XML suites passes (`test/rdfxml/Conformance.test.ts`), including `rdf:version`, base direction via `its:dir`, triple terms and annotations.
2161
+
2162
+ **XMP** (`/xmp`): There is no public XMP conformance suite; the unit tests are modelled on the ISO 16684-1
2163
+ examples. `scripts/fetch-test-suites.sh` checks out the metadata sections of
2164
+ [veraPDF's PDF/A and PDF/UA corpus](https://github.com/veraPDF/veraPDF-corpus), and
2165
+ `test/xmp/Conformance.test.ts` reads the packet of every file in them: all 497 files that should
2166
+ pass validation parse (and give the PDF/A part their section tests), and none of the 568 that
2167
+ should fail makes the reader throw. The ISO 16684-1 examples are not freely published, and the
2168
+ Adobe XMP Toolkit SDK ships no sample XMP files, only the C++ samples that generate them.
2169
+
2170
+ **SPARQL** (`/sparql`): The W3C test suites are run from `.test-suites/rdf-tests/sparql` (or
2171
+ `$RDF_TEST_SUITES`), using `/turtle`, `/ntriples` and `/rdfxml` to read
2172
+ manifests and data:
2173
+
2174
+ - **Syntax**: 574 / 574 (SPARQL 1.0, 1.1 query and update, 1.2) — every
2175
+ positive test also round-trips through `Generator.print`.
2176
+ - **Evaluation**: 707 / 707 runnable query, update and CSV/TSV result tests
2177
+ (SPARQL 1.0, 1.1, 1.2). Result sets are compared up to blank node renaming
2178
+ (numeric literals by value); ordered results check the `ORDER BY` keys.
2179
+ Skipped (77): `sparql11/entailment` (70; entailment regimes are not
2180
+ supported) and `sparql11/service` (7; federation needs remote endpoints,
2181
+ so these run over HTTP below).
2182
+ - **Evaluation over HTTP** (`test/sparql/HttpEvalConformance.test.ts`): the same
2183
+ 707 / 707 runnable tests pass with each test's data loaded into an
2184
+ in-process server and every query sent by the client, with results parsed
2185
+ from JSON, XML and TSV. Skipped: `sparql11/entailment` and
2186
+ `sparql11/service` (below).
2187
+ - **Federation** (`sparql11/service`): 7 / 7, with one in-process server per
2188
+ endpoint (the servers are federated themselves, for nested `SERVICE`).
2189
+
2190
+ The protocol suites in `.test-suites/rdf-tests/sparql/sparql11` are run
2191
+ in-process against the server, reading the HTTP requests and expected
2192
+ responses from the manifests — once against `SparqlServer.handler` and once
2193
+ against the `SparqlApi` routes (`SparqlApiBuilder`), with the same results.
2194
+ They live in `@thomasfosterau/effect-sparql-node`'s tests
2195
+ (`test/ProtocolConformance.test.ts`), since `SparqlApi` is that package's:
2196
+
2197
+ - **SPARQL 1.1 Protocol** (`protocol`): 34 / 34 on both.
2198
+ - **Graph Store Protocol** (`graph-store-protocol`, direct and indirect
2199
+ identification, graph creation by `POST`, `multipart/form-data`): 14 / 14
2200
+ on both (13 listed tests plus `head_non_existing_direct`, which the
2201
+ manifest defines but does not list).
2202
+ - **Service Description** (`service-description`): 3 / 3 on both (the
2203
+ manifest has no machine-readable requests; the three checks are
2204
+ implemented directly).
2205
+ - **Skipped**: `http-rdf-update` — deprecated (`dawg:Deprecated`, superseded
2206
+ by `graph-store-protocol`), its requests are prose-only and depend on state
2207
+ it never creates.
2208
+
2209
+ **SHACL-SPARQL** (`/sparql/shacl`): the W3C SHACL test suite (`.test-suites/data-shapes`, or
2210
+ `$RDF_TEST_SUITES`): all **22 / 22** SHACL-SPARQL tests (`sparql/`: node,
2211
+ property, component and pre-binding tests) pass, and all 98 Core tests pass
2212
+ with the evaluator installed.
2213
+
2214
+ **Knowledge-graph tools** (`/sparql/ai`): there is no conformance suite for tools; the tests
2215
+ (`test/sparql/ai/`) drive every handler against an in-memory engine and round-trip the tools through `McpTest` (`test/sparql/ai/McpClient.test.ts`, which needs effect 4.1; everything else in the package's suite also runs on the 4.0 floor).
2216
+
2217
+ **SPARQL over SQL** (`/sparql/sql`):
2218
+
2219
+ - The W3C SPARQL 1.0/1.1/1.2 evaluation tests run against SQLite (a fresh
2220
+ database per test) **and against PostgreSQL** (PGlite, PostgreSQL compiled
2221
+ to WASM, through a small `SqlClient` adapter in `test/sparql/sql/support/pglite.ts`):
2222
+ 707/707 runnable query, update and result-format tests pass on both (the
2223
+ same 77 entailment and federation tests as the in-memory store are
2224
+ skipped). More than half of the queries run entirely as one SQL statement.
2225
+ - Differential tests compare the SQL store with the in-memory store on
2226
+ random queries over random datasets (including data that forces the
2227
+ exactness fallbacks).
2228
+ - Translation tests assert that representative queries are executed entirely
2229
+ in SQL and inspect the generated SQL (SQLite and PostgreSQL).
2230
+ - A bulk-load test loads 100,000 triples and runs a join/filter/count query.
2231
+
2232
+ **ShEx** (`/shex`): The tests run the [shexTest](https://github.com/shexSpec/shexTest) suite when
2233
+ it is checked out under `.test-suites/shexTest` (or set `RDF_TEST_SUITES`):
2234
+
2235
+ | Suite | Result |
2236
+ | --------------------------------------------------- | ------------------------------------- |
2237
+ | `schemas`: ShExC → ShExJ | 484 / 484 |
2238
+ | `schemas`: ShExJ → ShExC → ShExJ (printer) | 484 / 484 |
2239
+ | `schemas`: ShExR → ShExJ, and ShExJ → ShExR → ShExJ | 482 / 484 each (2 skipped, see below) |
2240
+ | `negativeSyntax` | 105 / 105 |
2241
+ | `negativeStructure` | 19 / 19 |
2242
+ | `validation` (status, plus Test-extension output) | 1309 / 1309 |
2243
+
2244
+ These two ShExR tests are skipped because the RDF is ambiguous. In both, a
2245
+ labelled triple expression is used in several places, and RDF cannot record
2246
+ which use defines it: `1Include1` and `ShExR`. The `TwoNegation_pass`
2247
+ representation test is not checked structurally, because its schema
2248
+ deliberately breaks the negation requirement.
2249
+
2250
+ ## License
2251
+
2252
+ MIT