@thomasfosterau/effect-rdf 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +2251 -2
- package/dist/Algebra-CGG4oae3.mjs +433 -0
- package/dist/Algebra-CGG4oae3.mjs.map +1 -0
- package/dist/Algebra-uGsIEOUA.d.mts +446 -0
- package/dist/Ast-CPRZmBvk.mjs +552 -0
- package/dist/Ast-CPRZmBvk.mjs.map +1 -0
- package/dist/Ast-vcw2W-lf.d.mts +827 -0
- package/dist/Bindings-Ck3m7JRC.mjs +249 -0
- package/dist/Bindings-Ck3m7JRC.mjs.map +1 -0
- package/dist/Bindings-DiKpBd7f.d.mts +153 -0
- package/dist/ClientErrors-CpXdFX5u.mjs +128 -0
- package/dist/ClientErrors-CpXdFX5u.mjs.map +1 -0
- package/dist/ClientErrors-D4IZHUM-.d.mts +101 -0
- package/dist/ClientFormats-CXomkXLV.mjs +73 -0
- package/dist/ClientFormats-CXomkXLV.mjs.map +1 -0
- package/dist/ClientFormats-Dm57ZPwR.d.mts +36 -0
- package/dist/Csv2Json-DynEcwCh.mjs +194 -0
- package/dist/Csv2Json-DynEcwCh.mjs.map +1 -0
- package/dist/Csv2Json-aXNVDoXQ.d.mts +40 -0
- package/dist/Csv2Rdf-8maXP6mt.d.mts +41 -0
- package/dist/Csv2Rdf-DuSEkaKE.mjs +161 -0
- package/dist/Csv2Rdf-DuSEkaKE.mjs.map +1 -0
- package/dist/Dataset-BlmlcI7J.mjs +782 -0
- package/dist/Dataset-BlmlcI7J.mjs.map +1 -0
- package/dist/Dataset-DS3ESOcO.d.mts +426 -0
- package/dist/Dataset.d.mts +2 -0
- package/dist/Dataset.mjs +1 -0
- package/dist/Datatype-CxMpamHC.d.mts +212 -0
- package/dist/Datatype-DQvkNkiH.mjs +1001 -0
- package/dist/Datatype-DQvkNkiH.mjs.map +1 -0
- package/dist/Dialect-8ChFj5EX.d.mts +113 -0
- package/dist/Dialect-CzVH38uo.mjs +165 -0
- package/dist/Dialect-CzVH38uo.mjs.map +1 -0
- package/dist/DocumentLoader-D3lZREnB.mjs +346 -0
- package/dist/DocumentLoader-D3lZREnB.mjs.map +1 -0
- package/dist/DocumentLoader-ZUCEVrW5.d.mts +178 -0
- package/dist/DublinCore-AnwqxRx6.mjs +137 -0
- package/dist/DublinCore-AnwqxRx6.mjs.map +1 -0
- package/dist/DublinCore-Dq-tVA5d.d.mts +119 -0
- package/dist/Engine-CYPB3g4h.d.mts +144 -0
- package/dist/Engine-DF8PN7g-.mjs +872 -0
- package/dist/Engine-DF8PN7g-.mjs.map +1 -0
- package/dist/Errors-CMdV1RhU.mjs +37 -0
- package/dist/Errors-CMdV1RhU.mjs.map +1 -0
- package/dist/Errors-u2bDjUeM.d.mts +30 -0
- package/dist/Evaluator-DEsU4d1o.d.mts +50 -0
- package/dist/Evaluator-DOY2-guC.mjs +281 -0
- package/dist/Evaluator-DOY2-guC.mjs.map +1 -0
- package/dist/Exif-CpqniYQd.d.mts +78 -0
- package/dist/Exif-DjkCfEB-.mjs +89 -0
- package/dist/Exif-DjkCfEB-.mjs.map +1 -0
- package/dist/Expression-BIMMvduQ.mjs +230 -0
- package/dist/Expression-BIMMvduQ.mjs.map +1 -0
- package/dist/Expression-W8A9cyX7.d.mts +89 -0
- package/dist/Extract-Bop85jES.mjs +220 -0
- package/dist/Extract-Bop85jES.mjs.map +1 -0
- package/dist/Extract-C_pCvuTC.d.mts +88 -0
- package/dist/Federation-BcNrozc1.d.mts +52 -0
- package/dist/Federation-CHScBkaK.mjs +130 -0
- package/dist/Federation-CHScBkaK.mjs.map +1 -0
- package/dist/Functions-AvjNcId_.mjs +1346 -0
- package/dist/Functions-AvjNcId_.mjs.map +1 -0
- package/dist/Functions-OQJZP-F6.d.mts +111 -0
- package/dist/Generator-C52mmq01.d.mts +50 -0
- package/dist/Generator-D3utCJC7.mjs +205 -0
- package/dist/Generator-D3utCJC7.mjs.map +1 -0
- package/dist/Grammar-CPlSxGH6.mjs +200 -0
- package/dist/Grammar-CPlSxGH6.mjs.map +1 -0
- package/dist/Grammar-CRhnRoCu.d.mts +148 -0
- package/dist/Grammar-D6jZdJQ3.d.mts +169 -0
- package/dist/Grammar-mQ8CgE1M.mjs +258 -0
- package/dist/Grammar-mQ8CgE1M.mjs.map +1 -0
- package/dist/GraphStore-DhUa4nFO.d.mts +45 -0
- package/dist/GraphStore-DvbqwKy1.mjs +212 -0
- package/dist/GraphStore-DvbqwKy1.mjs.map +1 -0
- package/dist/Iri-BB8Ks-BF.d.mts +84 -0
- package/dist/Iri-Cr92gsQV.mjs +232 -0
- package/dist/Iri-Cr92gsQV.mjs.map +1 -0
- package/dist/Iri.d.mts +2 -0
- package/dist/Iri.mjs +1 -0
- package/dist/Isomorphism-B6qTY-xy.d.mts +28 -0
- package/dist/Isomorphism-BmqxhKyJ.mjs +232 -0
- package/dist/Isomorphism-BmqxhKyJ.mjs.map +1 -0
- package/dist/Isomorphism-DInKnWIy.mjs +390 -0
- package/dist/Isomorphism-DInKnWIy.mjs.map +1 -0
- package/dist/Isomorphism-DvSfZXLR.d.mts +37 -0
- package/dist/Isomorphism.d.mts +2 -0
- package/dist/Isomorphism.mjs +1 -0
- package/dist/JsonLd-DOwS2FHg.mjs +2836 -0
- package/dist/JsonLd-DOwS2FHg.mjs.map +1 -0
- package/dist/JsonLd-DobFrQNa.d.mts +242 -0
- package/dist/JsonLdError-C9J-BxfG.mjs +106 -0
- package/dist/JsonLdError-C9J-BxfG.mjs.map +1 -0
- package/dist/JsonLdError-DSOfHkOy.d.mts +43 -0
- package/dist/LanguageTag-BCu8rcmX.d.mts +38 -0
- package/dist/LanguageTag-BcOpMGZ4.mjs +89 -0
- package/dist/LanguageTag-BcOpMGZ4.mjs.map +1 -0
- package/dist/LanguageTag.d.mts +2 -0
- package/dist/LanguageTag.mjs +1 -0
- package/dist/Lexical-Cg0JHrf6.mjs +243 -0
- package/dist/Lexical-Cg0JHrf6.mjs.map +1 -0
- package/dist/Lexical-D3zySYVS.d.mts +163 -0
- package/dist/Manifest-B6xf6mUc.mjs +148 -0
- package/dist/Manifest-B6xf6mUc.mjs.map +1 -0
- package/dist/Manifest-DQUMRBFr.d.mts +98 -0
- package/dist/Mapping-BgG74x3U.d.mts +63 -0
- package/dist/Mapping-Dc1ZGirk.mjs +259 -0
- package/dist/Mapping-Dc1ZGirk.mjs.map +1 -0
- package/dist/Mcp-1DwTTKOK.mjs +48 -0
- package/dist/Mcp-1DwTTKOK.mjs.map +1 -0
- package/dist/Mcp-d-pX9erx.d.mts +25 -0
- package/dist/Message-CK_DGQuu.mjs +95 -0
- package/dist/Message-CK_DGQuu.mjs.map +1 -0
- package/dist/Message-fIvTJSXM.d.mts +49 -0
- package/dist/Metadata-BbffwGDZ.mjs +445 -0
- package/dist/Metadata-BbffwGDZ.mjs.map +1 -0
- package/dist/Metadata-CEt3yc1-.d.mts +901 -0
- package/dist/Metadata-D4HsQNXr.mjs +1273 -0
- package/dist/Metadata-D4HsQNXr.mjs.map +1 -0
- package/dist/Metadata-nzS7QKRj.d.mts +350 -0
- package/dist/Model-DaMmxurZ.d.mts +293 -0
- package/dist/Model-DlPzRYhF.mjs +528 -0
- package/dist/Model-DlPzRYhF.mjs.map +1 -0
- package/dist/N3-BzyV-KIH.mjs +974 -0
- package/dist/N3-BzyV-KIH.mjs.map +1 -0
- package/dist/N3-DSLnonDn.d.mts +100 -0
- package/dist/NQuads-C1VBOJ3-.mjs +117 -0
- package/dist/NQuads-C1VBOJ3-.mjs.map +1 -0
- package/dist/NQuads-FjFZikFg.d.mts +76 -0
- package/dist/NTriples-D-kLSEAf.mjs +120 -0
- package/dist/NTriples-D-kLSEAf.mjs.map +1 -0
- package/dist/NTriples-D34OspfW.d.mts +76 -0
- package/dist/Namespace-ClTUO_ya.d.mts +42 -0
- package/dist/Namespace-DHFIsORd.mjs +75 -0
- package/dist/Namespace-DHFIsORd.mjs.map +1 -0
- package/dist/Namespace.d.mts +2 -0
- package/dist/Namespace.mjs +1 -0
- package/dist/Namespaces-Bz85vpjW.mjs +235 -0
- package/dist/Namespaces-Bz85vpjW.mjs.map +1 -0
- package/dist/Namespaces-C1jW6EDo.d.mts +178 -0
- package/dist/Optimizer-BWWQRQPY.mjs +168 -0
- package/dist/Optimizer-BWWQRQPY.mjs.map +1 -0
- package/dist/Optimizer-DUpUow6k.d.mts +26 -0
- package/dist/Options-BLBGL4HE.mjs +72 -0
- package/dist/Options-BLBGL4HE.mjs.map +1 -0
- package/dist/Options-BommQwP9.d.mts +127 -0
- package/dist/Packet-CA76wVX3.mjs +197 -0
- package/dist/Packet-CA76wVX3.mjs.map +1 -0
- package/dist/Packet-Cy5g6XuW.d.mts +130 -0
- package/dist/Parser-p1Gyr0QN.d.mts +85 -0
- package/dist/Parser-xARsOWXk.mjs +1317 -0
- package/dist/Parser-xARsOWXk.mjs.map +1 -0
- package/dist/Path-B20Yp8KE.mjs +284 -0
- package/dist/Path-B20Yp8KE.mjs.map +1 -0
- package/dist/Path-DRB8e-Zn.d.mts +181 -0
- package/dist/Pdf-B9RZn--8.d.mts +77 -0
- package/dist/Pdf-CKK6dkZn.mjs +89 -0
- package/dist/Pdf-CKK6dkZn.mjs.map +1 -0
- package/dist/PdfA-CUjSuntT.d.mts +109 -0
- package/dist/PdfA-CleOhuL6.mjs +124 -0
- package/dist/PdfA-CleOhuL6.mjs.map +1 -0
- package/dist/Photoshop-CiHo80qV.mjs +233 -0
- package/dist/Photoshop-CiHo80qV.mjs.map +1 -0
- package/dist/Photoshop-udKZ0I3N.d.mts +204 -0
- package/dist/PrefixMap-BHFPDLO-.mjs +103 -0
- package/dist/PrefixMap-BHFPDLO-.mjs.map +1 -0
- package/dist/PrefixMap-BMlXrWNd.d.mts +64 -0
- package/dist/PrefixMap.d.mts +2 -0
- package/dist/PrefixMap.mjs +1 -0
- package/dist/Processor-CMmecFsb.mjs +198 -0
- package/dist/Processor-CMmecFsb.mjs.map +1 -0
- package/dist/Processor-FJPvO0CB.d.mts +435 -0
- package/dist/Protocol-BA7OkNgp.mjs +202 -0
- package/dist/Protocol-BA7OkNgp.mjs.map +1 -0
- package/dist/Protocol-DlSoiIKB.d.mts +108 -0
- package/dist/ProtocolErrors-Ckfejnl6.d.mts +84 -0
- package/dist/ProtocolErrors-czDPVcX5.mjs +129 -0
- package/dist/ProtocolErrors-czDPVcX5.mjs.map +1 -0
- package/dist/Query-D9A6LOpP.mjs +364 -0
- package/dist/Query-D9A6LOpP.mjs.map +1 -0
- package/dist/Query-KqSkTYyG.d.mts +96 -0
- package/dist/QueryBuilder-BLK9Vfbz.mjs +674 -0
- package/dist/QueryBuilder-BLK9Vfbz.mjs.map +1 -0
- package/dist/QueryBuilder-DUqh4ECe.d.mts +529 -0
- package/dist/RdfJs-D6-49XB5.mjs +86 -0
- package/dist/RdfJs-D6-49XB5.mjs.map +1 -0
- package/dist/RdfJs-DXFqdWrd.d.mts +76 -0
- package/dist/RdfJs.d.mts +2 -0
- package/dist/RdfJs.mjs +1 -0
- package/dist/RdfJson-5aKPDlPS.d.mts +297 -0
- package/dist/RdfJson-BNocgW4f.mjs +548 -0
- package/dist/RdfJson-BNocgW4f.mjs.map +1 -0
- package/dist/RdfXml-DJ9G4gOq.mjs +1014 -0
- package/dist/RdfXml-DJ9G4gOq.mjs.map +1 -0
- package/dist/RdfXml-_N7yxWEp.d.mts +149 -0
- package/dist/Rdfs-BRVPsAjc.d.mts +84 -0
- package/dist/Rdfs-CyY2wVHI.mjs +167 -0
- package/dist/Rdfs-CyY2wVHI.mjs.map +1 -0
- package/dist/Rdfs.d.mts +2 -0
- package/dist/Rdfs.mjs +1 -0
- package/dist/Reasoner-BxYhx9s7.d.mts +94 -0
- package/dist/Reasoner-Cw90zrjv.mjs +798 -0
- package/dist/Reasoner-Cw90zrjv.mjs.map +1 -0
- package/dist/Reification-BmN4EfnM.d.mts +40 -0
- package/dist/Reification-dpqQvjDE.mjs +82 -0
- package/dist/Reification-dpqQvjDE.mjs.map +1 -0
- package/dist/Reification.d.mts +2 -0
- package/dist/Reification.mjs +1 -0
- package/dist/Report-XHs8htzK.mjs +121 -0
- package/dist/Report-XHs8htzK.mjs.map +1 -0
- package/dist/Report-hcXsc_19.d.mts +115 -0
- package/dist/Resource-B1cI8FyJ.d.mts +218 -0
- package/dist/Resource-sIEeNh15.mjs +374 -0
- package/dist/Resource-sIEeNh15.mjs.map +1 -0
- package/dist/Resource.d.mts +2 -0
- package/dist/Resource.mjs +1 -0
- package/dist/Results-BVNEC-Wt.d.mts +277 -0
- package/dist/Results-peJjGA2V.mjs +621 -0
- package/dist/Results-peJjGA2V.mjs.map +1 -0
- package/dist/ResultsStream-BhF0OcNn.mjs +639 -0
- package/dist/ResultsStream-BhF0OcNn.mjs.map +1 -0
- package/dist/ResultsStream-toYMXrOe.d.mts +100 -0
- package/dist/SemAct-CDchtk49.d.mts +96 -0
- package/dist/SemAct-CmyT4yI8.mjs +118 -0
- package/dist/SemAct-CmyT4yI8.mjs.map +1 -0
- package/dist/Serialization-DP9H5bYQ.mjs +514 -0
- package/dist/Serialization-DP9H5bYQ.mjs.map +1 -0
- package/dist/Serialization-Df8iVauT.d.mts +392 -0
- package/dist/Serialization.d.mts +2 -0
- package/dist/Serialization.mjs +1 -0
- package/dist/ServerFormats-CrRnjjf6.mjs +64 -0
- package/dist/ServerFormats-CrRnjjf6.mjs.map +1 -0
- package/dist/ServerFormats-DjC9NsO_.d.mts +36 -0
- package/dist/ServiceDescription-BERsvg-k.d.mts +54 -0
- package/dist/ServiceDescription-Dq5hxeGy.mjs +97 -0
- package/dist/ServiceDescription-Dq5hxeGy.mjs.map +1 -0
- package/dist/ShExC-Ctf-06CL.d.mts +67 -0
- package/dist/ShExC-DJbB19i3.mjs +1181 -0
- package/dist/ShExC-DJbB19i3.mjs.map +1 -0
- package/dist/ShExError-BeYFD9rv.d.mts +61 -0
- package/dist/ShExError-CVtq2NnM.mjs +64 -0
- package/dist/ShExError-CVtq2NnM.mjs.map +1 -0
- package/dist/ShExJ-C1u_wgyD.d.mts +531 -0
- package/dist/ShExJ-COiPd9Xw.mjs +532 -0
- package/dist/ShExJ-COiPd9Xw.mjs.map +1 -0
- package/dist/ShExR-ByR4mCh-.mjs +480 -0
- package/dist/ShExR-ByR4mCh-.mjs.map +1 -0
- package/dist/ShExR-CqONNNBz.d.mts +40 -0
- package/dist/ShapeMap-C422pxKl.d.mts +140 -0
- package/dist/ShapeMap-CsgKxB2G.mjs +181 -0
- package/dist/ShapeMap-CsgKxB2G.mjs.map +1 -0
- package/dist/Shapes-18GjfyBV.mjs +597 -0
- package/dist/Shapes-18GjfyBV.mjs.map +1 -0
- package/dist/Shapes-BQFDFc4Q.d.mts +306 -0
- package/dist/Sparql-BiYFMSpl.mjs +29 -0
- package/dist/Sparql-BiYFMSpl.mjs.map +1 -0
- package/dist/Sparql-DecpWTPh.d.mts +97 -0
- package/dist/SparqlClient-C_CjXbwj.mjs +333 -0
- package/dist/SparqlClient-C_CjXbwj.mjs.map +1 -0
- package/dist/SparqlClient-UPc88SHy.d.mts +290 -0
- package/dist/SparqlServer-CbUW-pVj.d.mts +71 -0
- package/dist/SparqlServer-Db7w7Cll.mjs +308 -0
- package/dist/SparqlServer-Db7w7Cll.mjs.map +1 -0
- package/dist/SqlStore-BBqFx-Wl.mjs +684 -0
- package/dist/SqlStore-BBqFx-Wl.mjs.map +1 -0
- package/dist/SqlStore-CCJo5I-e.d.mts +117 -0
- package/dist/Store-D0jmVfb3.d.mts +166 -0
- package/dist/Store-DH3Ow1GO.mjs +162 -0
- package/dist/Store-DH3Ow1GO.mjs.map +1 -0
- package/dist/Structure-BjMo4Bkc.mjs +334 -0
- package/dist/Structure-BjMo4Bkc.mjs.map +1 -0
- package/dist/Structure-n4CmDiab.d.mts +63 -0
- package/dist/Table-BGgm9nbM.mjs +538 -0
- package/dist/Table-BGgm9nbM.mjs.map +1 -0
- package/dist/Table-C3reiVrb.d.mts +261 -0
- package/dist/Term-B_4ezyXq.d.mts +619 -0
- package/dist/Term-CFLG1qu4.mjs +991 -0
- package/dist/Term-CFLG1qu4.mjs.map +1 -0
- package/dist/Term.d.mts +2 -0
- package/dist/Term.mjs +1 -0
- package/dist/TermCodec-CDZiEztW.mjs +397 -0
- package/dist/TermCodec-CDZiEztW.mjs.map +1 -0
- package/dist/TermCodec-_03bDXr0.d.mts +209 -0
- package/dist/TermCodec.d.mts +2 -0
- package/dist/TermCodec.mjs +1 -0
- package/dist/TermJson-BOPe0yve.d.mts +232 -0
- package/dist/TermJson-FAg3lvjl.mjs +142 -0
- package/dist/TermJson-FAg3lvjl.mjs.map +1 -0
- package/dist/TermJson.d.mts +2 -0
- package/dist/TermJson.mjs +1 -0
- package/dist/TermSchema-Bn9CoaYA.mjs +30 -0
- package/dist/TermSchema-Bn9CoaYA.mjs.map +1 -0
- package/dist/TermSchema-DBO6MroX.d.mts +6 -0
- package/dist/Testing-B-aPCpmu.mjs +63 -0
- package/dist/Testing-B-aPCpmu.mjs.map +1 -0
- package/dist/Testing-CF6s8P3O.d.mts +53 -0
- package/dist/Tiff-CR-2QeSI.d.mts +148 -0
- package/dist/Tiff-DlsOlZPM.mjs +169 -0
- package/dist/Tiff-DlsOlZPM.mjs.map +1 -0
- package/dist/Tools-7MdRKccr.d.mts +583 -0
- package/dist/Tools-ryxphZbI.mjs +329 -0
- package/dist/Tools-ryxphZbI.mjs.map +1 -0
- package/dist/Translate-B0YKKANS.d.mts +37 -0
- package/dist/Translate-gzWPq0Ia.mjs +308 -0
- package/dist/Translate-gzWPq0Ia.mjs.map +1 -0
- package/dist/Translation-DC9rLm2y.mjs +1535 -0
- package/dist/Translation-DC9rLm2y.mjs.map +1 -0
- package/dist/Translation-K4L9XBqX.d.mts +103 -0
- package/dist/TriG-BkqtqZ6Y.mjs +125 -0
- package/dist/TriG-BkqtqZ6Y.mjs.map +1 -0
- package/dist/TriG-xDjdDatp.d.mts +86 -0
- package/dist/Turtle-Bv1_bisW.d.mts +85 -0
- package/dist/Turtle-CZVUOtwl.mjs +133 -0
- package/dist/Turtle-CZVUOtwl.mjs.map +1 -0
- package/dist/ValidationReport-DBdJshW5.mjs +285 -0
- package/dist/ValidationReport-DBdJshW5.mjs.map +1 -0
- package/dist/ValidationReport-Dx3WxY0h.d.mts +192 -0
- package/dist/Validator-227ff9Rn.d.mts +179 -0
- package/dist/Validator-BzXMKDeJ.mjs +1162 -0
- package/dist/Validator-BzXMKDeJ.mjs.map +1 -0
- package/dist/Validator-Dl8cih5V.d.mts +207 -0
- package/dist/Validator-DmUfMbpL.mjs +568 -0
- package/dist/Validator-DmUfMbpL.mjs.map +1 -0
- package/dist/Vocab-DceKywpq.mjs +168 -0
- package/dist/Vocab-DceKywpq.mjs.map +1 -0
- package/dist/Vocab-Df-L-IwZ.d.mts +46 -0
- package/dist/Vocab-Do56UgBX.d.mts +81 -0
- package/dist/Vocab-TfpWG1Ne.mjs +118 -0
- package/dist/Vocab-TfpWG1Ne.mjs.map +1 -0
- package/dist/Vocab.d.mts +2 -0
- package/dist/Vocab.mjs +1 -0
- package/dist/Vocabulary-C6-GPOy4.d.mts +22 -0
- package/dist/Vocabulary-CnQl-_Z4.mjs +179 -0
- package/dist/Vocabulary-CnQl-_Z4.mjs.map +1 -0
- package/dist/Xml-B7iihYCS.mjs +976 -0
- package/dist/Xml-B7iihYCS.mjs.map +1 -0
- package/dist/Xml-CmfHS7SW.d.mts +267 -0
- package/dist/Xmp-D7CKA7MZ.d.mts +111 -0
- package/dist/Xmp-DCGN9pOD.mjs +307 -0
- package/dist/Xmp-DCGN9pOD.mjs.map +1 -0
- package/dist/XmpBasic-CP38jpR_.mjs +73 -0
- package/dist/XmpBasic-CP38jpR_.mjs.map +1 -0
- package/dist/XmpBasic-Cmbe53rQ.d.mts +64 -0
- package/dist/XmpMM-BfL_TgJF.mjs +73 -0
- package/dist/XmpMM-BfL_TgJF.mjs.map +1 -0
- package/dist/XmpMM-Dqm3IX8p.d.mts +63 -0
- package/dist/XmpRights-BKPtoI5L.mjs +73 -0
- package/dist/XmpRights-BKPtoI5L.mjs.map +1 -0
- package/dist/XmpRights-CaALZQz2.d.mts +63 -0
- package/dist/Xsd-Cdgx5zRB.mjs +405 -0
- package/dist/Xsd-Cdgx5zRB.mjs.map +1 -0
- package/dist/Xsd-D2elNhSm.d.mts +187 -0
- package/dist/Xsd.d.mts +2 -0
- package/dist/Xsd.mjs +1 -0
- package/dist/accessors-CEjaojuQ.mjs +28 -0
- package/dist/accessors-CEjaojuQ.mjs.map +1 -0
- package/dist/codec-Lb2T96rv.d.mts +20 -0
- package/dist/codec-QNo7IdJF.mjs +389 -0
- package/dist/codec-QNo7IdJF.mjs.map +1 -0
- package/dist/csvw/Csv2Json.d.mts +2 -0
- package/dist/csvw/Csv2Json.mjs +1 -0
- package/dist/csvw/Csv2Rdf.d.mts +2 -0
- package/dist/csvw/Csv2Rdf.mjs +1 -0
- package/dist/csvw/Datatype.d.mts +2 -0
- package/dist/csvw/Datatype.mjs +1 -0
- package/dist/csvw/Metadata.d.mts +2 -0
- package/dist/csvw/Metadata.mjs +1 -0
- package/dist/csvw/Processor.d.mts +2 -0
- package/dist/csvw/Processor.mjs +1 -0
- package/dist/csvw/Report.d.mts +2 -0
- package/dist/csvw/Report.mjs +1 -0
- package/dist/csvw/Table.d.mts +2 -0
- package/dist/csvw/Table.mjs +1 -0
- package/dist/csvw/index.d.mts +8 -0
- package/dist/csvw/index.mjs +8 -0
- package/dist/index-Cms2TYfC.d.mts +16 -0
- package/dist/index-Ct8xFmRs.d.mts +7 -0
- package/dist/index-D4QFwOjm.d.mts +28 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.mjs +17 -0
- package/dist/jsonld/DocumentLoader.d.mts +2 -0
- package/dist/jsonld/DocumentLoader.mjs +1 -0
- package/dist/jsonld/JsonLd.d.mts +2 -0
- package/dist/jsonld/JsonLd.mjs +1 -0
- package/dist/jsonld/JsonLdError.d.mts +2 -0
- package/dist/jsonld/JsonLdError.mjs +1 -0
- package/dist/jsonld/index.d.mts +4 -0
- package/dist/jsonld/index.mjs +4 -0
- package/dist/lists-D-OpQ0Ir.mjs +77 -0
- package/dist/lists-D-OpQ0Ir.mjs.map +1 -0
- package/dist/loop-D5-gNx9K.mjs +21 -0
- package/dist/loop-D5-gNx9K.mjs.map +1 -0
- package/dist/loop-DZ2hXlr_.mjs +21 -0
- package/dist/loop-DZ2hXlr_.mjs.map +1 -0
- package/dist/n3/Isomorphism.d.mts +2 -0
- package/dist/n3/Isomorphism.mjs +1 -0
- package/dist/n3/Mapping.d.mts +2 -0
- package/dist/n3/Mapping.mjs +1 -0
- package/dist/n3/Model.d.mts +2 -0
- package/dist/n3/Model.mjs +1 -0
- package/dist/n3/N3.d.mts +2 -0
- package/dist/n3/N3.mjs +1 -0
- package/dist/n3/Reasoner.d.mts +2 -0
- package/dist/n3/Reasoner.mjs +2 -0
- package/dist/n3/Vocab.d.mts +2 -0
- package/dist/n3/Vocab.mjs +1 -0
- package/dist/n3/index.d.mts +7 -0
- package/dist/n3/index.mjs +7 -0
- package/dist/ntriples/Grammar.d.mts +2 -0
- package/dist/ntriples/Grammar.mjs +1 -0
- package/dist/ntriples/Lexical.d.mts +2 -0
- package/dist/ntriples/Lexical.mjs +1 -0
- package/dist/ntriples/NQuads.d.mts +2 -0
- package/dist/ntriples/NQuads.mjs +1 -0
- package/dist/ntriples/NTriples.d.mts +2 -0
- package/dist/ntriples/NTriples.mjs +1 -0
- package/dist/ntriples/index.d.mts +5 -0
- package/dist/ntriples/index.mjs +5 -0
- package/dist/rdfjson/RdfJson.d.mts +2 -0
- package/dist/rdfjson/RdfJson.mjs +1 -0
- package/dist/rdfjson/index.d.mts +2 -0
- package/dist/rdfjson/index.mjs +1 -0
- package/dist/rdfxml/RdfXml.d.mts +2 -0
- package/dist/rdfxml/RdfXml.mjs +1 -0
- package/dist/rdfxml/Xml.d.mts +2 -0
- package/dist/rdfxml/Xml.mjs +1 -0
- package/dist/rdfxml/index.d.mts +3 -0
- package/dist/rdfxml/index.mjs +3 -0
- package/dist/rolldown-runtime-D8jQpDG7.mjs +12 -0
- package/dist/serialize-96KBvKaf.mjs +1463 -0
- package/dist/serialize-96KBvKaf.mjs.map +1 -0
- package/dist/serialize-DsoXdM6n.d.mts +37 -0
- package/dist/shacl/Message.d.mts +2 -0
- package/dist/shacl/Message.mjs +1 -0
- package/dist/shacl/Path.d.mts +2 -0
- package/dist/shacl/Path.mjs +1 -0
- package/dist/shacl/Shapes.d.mts +2 -0
- package/dist/shacl/Shapes.mjs +1 -0
- package/dist/shacl/Sparql.d.mts +2 -0
- package/dist/shacl/Sparql.mjs +1 -0
- package/dist/shacl/ValidationReport.d.mts +2 -0
- package/dist/shacl/ValidationReport.mjs +1 -0
- package/dist/shacl/Validator.d.mts +2 -0
- package/dist/shacl/Validator.mjs +1 -0
- package/dist/shacl/Vocabulary.d.mts +2 -0
- package/dist/shacl/Vocabulary.mjs +1 -0
- package/dist/shacl/index.d.mts +9 -0
- package/dist/shacl/index.mjs +8 -0
- package/dist/shex/SemAct.d.mts +2 -0
- package/dist/shex/SemAct.mjs +1 -0
- package/dist/shex/ShExC.d.mts +2 -0
- package/dist/shex/ShExC.mjs +1 -0
- package/dist/shex/ShExError.d.mts +2 -0
- package/dist/shex/ShExError.mjs +1 -0
- package/dist/shex/ShExJ.d.mts +2 -0
- package/dist/shex/ShExJ.mjs +2 -0
- package/dist/shex/ShExR.d.mts +2 -0
- package/dist/shex/ShExR.mjs +1 -0
- package/dist/shex/ShapeMap.d.mts +2 -0
- package/dist/shex/ShapeMap.mjs +1 -0
- package/dist/shex/Structure.d.mts +2 -0
- package/dist/shex/Structure.mjs +2 -0
- package/dist/shex/Validator.d.mts +2 -0
- package/dist/shex/Validator.mjs +4 -0
- package/dist/shex/index.d.mts +9 -0
- package/dist/shex/index.mjs +9 -0
- package/dist/sparql/Algebra.d.mts +2 -0
- package/dist/sparql/Algebra.mjs +1 -0
- package/dist/sparql/Ast.d.mts +2 -0
- package/dist/sparql/Ast.mjs +1 -0
- package/dist/sparql/Bindings.d.mts +2 -0
- package/dist/sparql/Bindings.mjs +1 -0
- package/dist/sparql/ClientErrors.d.mts +2 -0
- package/dist/sparql/ClientErrors.mjs +1 -0
- package/dist/sparql/ClientFormats.d.mts +2 -0
- package/dist/sparql/ClientFormats.mjs +1 -0
- package/dist/sparql/Engine.d.mts +2 -0
- package/dist/sparql/Engine.mjs +1 -0
- package/dist/sparql/Errors.d.mts +2 -0
- package/dist/sparql/Errors.mjs +1 -0
- package/dist/sparql/Expression.d.mts +2 -0
- package/dist/sparql/Expression.mjs +1 -0
- package/dist/sparql/Federation.d.mts +2 -0
- package/dist/sparql/Federation.mjs +1 -0
- package/dist/sparql/Functions.d.mts +2 -0
- package/dist/sparql/Functions.mjs +1 -0
- package/dist/sparql/Generator.d.mts +2 -0
- package/dist/sparql/Generator.mjs +1 -0
- package/dist/sparql/GraphStore.d.mts +2 -0
- package/dist/sparql/GraphStore.mjs +1 -0
- package/dist/sparql/Optimizer.d.mts +2 -0
- package/dist/sparql/Optimizer.mjs +1 -0
- package/dist/sparql/Options.d.mts +2 -0
- package/dist/sparql/Options.mjs +1 -0
- package/dist/sparql/Parser.d.mts +2 -0
- package/dist/sparql/Parser.mjs +1 -0
- package/dist/sparql/Protocol.d.mts +2 -0
- package/dist/sparql/Protocol.mjs +1 -0
- package/dist/sparql/ProtocolErrors.d.mts +2 -0
- package/dist/sparql/ProtocolErrors.mjs +1 -0
- package/dist/sparql/QueryBuilder.d.mts +2 -0
- package/dist/sparql/QueryBuilder.mjs +1 -0
- package/dist/sparql/Results.d.mts +2 -0
- package/dist/sparql/Results.mjs +1 -0
- package/dist/sparql/ResultsStream.d.mts +2 -0
- package/dist/sparql/ResultsStream.mjs +1 -0
- package/dist/sparql/ServerFormats.d.mts +2 -0
- package/dist/sparql/ServerFormats.mjs +1 -0
- package/dist/sparql/ServiceDescription.d.mts +2 -0
- package/dist/sparql/ServiceDescription.mjs +1 -0
- package/dist/sparql/SparqlClient.d.mts +2 -0
- package/dist/sparql/SparqlClient.mjs +1 -0
- package/dist/sparql/SparqlServer.d.mts +2 -0
- package/dist/sparql/SparqlServer.mjs +1 -0
- package/dist/sparql/Store.d.mts +2 -0
- package/dist/sparql/Store.mjs +1 -0
- package/dist/sparql/TermSchema.d.mts +3 -0
- package/dist/sparql/TermSchema.mjs +2 -0
- package/dist/sparql/Testing.d.mts +2 -0
- package/dist/sparql/Testing.mjs +1 -0
- package/dist/sparql/Translate.d.mts +2 -0
- package/dist/sparql/Translate.mjs +1 -0
- package/dist/sparql/ai/Mcp.d.mts +2 -0
- package/dist/sparql/ai/Mcp.mjs +1 -0
- package/dist/sparql/ai/Tools.d.mts +2 -0
- package/dist/sparql/ai/Tools.mjs +1 -0
- package/dist/sparql/ai/index.d.mts +3 -0
- package/dist/sparql/ai/index.mjs +3 -0
- package/dist/sparql/index.d.mts +30 -0
- package/dist/sparql/index.mjs +29 -0
- package/dist/sparql/shacl/Evaluator.d.mts +2 -0
- package/dist/sparql/shacl/Evaluator.mjs +1 -0
- package/dist/sparql/shacl/Query.d.mts +2 -0
- package/dist/sparql/shacl/Query.mjs +1 -0
- package/dist/sparql/shacl/index.d.mts +3 -0
- package/dist/sparql/shacl/index.mjs +3 -0
- package/dist/sparql/sql/Dialect.d.mts +2 -0
- package/dist/sparql/sql/Dialect.mjs +1 -0
- package/dist/sparql/sql/SqlStore.d.mts +2 -0
- package/dist/sparql/sql/SqlStore.mjs +1 -0
- package/dist/sparql/sql/Translation.d.mts +2 -0
- package/dist/sparql/sql/Translation.mjs +1 -0
- package/dist/sparql/sql/index.d.mts +4 -0
- package/dist/sparql/sql/index.mjs +4 -0
- package/dist/stringMap-D5YIpYtR.mjs +38 -0
- package/dist/stringMap-D5YIpYtR.mjs.map +1 -0
- package/dist/tokens-C6BzeodQ.mjs +24 -0
- package/dist/tokens-C6BzeodQ.mjs.map +1 -0
- package/dist/turtle/Grammar.d.mts +3 -0
- package/dist/turtle/Grammar.mjs +3 -0
- package/dist/turtle/Manifest.d.mts +2 -0
- package/dist/turtle/Manifest.mjs +1 -0
- package/dist/turtle/TriG.d.mts +2 -0
- package/dist/turtle/TriG.mjs +1 -0
- package/dist/turtle/Turtle.d.mts +2 -0
- package/dist/turtle/Turtle.mjs +1 -0
- package/dist/turtle/index.d.mts +5 -0
- package/dist/turtle/index.mjs +5 -0
- package/dist/xmp/DublinCore.d.mts +2 -0
- package/dist/xmp/DublinCore.mjs +1 -0
- package/dist/xmp/Exif.d.mts +2 -0
- package/dist/xmp/Exif.mjs +1 -0
- package/dist/xmp/Extract.d.mts +2 -0
- package/dist/xmp/Extract.mjs +1 -0
- package/dist/xmp/Metadata.d.mts +2 -0
- package/dist/xmp/Metadata.mjs +1 -0
- package/dist/xmp/Namespaces.d.mts +2 -0
- package/dist/xmp/Namespaces.mjs +1 -0
- package/dist/xmp/Packet.d.mts +2 -0
- package/dist/xmp/Packet.mjs +1 -0
- package/dist/xmp/Pdf.d.mts +2 -0
- package/dist/xmp/Pdf.mjs +1 -0
- package/dist/xmp/PdfA.d.mts +2 -0
- package/dist/xmp/PdfA.mjs +1 -0
- package/dist/xmp/Photoshop.d.mts +2 -0
- package/dist/xmp/Photoshop.mjs +1 -0
- package/dist/xmp/Tiff.d.mts +2 -0
- package/dist/xmp/Tiff.mjs +1 -0
- package/dist/xmp/Xmp.d.mts +2 -0
- package/dist/xmp/Xmp.mjs +3 -0
- package/dist/xmp/XmpBasic.d.mts +2 -0
- package/dist/xmp/XmpBasic.mjs +1 -0
- package/dist/xmp/XmpMM.d.mts +2 -0
- package/dist/xmp/XmpMM.mjs +1 -0
- package/dist/xmp/XmpRights.d.mts +2 -0
- package/dist/xmp/XmpRights.mjs +1 -0
- package/dist/xmp/index.d.mts +15 -0
- package/dist/xmp/index.mjs +15 -0
- package/package.json +195 -3
- package/src/Dataset.ts +1085 -0
- package/src/Iri.ts +322 -0
- package/src/Isomorphism.ts +319 -0
- package/src/LanguageTag.ts +98 -0
- package/src/Namespace.ts +100 -0
- package/src/PrefixMap.ts +117 -0
- package/src/RdfJs.ts +146 -0
- package/src/Rdfs.ts +241 -0
- package/src/Reification.ts +103 -0
- package/src/Resource.ts +873 -0
- package/src/Serialization.ts +775 -0
- package/src/Term.ts +1300 -0
- package/src/TermCodec.ts +505 -0
- package/src/TermJson.ts +313 -0
- package/src/Vocab.ts +161 -0
- package/src/Xsd.ts +573 -0
- package/src/csvw/Csv2Json.ts +265 -0
- package/src/csvw/Csv2Rdf.ts +268 -0
- package/src/csvw/Datatype.ts +589 -0
- package/src/csvw/Metadata.ts +1523 -0
- package/src/csvw/Processor.ts +276 -0
- package/src/csvw/Report.ts +135 -0
- package/src/csvw/Table.ts +759 -0
- package/src/csvw/index.ts +22 -0
- package/src/csvw/internal/dateFormat.ts +202 -0
- package/src/csvw/internal/json.ts +104 -0
- package/src/csvw/internal/numberFormat.ts +369 -0
- package/src/csvw/internal/object.ts +23 -0
- package/src/csvw/internal/uriTemplate.ts +130 -0
- package/src/csvw/internal/url.ts +93 -0
- package/src/csvw/internal/vocab.ts +282 -0
- package/src/index.ts +40 -0
- package/src/internal/arbitrary.ts +148 -0
- package/src/internal/loop.ts +24 -0
- package/src/internal/pending.ts +20 -0
- package/src/internal/textDecoder.ts +183 -0
- package/src/jsonld/DocumentLoader.ts +512 -0
- package/src/jsonld/JsonLd.ts +847 -0
- package/src/jsonld/JsonLdError.ts +105 -0
- package/src/jsonld/index.ts +13 -0
- package/src/jsonld/internal/compact.ts +852 -0
- package/src/jsonld/internal/context.ts +781 -0
- package/src/jsonld/internal/expand.ts +614 -0
- package/src/jsonld/internal/flatten.ts +243 -0
- package/src/jsonld/internal/frame.ts +559 -0
- package/src/jsonld/internal/iri.ts +52 -0
- package/src/jsonld/internal/json.ts +19 -0
- package/src/jsonld/internal/linkHeader.ts +60 -0
- package/src/jsonld/internal/rdf.ts +481 -0
- package/src/jsonld/internal/util.ts +367 -0
- package/src/n3/Isomorphism.ts +476 -0
- package/src/n3/Mapping.ts +332 -0
- package/src/n3/Model.ts +696 -0
- package/src/n3/N3.ts +237 -0
- package/src/n3/Reasoner.ts +568 -0
- package/src/n3/Vocab.ts +111 -0
- package/src/n3/index.ts +21 -0
- package/src/n3/internal/builtins.ts +668 -0
- package/src/n3/internal/guards.ts +23 -0
- package/src/n3/internal/lists.ts +106 -0
- package/src/n3/internal/loop.ts +28 -0
- package/src/n3/internal/parser.ts +1032 -0
- package/src/n3/internal/writer.ts +344 -0
- package/src/ntriples/Grammar.ts +317 -0
- package/src/ntriples/Lexical.ts +321 -0
- package/src/ntriples/NQuads.ts +140 -0
- package/src/ntriples/NTriples.ts +143 -0
- package/src/ntriples/index.ts +14 -0
- package/src/ntriples/internal/codec.ts +229 -0
- package/src/ntriples/internal/lineParser.ts +326 -0
- package/src/ntriples/internal/loop.ts +14 -0
- package/src/rdfjson/RdfJson.ts +806 -0
- package/src/rdfjson/index.ts +9 -0
- package/src/rdfjson/internal/json.ts +19 -0
- package/src/rdfxml/RdfXml.ts +1408 -0
- package/src/rdfxml/Xml.ts +419 -0
- package/src/rdfxml/index.ts +11 -0
- package/src/rdfxml/internal/chars.ts +73 -0
- package/src/rdfxml/internal/loop.ts +24 -0
- package/src/rdfxml/internal/parser.ts +1265 -0
- package/src/shacl/Message.ts +102 -0
- package/src/shacl/Path.ts +483 -0
- package/src/shacl/Shapes.ts +913 -0
- package/src/shacl/Sparql.ts +128 -0
- package/src/shacl/ValidationReport.ts +438 -0
- package/src/shacl/Validator.ts +1156 -0
- package/src/shacl/Vocabulary.ts +180 -0
- package/src/shacl/index.ts +22 -0
- package/src/shacl/internal/compare.ts +128 -0
- package/src/shacl/internal/regex.ts +56 -0
- package/src/shex/SemAct.ts +197 -0
- package/src/shex/ShExC.ts +415 -0
- package/src/shex/ShExError.ts +71 -0
- package/src/shex/ShExJ.ts +893 -0
- package/src/shex/ShExR.ts +640 -0
- package/src/shex/ShapeMap.ts +385 -0
- package/src/shex/Structure.ts +467 -0
- package/src/shex/Validator.ts +395 -0
- package/src/shex/index.ts +24 -0
- package/src/shex/internal/engine.ts +682 -0
- package/src/shex/internal/iterate.ts +14 -0
- package/src/shex/internal/json.ts +12 -0
- package/src/shex/internal/lexer.ts +56 -0
- package/src/shex/internal/nodeConstraint.ts +368 -0
- package/src/shex/internal/shexcLexer.ts +387 -0
- package/src/shex/internal/shexcParser.ts +1312 -0
- package/src/shex/internal/tokens.ts +41 -0
- package/src/shex/internal/xpathRegex.ts +290 -0
- package/src/sparql/Algebra.ts +914 -0
- package/src/sparql/Ast.ts +1344 -0
- package/src/sparql/Bindings.ts +272 -0
- package/src/sparql/ClientErrors.ts +156 -0
- package/src/sparql/ClientFormats.ts +70 -0
- package/src/sparql/Engine.ts +699 -0
- package/src/sparql/Errors.ts +30 -0
- package/src/sparql/Expression.ts +360 -0
- package/src/sparql/Federation.ts +187 -0
- package/src/sparql/Functions.ts +689 -0
- package/src/sparql/Generator.ts +426 -0
- package/src/sparql/GraphStore.ts +341 -0
- package/src/sparql/Optimizer.ts +230 -0
- package/src/sparql/Options.ts +191 -0
- package/src/sparql/Parser.ts +173 -0
- package/src/sparql/Protocol.ts +342 -0
- package/src/sparql/ProtocolErrors.ts +113 -0
- package/src/sparql/QueryBuilder.ts +953 -0
- package/src/sparql/Results.ts +844 -0
- package/src/sparql/ResultsStream.ts +771 -0
- package/src/sparql/ServerFormats.ts +75 -0
- package/src/sparql/ServiceDescription.ts +130 -0
- package/src/sparql/SparqlClient.ts +947 -0
- package/src/sparql/SparqlServer.ts +528 -0
- package/src/sparql/Store.ts +311 -0
- package/src/sparql/TermSchema.ts +20 -0
- package/src/sparql/Testing.ts +114 -0
- package/src/sparql/Translate.ts +407 -0
- package/src/sparql/ai/Mcp.ts +43 -0
- package/src/sparql/ai/Tools.ts +481 -0
- package/src/sparql/ai/index.ts +11 -0
- package/src/sparql/index.ts +64 -0
- package/src/sparql/internal/evaluator.ts +947 -0
- package/src/sparql/internal/hash.ts +418 -0
- package/src/sparql/internal/json.ts +12 -0
- package/src/sparql/internal/lexer.ts +488 -0
- package/src/sparql/internal/loop.ts +24 -0
- package/src/sparql/internal/parser.ts +2215 -0
- package/src/sparql/internal/prefetch.ts +46 -0
- package/src/sparql/internal/stringMap.ts +39 -0
- package/src/sparql/internal/textResults.ts +226 -0
- package/src/sparql/internal/values.ts +550 -0
- package/src/sparql/shacl/Evaluator.ts +478 -0
- package/src/sparql/shacl/Query.ts +574 -0
- package/src/sparql/shacl/index.ts +12 -0
- package/src/sparql/sql/Dialect.ts +246 -0
- package/src/sparql/sql/SqlStore.ts +1080 -0
- package/src/sparql/sql/Translation.ts +1898 -0
- package/src/sparql/sql/index.ts +17 -0
- package/src/sparql/sql/internal/encoding.ts +313 -0
- package/src/turtle/Grammar.ts +286 -0
- package/src/turtle/Manifest.ts +215 -0
- package/src/turtle/TriG.ts +158 -0
- package/src/turtle/Turtle.ts +166 -0
- package/src/turtle/index.ts +14 -0
- package/src/turtle/internal/loop.ts +23 -0
- package/src/turtle/internal/parse.ts +112 -0
- package/src/turtle/internal/parser.ts +1303 -0
- package/src/turtle/internal/serialize.ts +79 -0
- package/src/turtle/internal/streamWriter.ts +128 -0
- package/src/turtle/internal/writer.ts +898 -0
- package/src/xmp/DublinCore.ts +147 -0
- package/src/xmp/Exif.ts +109 -0
- package/src/xmp/Extract.ts +351 -0
- package/src/xmp/Metadata.ts +703 -0
- package/src/xmp/Namespaces.ts +228 -0
- package/src/xmp/Packet.ts +297 -0
- package/src/xmp/Pdf.ts +99 -0
- package/src/xmp/PdfA.ts +280 -0
- package/src/xmp/Photoshop.ts +275 -0
- package/src/xmp/Tiff.ts +196 -0
- package/src/xmp/Xmp.ts +430 -0
- package/src/xmp/XmpBasic.ts +90 -0
- package/src/xmp/XmpMM.ts +82 -0
- package/src/xmp/XmpRights.ts +83 -0
- package/src/xmp/index.ts +36 -0
- package/src/xmp/internal/accessors.ts +72 -0
- package/src/xmp/internal/xml.ts +63 -0
package/README.md
CHANGED
|
@@ -1,3 +1,2252 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @thomasfosterau/effect-rdf
|
|
2
2
|
|
|
3
|
-
|
|
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
|