diff --git a/.ai-guidance.md b/.ai-guidance.md index 9f322bf..c7f2d9a 100644 --- a/.ai-guidance.md +++ b/.ai-guidance.md @@ -14,6 +14,10 @@ Refinement of .ai-guidance, general-guidance, and personal guidance should be an ongoing process. Continually check that guidance is up-to-date, and offer suggestions for improvement whenever possible. +## Keep files short and modular + +So important! Too-long files are cause syntax errors from agent edits. + ## Ontologies are key There are several ontologies in the "ontology" folder (which is actually its own diff --git a/.gitignore b/.gitignore index 5751f56..4fd3efb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ sflow-platform test-ns .roo/mcp.json .env +logs diff --git a/deno.json b/deno.json index a07799c..dbc7475 100644 --- a/deno.json +++ b/deno.json @@ -11,7 +11,10 @@ "@hono/zod-openapi": "npm:@hono/zod-openapi@^1.0.2", "@scalar/openapi-to-markdown": "npm:@scalar/openapi-to-markdown@^0.2.23" }, + "tasks": { + "test": "deno test --allow-net --allow-env --allow-read --allow-write=./meshes,./flow-service/logs" + }, "fmt": { - "singleQuote": true + "singleQuote": false } } diff --git a/deno.lock b/deno.lock index d96f83f..4142d05 100644 --- a/deno.lock +++ b/deno.lock @@ -6,9 +6,19 @@ "jsr:@std/path@*": "1.1.0", "npm:@hono/zod-openapi@*": "0.19.10_hono@4.8.5_zod@3.24.1", "npm:@hono/zod-openapi@^1.0.2": "1.0.2_hono@4.8.5_zod@4.0.5", + "npm:@rdfjs/data-model@*": "2.1.1", + "npm:@rdfjs/types@*": "2.0.1", "npm:@scalar/hono-api-reference@*": "0.9.12_hono@4.8.5", "npm:@scalar/openapi-to-markdown@*": "0.2.23", - "npm:@sentry/deno@*": "9.40.0" + "npm:@sentry/deno@*": "9.40.0", + "npm:@types/jsonld@*": "1.5.15", + "npm:@types/node@*": "22.15.15", + "npm:abstract-level@*": "3.1.0", + "npm:jsonld@*": "8.3.3", + "npm:memory-level@*": "3.1.0", + "npm:quadstore-comunica@*": "6.2.0_asynciterator@3.9.0_quadstore@15.3.0", + "npm:quadstore@*": "15.3.0", + "npm:rdf-data-factory@*": "2.0.2" }, "jsr": { "@hono/hono@4.8.5": { @@ -53,6 +63,17 @@ "@babel/helper-validator-identifier" ] }, + "@digitalbazaar/http-client@3.4.1_ky@0.33.3": { + "integrity": "sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==", + "dependencies": [ + "ky", + "ky-universal", + "undici" + ] + }, + "@fastify/busboy@2.1.1": { + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==" + }, "@floating-ui/core@1.7.2": { "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", "dependencies": [ @@ -191,6 +212,22 @@ "@phosphor-icons/core@2.1.1": { "integrity": "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==" }, + "@rdfjs/data-model@2.1.1": { + "integrity": "sha512-6mcOI4DjIPS6MOZw23H8oAdujHCk5gippVNQ7mKwliYTvTNh+uqRM91B9OLqhoAoNcQ3t49Dx2ooIMRG9/6ooA==", + "bin": true + }, + "@rdfjs/types@1.1.2": { + "integrity": "sha512-wqpOJK1QCbmsGNtyzYnojPU8gRDPid2JO0Q0kMtb4j65xhCK880cnKAfEOwC+dX85VJcCByQx5zOwyyfCjDJsg==", + "dependencies": [ + "@types/node@22.15.15" + ] + }, + "@rdfjs/types@2.0.1": { + "integrity": "sha512-uyAzpugX7KekAXAHq26m3JlUIZJOC0uSBhpnefGV5i15bevDyyejoB7I+9MKeUrzXD8OOUI3+4FeV1wwQr5ihA==", + "dependencies": [ + "@types/node@22.15.15" + ] + }, "@scalar/code-highlight@0.1.8": { "integrity": "sha512-SH7gqBVGotf9Hw2srAKd/s854Wjf5lpR4FgAf/l3AlCYC5QXJ4hbj+fk1/3tvzeRFm+wvS6/ah4FuiIzvDPOug==", "dependencies": [ @@ -253,7 +290,7 @@ "integrity": "sha512-ho9ee29Ik+Jv17bSaQEXvGn1e2ydNEyc7Sv8A8hqPq084aIOiTBtVFISPPt+k/O6z0SIRayfL8FEZqr93GyZPg==", "dependencies": [ "@phosphor-icons/core", - "@types/node", + "@types/node@22.16.5", "chalk", "vue" ] @@ -427,6 +464,9 @@ "@types/unist" ] }, + "@types/jsonld@1.5.15": { + "integrity": "sha512-PlAFPZjL+AuGYmwlqwKEL0IMP8M8RexH0NIPGfCVWSQ041H2rR/8OlyZSD7KsCVoN8vCfWdtWDBxX8yBVP+xow==" + }, "@types/mdast@4.0.4": { "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "dependencies": [ @@ -436,12 +476,24 @@ "@types/ms@2.1.0": { "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" }, + "@types/node@22.15.15": { + "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==", + "dependencies": [ + "undici-types" + ] + }, "@types/node@22.16.5": { "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==", "dependencies": [ "undici-types" ] }, + "@types/sparqljs@3.1.12": { + "integrity": "sha512-zg/sdKKtYI0845wKPSuSgunyU1o/+7tRzMw85lHsf4p/0UbA6+65MXAyEtv1nkaqSqrq/bXm7+bqXas+Xo5dpQ==", + "dependencies": [ + "@rdfjs/types@2.0.1" + ] + }, "@types/unist@3.0.3": { "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, @@ -540,6 +592,23 @@ "vue-demi" ] }, + "abort-controller@3.0.0": { + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": [ + "event-target-shim" + ] + }, + "abstract-level@3.1.0": { + "integrity": "sha512-j2e+TsAxy7Ri+0h7dJqwasymgt0zHBWX4+nMk3XatyuqgHfdstBJ9wsMfbiGwE1O+QovRyPcVAqcViMYdyPaaw==", + "dependencies": [ + "buffer", + "is-buffer", + "level-supports", + "level-transcoder", + "maybe-combine-errors", + "module-error" + ] + }, "acorn@8.15.0": { "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "bin": true @@ -577,12 +646,25 @@ "tslib" ] }, + "asynciterator@3.9.0": { + "integrity": "sha512-bwLLTAnoE6Ap6XdjK/j8vDk2Vi9p3ojk0PFwM0SwktAG1k8pfRJF9ng+mmkaRFKdZCQQlOxcWnvOmX2NQ1HV0g==" + }, "bail@2.0.2": { "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "buffer-from@1.1.2": { "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "buffer@6.0.3": { + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dependencies": [ + "base64-js", + "ieee754" + ] + }, "camel-case@4.1.2": { "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "dependencies": [ @@ -590,6 +672,9 @@ "tslib" ] }, + "canonicalize@1.0.8": { + "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==" + }, "ccount@2.0.1": { "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" }, @@ -641,6 +726,9 @@ "dank-map@0.1.0": { "integrity": "sha512-mQoLySkWc5bQM8XKXz0rIuISX/+12rSSfPojYlTVT6KPj3LsvfLURtrv0w+QEt1gRIKwp9mxnwOcL5nsOTkk2Q==" }, + "data-uri-to-buffer@4.0.1": { + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, "debug@4.4.1": { "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dependencies": [ @@ -684,6 +772,9 @@ "estree-walker@2.0.2": { "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, + "event-target-shim@5.0.1": { + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "extend@3.0.2": { "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, @@ -693,9 +784,25 @@ "fast-uri@3.0.6": { "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==" }, + "fetch-blob@3.2.0": { + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dependencies": [ + "node-domexception", + "web-streams-polyfill" + ] + }, "flatted@3.3.3": { "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" }, + "formdata-polyfill@4.0.10": { + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": [ + "fetch-blob" + ] + }, + "functional-red-black-tree@1.0.1": { + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" + }, "get-own-enumerable-keys@1.0.0": { "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==" }, @@ -918,9 +1025,18 @@ "html-whitespace-sensitive-tag-names@3.0.1": { "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==" }, + "ieee754@1.2.1": { + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "imurmurhash@0.1.4": { + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, "is-absolute-url@4.0.1": { "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==" }, + "is-buffer@2.0.5": { + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + }, "is-obj@3.0.0": { "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==" }, @@ -930,12 +1046,24 @@ "is-regexp@3.1.0": { "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==" }, + "js-sorted-set@0.7.0": { + "integrity": "sha512-NGTSMeoLNYR2BoTLhQ6w+u7Ox4PO34omb/0OBCy4gyedWeXolMGv948Ato0/Of6tfxsAjeySDymCkAj93/xkeA==" + }, "json-schema-traverse@1.0.0": { "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "json-stringify-deterministic@1.0.12": { "integrity": "sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g==" }, + "jsonld@8.3.3": { + "integrity": "sha512-9YcilrF+dLfg9NTEof/mJLMtbdX1RJ8dbWtJgE00cMOIohb1lIyJl710vFiTaiHTl6ZYODJuBd32xFvUhmv3kg==", + "dependencies": [ + "@digitalbazaar/http-client", + "canonicalize", + "lru-cache", + "rdf-canonize" + ] + }, "jsonpointer@5.0.1": { "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==" }, @@ -945,6 +1073,27 @@ "just-curry-it@5.3.0": { "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==" }, + "ky-universal@0.11.0_ky@0.33.3": { + "integrity": "sha512-65KyweaWvk+uKKkCrfAf+xqN2/epw1IJDtlyCPxYffFCMR8u1sp2U65NtWpnozYfZxQ6IUzIlvUcw+hQ82U2Xw==", + "dependencies": [ + "abort-controller", + "ky", + "node-fetch" + ] + }, + "ky@0.33.3": { + "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==" + }, + "level-supports@6.2.0": { + "integrity": "sha512-QNxVXP0IRnBmMsJIh+sb2kwNCYcKciQZJEt+L1hPCHrKNELllXhvrlClVHXBYZVT+a7aTSM6StgNXdAldoab3w==" + }, + "level-transcoder@1.0.1": { + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", + "dependencies": [ + "buffer", + "module-error" + ] + }, "leven@4.0.0": { "integrity": "sha512-puehA3YKku3osqPlNuzGDUHq8WpwXupUg1V6NXdV38G+gr+gkBwFC8g1b/+YcIvp8gnqVIus+eJCH/eGsRmJNw==" }, @@ -965,6 +1114,12 @@ "highlight.js" ] }, + "lru-cache@6.0.0": { + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": [ + "yallist" + ] + }, "magic-string@0.30.17": { "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dependencies": [ @@ -974,6 +1129,9 @@ "markdown-table@3.0.4": { "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==" }, + "maybe-combine-errors@1.0.0": { + "integrity": "sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A==" + }, "mdast-util-find-and-replace@3.0.2": { "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "dependencies": [ @@ -1100,6 +1258,14 @@ "@types/mdast" ] }, + "memory-level@3.1.0": { + "integrity": "sha512-mTqFVi5iReKcjue/pag0OY4VNU7dlagCyjjPwWGierpk1Bpl9WjOxgXIswymPW3Q9bj3Foay+Z16mPGnKzvTkQ==", + "dependencies": [ + "abstract-level", + "functional-red-black-tree", + "module-error" + ] + }, "microdiff@1.5.0": { "integrity": "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==" }, @@ -1344,6 +1510,12 @@ "micromark-util-types" ] }, + "minimist@1.2.8": { + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "module-error@1.0.2": { + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==" + }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, @@ -1362,6 +1534,18 @@ "tslib" ] }, + "node-domexception@1.0.0": { + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": true + }, + "node-fetch@3.3.2": { + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": [ + "data-uri-to-buffer", + "fetch-blob", + "formdata-polyfill" + ] + }, "object-to-xml@2.0.0": { "integrity": "sha512-bArXy7WCF1V9R88/zF9adSZSeFQnFmmKhMqNuNLAxqrbkvzcWP8HgnaRCcVJsfvIgvpdHiYd0qzJi7LM7QFfcQ==", "dependencies": [ @@ -1416,6 +1600,25 @@ "property-information@7.1.0": { "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" }, + "quadstore-comunica@6.2.0_asynciterator@3.9.0_quadstore@15.3.0": { + "integrity": "sha512-diibvQsX9i8VHHX47a0+3W/P0oXm9LYC0fQgZ+hsEJ3wvBXrWs4AFZlSy7A+0ze1FSnHnvRtRu5/FohdtlkyAg==", + "dependencies": [ + "@rdfjs/types@2.0.1", + "asynciterator", + "quadstore", + "sparqlalgebrajs", + "sparqljs" + ] + }, + "quadstore@15.3.0": { + "integrity": "sha512-KeFNurvPg61nO/WgdFYxbO/Sj80ne1gmzMGnWI0ukIXabdLggZ7i9TERRzPoyLHiUJP+Kkc1J2lZ+wl7n8hNBA==", + "dependencies": [ + "@rdfjs/types@2.0.1", + "abstract-level", + "asynciterator", + "js-sorted-set" + ] + }, "radix-vue@1.9.17_vue@3.5.18": { "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==", "dependencies": [ @@ -1433,6 +1636,45 @@ "vue" ] }, + "rdf-canonize@3.4.0": { + "integrity": "sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==", + "dependencies": [ + "setimmediate" + ] + }, + "rdf-data-factory@1.1.3": { + "integrity": "sha512-ny6CI7m2bq4lfQQmDYvcb2l1F9KtGwz9chipX4oWu2aAtVoXjb7k3d8J1EsgAsEbMXnBipB/iuRen5H2fwRWWQ==", + "dependencies": [ + "@rdfjs/types@1.1.2" + ] + }, + "rdf-data-factory@2.0.2": { + "integrity": "sha512-WzPoYHwQYWvIP9k+7IBLY1b4nIDitzAK4mA37WumAF/Cjvu/KOtYJH9IPZnUTWNSd5K2+pq4vrcE9WZC4sRHhg==", + "dependencies": [ + "@rdfjs/types@2.0.1" + ] + }, + "rdf-isomorphic@2.0.1": { + "integrity": "sha512-8pfA3PeDs1sVrZ2R3dCR4KRM69m3pQGhzviNQq5Az/+Zch4I+fKstjGxCZ5ADAFPsm9zxHk8zMEQ/BFcqlnLKw==", + "dependencies": [ + "imurmurhash", + "rdf-string", + "rdf-terms" + ] + }, + "rdf-string@2.0.1": { + "integrity": "sha512-SMW4ponnKNrsP9kYpOLyICeM4UJmEXIeS3zri7kPK9gzLFsHD88oiza8LnokNYxd76zW4JoYWD+v4x0g8rJBjw==", + "dependencies": [ + "rdf-data-factory@2.0.2" + ] + }, + "rdf-terms@2.0.0": { + "integrity": "sha512-9O+ifVcvY4ZktOr+uXKswoOV6airAsIKeqCr+C47kFZBB8X+NyPSqDRGgI6X+je8It6z2e9jZhWwjJiEZ8Yn5Q==", + "dependencies": [ + "rdf-data-factory@2.0.2", + "rdf-string" + ] + }, "rehype-external-links@3.0.0": { "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==", "dependencies": [ @@ -1546,6 +1788,9 @@ "sanitizer@0.1.3": { "integrity": "sha512-j05vL56tR90rsYqm9ZD05v6K4HI7t4yMDEvvU0x4f+IADXM9Jx1x9mzatxOs5drJq6dGhugxDW99mcPvXVLl+Q==" }, + "setimmediate@1.0.5": { + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "source-map-js@1.2.1": { "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, @@ -1562,6 +1807,27 @@ "space-separated-tokens@2.0.2": { "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" }, + "sparqlalgebrajs@5.0.1": { + "integrity": "sha512-eb6Eiq5d/UUPatyTrtIZnT1c3aKfKJpp027cv7w7fxuzCHHYqhdNgOgLa9QbSGXl4X+uxTsTwZZhAPzw5X1odQ==", + "dependencies": [ + "@types/sparqljs", + "fast-deep-equal", + "minimist", + "rdf-data-factory@2.0.2", + "rdf-isomorphic", + "rdf-string", + "rdf-terms", + "sparqljs" + ], + "bin": true + }, + "sparqljs@3.7.3": { + "integrity": "sha512-FQfHUhfwn5PD9WH6xPU7DhFfXMgqK/XoDrYDVxz/grhw66Il0OjRg3JBgwuEvwHnQt7oSTiKWEiCZCPNaUbqgg==", + "dependencies": [ + "rdf-data-factory@1.1.3" + ], + "bin": true + }, "stringify-entities@4.0.4": { "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "dependencies": [ @@ -1611,6 +1877,12 @@ "undici-types@6.21.0": { "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, + "undici@5.29.0": { + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dependencies": [ + "@fastify/busboy" + ] + }, "unified@11.0.5": { "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "dependencies": [ @@ -1712,6 +1984,12 @@ "web-namespaces@2.0.1": { "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==" }, + "web-streams-polyfill@3.3.3": { + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" + }, + "yallist@4.0.0": { + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "yaml@2.8.0": { "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "bin": true @@ -1899,9 +2177,103 @@ "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", + "https://deno.land/std@0.224.0/fs/_create_walk_entry.ts": "5d9d2aaec05bcf09a06748b1684224d33eba7a4de24cf4cf5599991ca6b5b412", + "https://deno.land/std@0.224.0/fs/_get_file_info_type.ts": "da7bec18a7661dba360a1db475b826b18977582ce6fc9b25f3d4ee0403fe8cbd", + "https://deno.land/std@0.224.0/fs/_is_same_path.ts": "709c95868345fea051c58b9e96af95cff94e6ae98dfcff2b66dee0c212c4221f", + "https://deno.land/std@0.224.0/fs/_is_subdir.ts": "c68b309d46cc8568ed83c000f608a61bbdba0943b7524e7a30f9e450cf67eecd", + "https://deno.land/std@0.224.0/fs/_to_path_string.ts": "29bfc9c6c112254961d75cbf6ba814d6de5349767818eb93090cecfa9665591e", + "https://deno.land/std@0.224.0/fs/copy.ts": "7ab12a16adb65d155d4943c88081ca16ce3b0b5acada64c1ce93800653678039", + "https://deno.land/std@0.224.0/fs/empty_dir.ts": "e400e96e1d2c8c558a5a1712063bd43939e00619c1d1cc29959babc6f1639418", + "https://deno.land/std@0.224.0/fs/ensure_dir.ts": "51a6279016c65d2985f8803c848e2888e206d1b510686a509fa7cc34ce59d29f", + "https://deno.land/std@0.224.0/fs/ensure_file.ts": "67608cf550529f3d4aa1f8b6b36bf817bdc40b14487bf8f60e61cbf68f507cf3", + "https://deno.land/std@0.224.0/fs/ensure_link.ts": "5c98503ebfa9cc05e2f2efaa30e91e60b4dd5b43ebbda82f435c0a5c6e3ffa01", + "https://deno.land/std@0.224.0/fs/ensure_symlink.ts": "cafe904cebacb9a761977d6dbf5e3af938be946a723bb394080b9a52714fafe4", + "https://deno.land/std@0.224.0/fs/eol.ts": "18c4ac009d0318504c285879eb7f47942643f13619e0ff070a0edc59353306bd", + "https://deno.land/std@0.224.0/fs/exists.ts": "3d38cb7dcbca3cf313be343a7b8af18a87bddb4b5ca1bd2314be12d06533b50f", + "https://deno.land/std@0.224.0/fs/expand_glob.ts": "2e428d90acc6676b2aa7b5c78ef48f30641b13f1fe658e7976c9064fb4b05309", + "https://deno.land/std@0.224.0/fs/mod.ts": "c25e6802cbf27f3050f60b26b00c2d8dba1cb7fcdafe34c66006a7473b7b34d4", + "https://deno.land/std@0.224.0/fs/move.ts": "ca205d848908d7f217353bc5c623627b1333490b8b5d3ef4cab600a700c9bd8f", + "https://deno.land/std@0.224.0/fs/walk.ts": "cddf87d2705c0163bff5d7767291f05b0f46ba10b8b28f227c3849cace08d303", "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", + "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", + "https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", + "https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c", + "https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", + "https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b", + "https://deno.land/std@0.224.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf", + "https://deno.land/std@0.224.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d", + "https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3", + "https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607", + "https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a", + "https://deno.land/std@0.224.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883", + "https://deno.land/std@0.224.0/path/_interface.ts": "8dfeb930ca4a772c458a8c7bbe1e33216fe91c253411338ad80c5b6fa93ddba0", + "https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15", + "https://deno.land/std@0.224.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e", + "https://deno.land/std@0.224.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643", + "https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36", + "https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c", + "https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441", + "https://deno.land/std@0.224.0/path/format.ts": "6ce1779b0980296cf2bc20d66436b12792102b831fd281ab9eb08fa8a3e6f6ac", + "https://deno.land/std@0.224.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069", + "https://deno.land/std@0.224.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972", + "https://deno.land/std@0.224.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7", + "https://deno.land/std@0.224.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141", + "https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a", + "https://deno.land/std@0.224.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0", + "https://deno.land/std@0.224.0/path/mod.ts": "f6bd79cb08be0e604201bc9de41ac9248582699d1b2ee0ab6bc9190d472cf9cd", + "https://deno.land/std@0.224.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352", + "https://deno.land/std@0.224.0/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f", + "https://deno.land/std@0.224.0/path/parse.ts": "77ad91dcb235a66c6f504df83087ce2a5471e67d79c402014f6e847389108d5a", + "https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d", + "https://deno.land/std@0.224.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0", + "https://deno.land/std@0.224.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.224.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1", + "https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00", + "https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2", + "https://deno.land/std@0.224.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1", + "https://deno.land/std@0.224.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40", + "https://deno.land/std@0.224.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f", + "https://deno.land/std@0.224.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede", + "https://deno.land/std@0.224.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63", + "https://deno.land/std@0.224.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", + "https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91", + "https://deno.land/std@0.224.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/posix/parse.ts": "09dfad0cae530f93627202f28c1befa78ea6e751f92f478ca2cc3b56be2cbb6a", + "https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c", + "https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf", + "https://deno.land/std@0.224.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf", + "https://deno.land/std@0.224.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0", + "https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add", + "https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d", + "https://deno.land/std@0.224.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b", + "https://deno.land/std@0.224.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40", + "https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808", + "https://deno.land/std@0.224.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660", + "https://deno.land/std@0.224.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.224.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5", + "https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", + "https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef", + "https://deno.land/std@0.224.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6", + "https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01", + "https://deno.land/std@0.224.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8", + "https://deno.land/std@0.224.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a", + "https://deno.land/std@0.224.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf", + "https://deno.land/std@0.224.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", + "https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780", + "https://deno.land/std@0.224.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/windows/parse.ts": "08804327b0484d18ab4d6781742bf374976de662f8642e62a67e93346e759707", + "https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7", + "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", + "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", + "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", "https://deno.land/std@0.224.0/testing/mock.ts": "a963181c2860b6ba3eb60e08b62c164d33cf5da7cd445893499b2efda20074db", "https://deno.land/x/free_port@v1.2.0/mod.ts": "512646732aaea41fbfd1f210f3ae82660f38251777d189d290da331d0235a58e", "https://deno.land/x/opine@2.1.2/src/methods.ts": "0481daecc6068d24e9e5391818baddf555ab803d39a465dcd259161f8bd8ee49", diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..5f246cb --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,19 @@ +// eslint.config.mjs +import tsParser from "@typescript-eslint/parser"; + +export default [ + { + files: ["**/*.ts"], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + }, + }, + rules: { + "quote-props": ["error", "always"], + "quotes": ["error", "double"], + }, + }, +]; diff --git a/flow-core/src/deps.ts b/flow-core/src/deps.ts index 89051b2..195356d 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -12,3 +12,26 @@ export { } from 'https://deno.land/std@0.224.0/path/mod.ts'; export { existsSync } from 'https://deno.land/std@0.224.0/fs/mod.ts'; export { ensureDir } from 'https://deno.land/std@0.224.0/fs/ensure_dir.ts'; + +export { Quadstore } from 'npm:quadstore'; +export { DataFactory } from 'npm:rdf-data-factory'; +export { MemoryLevel } from 'npm:memory-level'; + +import jsonldDefault from "npm:jsonld"; +export const jsonld = jsonldDefault; + +export { Engine } from 'npm:quadstore-comunica'; + + + +// exported types + +export type { + JsonLdDocument, + NodeObject, + ValueObject, + ContextDefinition, +} from "npm:@types/jsonld"; + +//RDF: Quad, NamedNode, Literal, Term, Stream, DefaultGraph, etc +export type * as RDF from 'npm:@rdfjs/types'; diff --git a/flow-core/src/platform-constants.ts b/flow-core/src/platform-constants.ts new file mode 100644 index 0000000..352f452 --- /dev/null +++ b/flow-core/src/platform-constants.ts @@ -0,0 +1,19 @@ +export const validLogLevels = ['debug', 'info', 'warn', 'error'] as const; + +export type LogLevel = typeof validLogLevels[number]; + +/** + * Validates that the provided log level is one of the allowed values. + * + * @param level - The log level to validate, as unknown + * @returns The validated log level as a LogLevel type + * @throws Error if the log level is not valid + */ +export function validateLogLevel(level: unknown): LogLevel { + if (typeof level !== 'string' || !validLogLevels.includes(level as LogLevel)) { + throw new Error( + `Invalid log level: ${level}. Must be one of: ${validLogLevels.join(', ')}`, + ); + } + return level as LogLevel; +} diff --git a/flow-core/src/types.ts b/flow-core/src/types.ts new file mode 100644 index 0000000..39cf59c --- /dev/null +++ b/flow-core/src/types.ts @@ -0,0 +1,13 @@ +// Imports for QuadstoreBundle types +// usage: const bundle: QuadstoreBundle> = { ... }; +import type { Quadstore, DataFactory } from './deps.ts'; +import type { AbstractLevel } from 'npm:abstract-level'; +import { Engine } from 'npm:quadstore-comunica'; + +// Quadstore Bundle Type +export interface QuadstoreBundle = AbstractLevel> { + store: Quadstore; + df: DataFactory; + backend: B; + engine?: Engine; +} diff --git a/flow-core/src/utils/quadstore-utils.ts b/flow-core/src/utils/quadstore-utils.ts new file mode 100644 index 0000000..6385a2a --- /dev/null +++ b/flow-core/src/utils/quadstore-utils.ts @@ -0,0 +1,129 @@ +import { NodeObject, Quadstore, DataFactory } from '../deps.ts'; +import { RDF } from '../deps.ts'; +import { jsonld } from '../deps.ts'; +import { defaultQuadstoreBundle } from '../../../flow-service/src/quadstore-default-bundle.ts'; +import { jsonldToQuads } from './rdfjs-utils.ts' +import type { QuadstoreBundle } from '../types.ts'; + +export function countQuadsInStream(stream: RDF.Stream): Promise { + return new Promise((resolve, reject) => { + let count = 0; + stream.on('data', () => count++); + stream.on('end', () => resolve(count)); + stream.on('error', (err) => reject(err)); + }); +} + + +// returns whether there are any quads in the specified named graph. + +export async function isGraphEmpty( + graph: RDF.NamedNode, + { + store + } = defaultQuadstoreBundle +): Promise { + const matchStream = store.match(undefined, undefined, undefined, graph); + const count = await countQuadsInStream(matchStream); + + return count === 0; +} + + +// Clears all quads in the specified named graph. Returns the number of quads deleted. + +export async function clearGraph( + graph: RDF.NamedNode, + { + store + }: { store: Quadstore } = defaultQuadstoreBundle +): Promise { + const matchStream = store.match(undefined, undefined, undefined, graph); + const count = await countQuadsInStream(matchStream); + + if (count > 0) { + const delStream = store.match(undefined, undefined, undefined, graph); + await store.delStream(delStream as any); + } + + return count; +} + +// Copies all quads from source graph to target graph. + +export async function copyGraph( + sourceGraph: RDF.NamedNode, + targetGraph: RDF.NamedNode, + { + store, + df, + }: { store: Quadstore, df: DataFactory } = defaultQuadstoreBundle +): Promise { + const stream = store.match(undefined, undefined, undefined, sourceGraph); + const quads = []; + for await (const q of stream as any) { + const newQuad = df.quad(q.subject, q.predicate, q.object, targetGraph); + quads.push(newQuad); + } + await store.multiPut(quads); + return quads.length; +} + + +// Converts a JSON-LD object to quads and puts them into the specified graph with store.multiPut + +export async function putJsonLdToGraph( + inputJsonLd: NodeObject, + graphName?: string, + { + store, + df + } = defaultQuadstoreBundle +): Promise { + // Convert JSON-LD to RDF quads + const graph = graphName ? df.namedNode(graphName) : df.defaultGraph(); + const quads = await jsonldToQuads(inputJsonLd, graph); + + // Put quads into the store + await store.multiPut(quads); + + return quads.length; +} + +/** + * Creates a new graph from a JSON-LD object, optionally overwriting existing quads. + * If overwrite is false, it will throw an error if the target graph is not empty. + * + * @param obj - The JSON-LD object to convert to RDF quads + * @param graphName - The name of the graph to create + * @param overwrite - Whether to clear existing quads in the target graph + * @param options - Optional store and data factory for quadstore operations + * @returns numQuads - The number of quads created in the new graph + */ + +export interface CreateGraphOptions { + overwrite?: boolean; + graphName?: string; + bundle?: QuadstoreBundle; +} + +export async function createNewGraphFromJsonLd( + inputJsonLd: NodeObject, + options: CreateGraphOptions = {} +): Promise { + const { + overwrite = false, + graphName = null, + bundle = defaultQuadstoreBundle, + } = options; + + const { store, df } = bundle; + const graph = graphName ? df.namedNode(graphName) : df.defaultGraph(); + const quads = await jsonldToQuads(inputJsonLd, graph); + if (overwrite && graphName) clearGraph(df.namedNode(graphName)); + + // Put quads into the store + await store.multiPut(quads); + + return quads.length; +} diff --git a/flow-core/src/utils/quadstore/quadstore-factory.ts b/flow-core/src/utils/quadstore/quadstore-factory.ts new file mode 100644 index 0000000..b47d874 --- /dev/null +++ b/flow-core/src/utils/quadstore/quadstore-factory.ts @@ -0,0 +1,17 @@ +// quadstoreFactory.ts +import { Quadstore, MemoryLevel, DataFactory, Engine } from '../../deps.ts'; +import { QuadstoreBundle } from '../../types.ts'; + +/** + * Creates and opens a new in-memory QuadstoreBundle. + */ +export async function createQuadstoreBundle(): Promise { + const backend = new MemoryLevel(); + const df = new DataFactory(); + const store = new Quadstore({ backend, dataFactory: df }); + const engine = new Engine(store); + + await store.open(); + + return { store, df, backend, engine }; +} diff --git a/flow-core/src/utils/rdfjs-utils.ts b/flow-core/src/utils/rdfjs-utils.ts new file mode 100644 index 0000000..ef8bd21 --- /dev/null +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -0,0 +1,25 @@ +import { jsonld } from '../deps.ts'; +import { NodeObject } from '../deps.ts'; +import type { RDF } from '../deps.ts'; +import { DataFactory } from 'npm:rdf-data-factory'; + +const df = new DataFactory(); + +// Converts a JSON-LD object to an array of RDFJS quads using a local DataFactory instance +export async function jsonldToQuads( + inputJsonLd: NodeObject, + graph: RDF.NamedNode | RDF.DefaultGraph = df.defaultGraph(), +): Promise { + // Use jsonld.toRDF() to get quads directly - much simpler! + const quads = await jsonld.toRDF(inputJsonLd) as RDF.Quad[]; + + // If the desired graph is the default graph, return as-is + if (graph.termType === 'DefaultGraph') { + return quads; + } + + // If a specific named graph is requested, update all quads to use that graph + return quads.map(quad => + df.quad(quad.subject, quad.predicate, quad.object, graph) + ); +} diff --git a/flow-core/tests/unit/quadstore-utils.test.ts b/flow-core/tests/unit/quadstore-utils.test.ts new file mode 100644 index 0000000..3b8292f --- /dev/null +++ b/flow-core/tests/unit/quadstore-utils.test.ts @@ -0,0 +1,85 @@ +import { assertEquals } from '../../src/deps.ts'; +import { clearGraph, copyGraph, isGraphEmpty, countQuadsInStream, putJsonLdToGraph, createNewGraphFromJsonLd } from '../../src/utils/quadstore-utils.ts'; +import { createQuadstoreBundle } from "../../../flow-core/src/utils/quadstore/quadstore-factory.ts"; + +Deno.test('countQuadsInStream counts quads correctly', async () => { + const testQuadstoreBundle = await createQuadstoreBundle(); + const df = testQuadstoreBundle.df; + const store = testQuadstoreBundle.store; + + const graph = df.namedNode('urn:testGraph1'); + await clearGraph(graph); + + const quad1 = df.quad(df.namedNode('s1'), df.namedNode('p1'), df.literal('o1'), graph); + const quad2 = df.quad(df.namedNode('s2'), df.namedNode('p2'), df.literal('o2'), graph); + + await store.put(quad1); + await store.put(quad2); + + const stream = store.match(undefined, undefined, undefined, graph); + const count = await countQuadsInStream(stream); + assertEquals(count, 2); + + await clearGraph(graph); +}); + +Deno.test('isGraphEmpty returns true for empty graph and false otherwise', async () => { + const testQuadstoreBundle = await createQuadstoreBundle(); + const df = testQuadstoreBundle.df; + const store = testQuadstoreBundle.store; + + const graph = df.namedNode('urn:testGraph2'); + await clearGraph(graph); + + let empty = await isGraphEmpty(graph, testQuadstoreBundle); + assertEquals(empty, true); + + const quad = df.quad(df.namedNode('s'), df.namedNode('p'), df.literal('o'), graph); + await store.put(quad); + + empty = await isGraphEmpty(graph, testQuadstoreBundle); + assertEquals(empty, false); + + await clearGraph(graph); +}); + +Deno.test('clearGraph deletes all quads and returns count', async () => { + const testQuadstoreBundle = await createQuadstoreBundle(); + const df = testQuadstoreBundle.df; + const store = testQuadstoreBundle.store; + + const graph = df.namedNode('urn:testGraph3'); + await clearGraph(graph); + + const quad = df.quad(df.namedNode('s'), df.namedNode('p'), df.literal('o'), graph); + await store.put(quad); + + const count = await clearGraph(graph, testQuadstoreBundle); + assertEquals(count, 1); + + const empty = await isGraphEmpty(graph, testQuadstoreBundle); + assertEquals(empty, true); +}); + +Deno.test('copyGraph copies quads from source to target graph', async () => { + const testQuadstoreBundle = await createQuadstoreBundle(); + const df = testQuadstoreBundle.df; + const store = testQuadstoreBundle.store; + + const sourceGraph = df.namedNode('urn:sourceGraph'); + const targetGraph = df.namedNode('urn:targetGraph'); + + await clearGraph(sourceGraph, testQuadstoreBundle); + await clearGraph(targetGraph, testQuadstoreBundle); + + const quad = df.quad(df.namedNode('s'), df.namedNode('p'), df.literal('o'), sourceGraph); + await store.put(quad); + + await copyGraph(sourceGraph, targetGraph, testQuadstoreBundle); + + const countSource = await countQuadsInStream(store.match(undefined, undefined, undefined, sourceGraph)); + const countTarget = await countQuadsInStream(store.match(undefined, undefined, undefined, targetGraph)); + + assertEquals(countSource, 1); + assertEquals(countTarget, 1); +}); diff --git a/flow-service/deno.json b/flow-service/deno.json index d884e92..7103d2d 100644 --- a/flow-service/deno.json +++ b/flow-service/deno.json @@ -8,16 +8,16 @@ "@sentry/deno": "npm:@sentry/deno@^8.0.0" }, "tasks": { - "start": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes --env-file main.ts", - "dev": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes --env-file --watch main.ts", - "debug": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes --env-file --inspect-brk=0.0.0.0:9229 main.ts", - "test": "deno test --allow-net --allow-env --allow-read --allow-write=../meshes" + "start": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes,./logs --env-file main.ts", + "dev": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes,./logs --env-file --watch main.ts", + "debug": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes,./logs --env-file --inspect-brk=0.0.0.0:9229 main.ts", + "test": "deno test --allow-net --allow-env --allow-read --allow-write=../meshes,./logs" }, "compilerOptions": { "jsx": "precompile", "jsxImportSource": "hono/jsx" }, "fmt": { - "singleQuote": true + "singleQuote": false } } diff --git a/flow-service/deno.lock b/flow-service/deno.lock index 49e8a9f..176ab73 100644 --- a/flow-service/deno.lock +++ b/flow-service/deno.lock @@ -4,15 +4,26 @@ "jsr:@hono/hono@*": "4.8.4", "jsr:@hono/hono@^4.8.4": "4.8.4", "jsr:@std/path@*": "1.1.0", + "npm:@comunica/actor-init-sparql@*": "3.1.0", "npm:@hono/zod-openapi@*": "0.18.4_hono@4.8.4_zod@3.25.76", "npm:@hono/zod-openapi@0.18": "0.18.4_hono@4.8.4_zod@3.25.76", + "npm:@rdfjs/data-model@*": "2.1.1", + "npm:@rdfjs/types@*": "2.0.1", "npm:@scalar/hono-api-reference@*": "0.5.184_hono@4.8.4", "npm:@scalar/hono-api-reference@0.5": "0.5.184_hono@4.8.4", "npm:@scalar/hono-api-reference@~0.9.10": "0.9.10_hono@4.8.4", "npm:@scalar/openapi-to-markdown@*": "0.2.20", "npm:@sentry/deno@*": "8.55.0", "npm:@sentry/deno@8": "8.55.0", + "npm:@types/jsonld@*": "1.5.15", + "npm:@types/node@*": "22.15.15", + "npm:abstract-level@*": "3.1.0", "npm:hono@*": "4.8.4", + "npm:jsonld@*": "8.3.3", + "npm:memory-level@*": "3.1.0", + "npm:quadstore-comunica@*": "6.2.0_asynciterator@3.9.0_quadstore@15.3.0", + "npm:quadstore@*": "15.3.0", + "npm:rdf-data-factory@*": "2.0.2", "npm:zod@^3.22.0": "3.25.76" }, "jsr": { @@ -51,6 +62,20 @@ "@babel/helper-validator-identifier" ] }, + "@comunica/actor-init-sparql@3.1.0": { + "integrity": "sha512-DhnE9t+cy4TlVaGVeIsJoFaNM7CzaSiqk3nKOP64jhBub9/I1rgosMHjhJhLOD71a60sFLDzj3PeMLospBht0A==" + }, + "@digitalbazaar/http-client@3.4.1_ky@0.33.3": { + "integrity": "sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==", + "dependencies": [ + "ky", + "ky-universal", + "undici" + ] + }, + "@fastify/busboy@2.1.1": { + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==" + }, "@floating-ui/core@1.7.2": { "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", "dependencies": [ @@ -171,6 +196,22 @@ "@phosphor-icons/core@2.1.1": { "integrity": "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==" }, + "@rdfjs/data-model@2.1.1": { + "integrity": "sha512-6mcOI4DjIPS6MOZw23H8oAdujHCk5gippVNQ7mKwliYTvTNh+uqRM91B9OLqhoAoNcQ3t49Dx2ooIMRG9/6ooA==", + "bin": true + }, + "@rdfjs/types@1.1.2": { + "integrity": "sha512-wqpOJK1QCbmsGNtyzYnojPU8gRDPid2JO0Q0kMtb4j65xhCK880cnKAfEOwC+dX85VJcCByQx5zOwyyfCjDJsg==", + "dependencies": [ + "@types/node@22.15.15" + ] + }, + "@rdfjs/types@2.0.1": { + "integrity": "sha512-uyAzpugX7KekAXAHq26m3JlUIZJOC0uSBhpnefGV5i15bevDyyejoB7I+9MKeUrzXD8OOUI3+4FeV1wwQr5ihA==", + "dependencies": [ + "@types/node@22.15.15" + ] + }, "@scalar/code-highlight@0.1.6": { "integrity": "sha512-4j6QLEMjhqgQ3NT9PAywBi/x5gmDHy5vysEe9ogzVDQhVzpL58/ylfH85pXDqtiUizOXman+eRpP2BH/f9xF3g==", "dependencies": [ @@ -246,7 +287,7 @@ "integrity": "sha512-ho9ee29Ik+Jv17bSaQEXvGn1e2ydNEyc7Sv8A8hqPq084aIOiTBtVFISPPt+k/O6z0SIRayfL8FEZqr93GyZPg==", "dependencies": [ "@phosphor-icons/core", - "@types/node", + "@types/node@22.16.3", "chalk", "vue" ] @@ -431,6 +472,9 @@ "@types/unist" ] }, + "@types/jsonld@1.5.15": { + "integrity": "sha512-PlAFPZjL+AuGYmwlqwKEL0IMP8M8RexH0NIPGfCVWSQ041H2rR/8OlyZSD7KsCVoN8vCfWdtWDBxX8yBVP+xow==" + }, "@types/mdast@4.0.4": { "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "dependencies": [ @@ -440,12 +484,24 @@ "@types/ms@2.1.0": { "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" }, + "@types/node@22.15.15": { + "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==", + "dependencies": [ + "undici-types" + ] + }, "@types/node@22.16.3": { "integrity": "sha512-sr4Xz74KOUeYadexo1r8imhRtlVXcs+j3XK3TcoiYk7B1t3YRVJgtaD3cwX73NYb71pmVuMLNRhJ9XKdoDB74g==", "dependencies": [ "undici-types" ] }, + "@types/sparqljs@3.1.12": { + "integrity": "sha512-zg/sdKKtYI0845wKPSuSgunyU1o/+7tRzMw85lHsf4p/0UbA6+65MXAyEtv1nkaqSqrq/bXm7+bqXas+Xo5dpQ==", + "dependencies": [ + "@rdfjs/types@2.0.1" + ] + }, "@types/unist@3.0.3": { "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, @@ -551,6 +607,23 @@ "vue-demi" ] }, + "abort-controller@3.0.0": { + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": [ + "event-target-shim" + ] + }, + "abstract-level@3.1.0": { + "integrity": "sha512-j2e+TsAxy7Ri+0h7dJqwasymgt0zHBWX4+nMk3XatyuqgHfdstBJ9wsMfbiGwE1O+QovRyPcVAqcViMYdyPaaw==", + "dependencies": [ + "buffer", + "is-buffer", + "level-supports", + "level-transcoder", + "maybe-combine-errors", + "module-error" + ] + }, "acorn@8.15.0": { "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "bin": true @@ -588,12 +661,25 @@ "tslib" ] }, + "asynciterator@3.9.0": { + "integrity": "sha512-bwLLTAnoE6Ap6XdjK/j8vDk2Vi9p3ojk0PFwM0SwktAG1k8pfRJF9ng+mmkaRFKdZCQQlOxcWnvOmX2NQ1HV0g==" + }, "bail@2.0.2": { "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "buffer-from@1.1.2": { "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "buffer@6.0.3": { + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dependencies": [ + "base64-js", + "ieee754" + ] + }, "camel-case@4.1.2": { "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "dependencies": [ @@ -601,6 +687,9 @@ "tslib" ] }, + "canonicalize@1.0.8": { + "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==" + }, "ccount@2.0.1": { "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" }, @@ -652,6 +741,9 @@ "dank-map@0.1.0": { "integrity": "sha512-mQoLySkWc5bQM8XKXz0rIuISX/+12rSSfPojYlTVT6KPj3LsvfLURtrv0w+QEt1gRIKwp9mxnwOcL5nsOTkk2Q==" }, + "data-uri-to-buffer@4.0.1": { + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, "debug@4.4.1": { "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dependencies": [ @@ -695,6 +787,9 @@ "estree-walker@2.0.2": { "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, + "event-target-shim@5.0.1": { + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "extend@3.0.2": { "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, @@ -704,9 +799,25 @@ "fast-uri@3.0.6": { "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==" }, + "fetch-blob@3.2.0": { + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dependencies": [ + "node-domexception", + "web-streams-polyfill" + ] + }, "flatted@3.3.3": { "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" }, + "formdata-polyfill@4.0.10": { + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": [ + "fetch-blob" + ] + }, + "functional-red-black-tree@1.0.1": { + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" + }, "get-own-enumerable-keys@1.0.0": { "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==" }, @@ -932,9 +1043,18 @@ "html-whitespace-sensitive-tag-names@3.0.1": { "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==" }, + "ieee754@1.2.1": { + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "imurmurhash@0.1.4": { + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, "is-absolute-url@4.0.1": { "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==" }, + "is-buffer@2.0.5": { + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + }, "is-obj@3.0.0": { "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==" }, @@ -944,12 +1064,24 @@ "is-regexp@3.1.0": { "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==" }, + "js-sorted-set@0.7.0": { + "integrity": "sha512-NGTSMeoLNYR2BoTLhQ6w+u7Ox4PO34omb/0OBCy4gyedWeXolMGv948Ato0/Of6tfxsAjeySDymCkAj93/xkeA==" + }, "json-schema-traverse@1.0.0": { "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "json-stringify-deterministic@1.0.12": { "integrity": "sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g==" }, + "jsonld@8.3.3": { + "integrity": "sha512-9YcilrF+dLfg9NTEof/mJLMtbdX1RJ8dbWtJgE00cMOIohb1lIyJl710vFiTaiHTl6ZYODJuBd32xFvUhmv3kg==", + "dependencies": [ + "@digitalbazaar/http-client", + "canonicalize", + "lru-cache", + "rdf-canonize" + ] + }, "jsonpointer@5.0.1": { "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==" }, @@ -959,6 +1091,27 @@ "just-curry-it@5.3.0": { "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==" }, + "ky-universal@0.11.0_ky@0.33.3": { + "integrity": "sha512-65KyweaWvk+uKKkCrfAf+xqN2/epw1IJDtlyCPxYffFCMR8u1sp2U65NtWpnozYfZxQ6IUzIlvUcw+hQ82U2Xw==", + "dependencies": [ + "abort-controller", + "ky", + "node-fetch" + ] + }, + "ky@0.33.3": { + "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==" + }, + "level-supports@6.2.0": { + "integrity": "sha512-QNxVXP0IRnBmMsJIh+sb2kwNCYcKciQZJEt+L1hPCHrKNELllXhvrlClVHXBYZVT+a7aTSM6StgNXdAldoab3w==" + }, + "level-transcoder@1.0.1": { + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", + "dependencies": [ + "buffer", + "module-error" + ] + }, "leven@4.0.0": { "integrity": "sha512-puehA3YKku3osqPlNuzGDUHq8WpwXupUg1V6NXdV38G+gr+gkBwFC8g1b/+YcIvp8gnqVIus+eJCH/eGsRmJNw==" }, @@ -979,6 +1132,12 @@ "highlight.js" ] }, + "lru-cache@6.0.0": { + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": [ + "yallist" + ] + }, "magic-string@0.30.17": { "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dependencies": [ @@ -988,6 +1147,9 @@ "markdown-table@3.0.4": { "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==" }, + "maybe-combine-errors@1.0.0": { + "integrity": "sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A==" + }, "mdast-util-find-and-replace@3.0.2": { "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "dependencies": [ @@ -1114,6 +1276,14 @@ "@types/mdast" ] }, + "memory-level@3.1.0": { + "integrity": "sha512-mTqFVi5iReKcjue/pag0OY4VNU7dlagCyjjPwWGierpk1Bpl9WjOxgXIswymPW3Q9bj3Foay+Z16mPGnKzvTkQ==", + "dependencies": [ + "abstract-level", + "functional-red-black-tree", + "module-error" + ] + }, "microdiff@1.5.0": { "integrity": "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==" }, @@ -1358,6 +1528,12 @@ "micromark-util-types" ] }, + "minimist@1.2.8": { + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "module-error@1.0.2": { + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==" + }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, @@ -1376,6 +1552,18 @@ "tslib" ] }, + "node-domexception@1.0.0": { + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": true + }, + "node-fetch@3.3.2": { + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": [ + "data-uri-to-buffer", + "fetch-blob", + "formdata-polyfill" + ] + }, "object-to-xml@2.0.0": { "integrity": "sha512-bArXy7WCF1V9R88/zF9adSZSeFQnFmmKhMqNuNLAxqrbkvzcWP8HgnaRCcVJsfvIgvpdHiYd0qzJi7LM7QFfcQ==", "dependencies": [ @@ -1430,6 +1618,25 @@ "property-information@7.1.0": { "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" }, + "quadstore-comunica@6.2.0_asynciterator@3.9.0_quadstore@15.3.0": { + "integrity": "sha512-diibvQsX9i8VHHX47a0+3W/P0oXm9LYC0fQgZ+hsEJ3wvBXrWs4AFZlSy7A+0ze1FSnHnvRtRu5/FohdtlkyAg==", + "dependencies": [ + "@rdfjs/types@2.0.1", + "asynciterator", + "quadstore", + "sparqlalgebrajs", + "sparqljs" + ] + }, + "quadstore@15.3.0": { + "integrity": "sha512-KeFNurvPg61nO/WgdFYxbO/Sj80ne1gmzMGnWI0ukIXabdLggZ7i9TERRzPoyLHiUJP+Kkc1J2lZ+wl7n8hNBA==", + "dependencies": [ + "@rdfjs/types@2.0.1", + "abstract-level", + "asynciterator", + "js-sorted-set" + ] + }, "radix-vue@1.9.17_vue@3.5.17": { "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==", "dependencies": [ @@ -1447,6 +1654,45 @@ "vue" ] }, + "rdf-canonize@3.4.0": { + "integrity": "sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==", + "dependencies": [ + "setimmediate" + ] + }, + "rdf-data-factory@1.1.3": { + "integrity": "sha512-ny6CI7m2bq4lfQQmDYvcb2l1F9KtGwz9chipX4oWu2aAtVoXjb7k3d8J1EsgAsEbMXnBipB/iuRen5H2fwRWWQ==", + "dependencies": [ + "@rdfjs/types@1.1.2" + ] + }, + "rdf-data-factory@2.0.2": { + "integrity": "sha512-WzPoYHwQYWvIP9k+7IBLY1b4nIDitzAK4mA37WumAF/Cjvu/KOtYJH9IPZnUTWNSd5K2+pq4vrcE9WZC4sRHhg==", + "dependencies": [ + "@rdfjs/types@2.0.1" + ] + }, + "rdf-isomorphic@2.0.1": { + "integrity": "sha512-8pfA3PeDs1sVrZ2R3dCR4KRM69m3pQGhzviNQq5Az/+Zch4I+fKstjGxCZ5ADAFPsm9zxHk8zMEQ/BFcqlnLKw==", + "dependencies": [ + "imurmurhash", + "rdf-string", + "rdf-terms" + ] + }, + "rdf-string@2.0.1": { + "integrity": "sha512-SMW4ponnKNrsP9kYpOLyICeM4UJmEXIeS3zri7kPK9gzLFsHD88oiza8LnokNYxd76zW4JoYWD+v4x0g8rJBjw==", + "dependencies": [ + "rdf-data-factory@2.0.2" + ] + }, + "rdf-terms@2.0.0": { + "integrity": "sha512-9O+ifVcvY4ZktOr+uXKswoOV6airAsIKeqCr+C47kFZBB8X+NyPSqDRGgI6X+je8It6z2e9jZhWwjJiEZ8Yn5Q==", + "dependencies": [ + "rdf-data-factory@2.0.2", + "rdf-string" + ] + }, "rehype-external-links@3.0.0": { "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==", "dependencies": [ @@ -1560,6 +1806,9 @@ "sanitizer@0.1.3": { "integrity": "sha512-j05vL56tR90rsYqm9ZD05v6K4HI7t4yMDEvvU0x4f+IADXM9Jx1x9mzatxOs5drJq6dGhugxDW99mcPvXVLl+Q==" }, + "setimmediate@1.0.5": { + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, "source-map-js@1.2.1": { "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, @@ -1576,6 +1825,27 @@ "space-separated-tokens@2.0.2": { "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" }, + "sparqlalgebrajs@5.0.1": { + "integrity": "sha512-eb6Eiq5d/UUPatyTrtIZnT1c3aKfKJpp027cv7w7fxuzCHHYqhdNgOgLa9QbSGXl4X+uxTsTwZZhAPzw5X1odQ==", + "dependencies": [ + "@types/sparqljs", + "fast-deep-equal", + "minimist", + "rdf-data-factory@2.0.2", + "rdf-isomorphic", + "rdf-string", + "rdf-terms", + "sparqljs" + ], + "bin": true + }, + "sparqljs@3.7.3": { + "integrity": "sha512-FQfHUhfwn5PD9WH6xPU7DhFfXMgqK/XoDrYDVxz/grhw66Il0OjRg3JBgwuEvwHnQt7oSTiKWEiCZCPNaUbqgg==", + "dependencies": [ + "rdf-data-factory@1.1.3" + ], + "bin": true + }, "stringify-entities@4.0.4": { "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "dependencies": [ @@ -1625,6 +1895,12 @@ "undici-types@6.21.0": { "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, + "undici@5.29.0": { + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dependencies": [ + "@fastify/busboy" + ] + }, "unified@11.0.5": { "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "dependencies": [ @@ -1726,6 +2002,12 @@ "web-namespaces@2.0.1": { "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==" }, + "web-streams-polyfill@3.3.3": { + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" + }, + "yallist@4.0.0": { + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "yaml@2.8.0": { "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "bin": true @@ -1745,9 +2027,34 @@ }, "redirects": { "https://deno.land/std/path/mod.ts": "https://deno.land/std@0.224.0/path/mod.ts", + "https://deno.land/std/testing/asserts.ts": "https://deno.land/std@0.224.0/testing/asserts.ts", "https://deno.land/x/sentry/index.mjs": "https://deno.land/x/sentry@8.55.0/index.mjs" }, "remote": { + "https://deno.land/std@0.114.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", + "https://deno.land/std@0.114.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", + "https://deno.land/std@0.114.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b", + "https://deno.land/std@0.114.0/fs/copy.ts": "631bbafbfe6cba282158abc8aeb7e8251cc69a7ec28ce12878ea1b75fec2add4", + "https://deno.land/std@0.114.0/fs/empty_dir.ts": "5f08b263dd064dc7917c4bbeb13de0f5505a664b9cdfe312fa86e7518cfaeb84", + "https://deno.land/std@0.114.0/fs/ensure_dir.ts": "b7c103dc41a3d1dbbb522bf183c519c37065fdc234831a4a0f7d671b1ed5fea7", + "https://deno.land/std@0.114.0/fs/ensure_file.ts": "c06031af24368e80c330897e4b8e9109efc8602ffabc8f3e2306be07529e1d13", + "https://deno.land/std@0.114.0/fs/ensure_link.ts": "26e54363508b822afd87a3f6e873bbbcd6b5993dd638f8170758c16262a75065", + "https://deno.land/std@0.114.0/fs/ensure_symlink.ts": "c07b6d19ef58b6f5c671ffa942e7f9be50315f4f78e2f9f511626fd2e13beccc", + "https://deno.land/std@0.114.0/fs/eol.ts": "afaebaaac36f48c423b920c836551997715672b80a0fee9aa7667c181a94f2df", + "https://deno.land/std@0.114.0/fs/exists.ts": "c3c3335a212bd945bb75df379096ab57fb6c86598fa273dfb24da3b3939a951e", + "https://deno.land/std@0.114.0/fs/expand_glob.ts": "f6f64ef54addcc84c9012d6dfcf66d39ecc2565e40c4d2b7644d585fe4d12a04", + "https://deno.land/std@0.114.0/fs/mod.ts": "26eee4b52a8c516e37d464094b080ff6822883e7f01ff0ba0a72b8dcd54b9927", + "https://deno.land/std@0.114.0/fs/move.ts": "4623058e39bbbeb3ad30aeff9c974c55d2d574ad7c480295c12b04c244686a99", + "https://deno.land/std@0.114.0/fs/walk.ts": "f633829f967d2979ab285dbfb09eb0d7d000fd175b95156b63fcede435d1a807", + "https://deno.land/std@0.114.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", + "https://deno.land/std@0.114.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", + "https://deno.land/std@0.114.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b", + "https://deno.land/std@0.114.0/path/common.ts": "f41a38a0719a1e85aa11c6ba3bea5e37c15dd009d705bd8873f94c833568cbc4", + "https://deno.land/std@0.114.0/path/glob.ts": "ea87985765b977cc284b92771003b2070c440e0807c90e1eb0ff3e095911a820", + "https://deno.land/std@0.114.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12", + "https://deno.land/std@0.114.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2", + "https://deno.land/std@0.114.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", + "https://deno.land/std@0.114.0/path/win32.ts": "11549e8c6df8307a8efcfa47ad7b2a75da743eac7d4c89c9723a944661c8bd2e", "https://deno.land/std@0.129.0/async/abortable.ts": "a896ac6b0d4237bd2d2d248217cfa1f0d85ccda93cb25ebda55e33850e526be6", "https://deno.land/std@0.129.0/async/deadline.ts": "48ac998d7564969f3e6ec6b6f9bf0217ebd00239b1b2292feba61272d5dd58d0", "https://deno.land/std@0.129.0/async/debounce.ts": "564273ef242bcfcda19a439132f940db8694173abffc159ea34f07d18fc42620", @@ -1762,6 +2069,10 @@ "https://deno.land/std@0.129.0/http/server.ts": "10c3a7814666cdbabd6f9c18acdcc4593e222275a628280a4868881445fbb5bc", "https://deno.land/std@0.129.0/testing/_diff.ts": "9d849cd6877694152e01775b2d93f9d6b7aef7e24bfe3bfafc4d7a1ac8e9f392", "https://deno.land/std@0.129.0/testing/asserts.ts": "0a95d9e8076dd3e7f0eeb605a67c148078b4b11f4abcd5eef115b0361b0736a2", + "https://deno.land/std@0.177.0/fmt/colors.ts": "938c5d44d889fb82eff6c358bea8baa7e85950a16c9f6dae3ec3a7a729164471", + "https://deno.land/std@0.177.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", + "https://deno.land/std@0.177.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", + "https://deno.land/std@0.177.0/testing/asserts.ts": "984ab0bfb3faeed92ffaa3a6b06536c66811185328c5dd146257c702c41b01ab", "https://deno.land/std@0.208.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", "https://deno.land/std@0.208.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", "https://deno.land/std@0.208.0/fs/_util.ts": "fbf57dcdc9f7bc8128d60301eece608246971a7836a3bb1e78da75314f08b978", @@ -2052,6 +2363,7 @@ "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", + "https://deno.land/std@0.224.0/testing/asserts.ts": "d0cdbabadc49cc4247a50732ee0df1403fdcd0f95360294ad448ae8c240f3f5c", "https://deno.land/std@0.224.0/testing/mock.ts": "a963181c2860b6ba3eb60e08b62c164d33cf5da7cd445893499b2efda20074db", "https://deno.land/x/free_port@v1.2.0/mod.ts": "512646732aaea41fbfd1f210f3ae82660f38251777d189d290da331d0235a58e", "https://deno.land/x/hono@v4.2.8/adapter/deno/ssg.ts": "49d73dd0d351fc52a54bfc8094096aff957f5a955a3eb976844ed8d88903bc0f", diff --git a/flow-service/flow-service-config.jsonld b/flow-service/flow-service-config.jsonld index 3b60019..a81e850 100644 --- a/flow-service/flow-service-config.jsonld +++ b/flow-service/flow-service-config.jsonld @@ -7,10 +7,12 @@ "conf": "https://semantic-flow.github.io/ontology/config-flow/", "meta": "https://semantic-flow.github.io/ontology/meta-flow/", "prov": "http://www.w3.org/ns/prov#", - "dcterms": "http://purl.org/dc/terms/" + "dcterms": "http://purl.org/dc/terms/", + "mergedServiceConfig": "http://localhost/graph/mergedServiceConfig/" }, + "@id": "local:serviceConfig", "@type": "fsvc:ServiceConfig", - "@id": "fsvc:defaultConfig", + "fsvc:host": "localhost", "fsvc:port": 8080, "fsvc:meshPaths": [ "../meshes/test-ns/" diff --git a/flow-service/logs/test-pretty-direct.log b/flow-service/logs/test-pretty-direct.log new file mode 100644 index 0000000..74deccb --- /dev/null +++ b/flow-service/logs/test-pretty-direct.log @@ -0,0 +1,4 @@ +[2025-08-02T05:45:58.391Z] INFO File logging test started (op=startup, [config-resolver]) +[2025-08-02T05:45:58.393Z] WARN Configuration override detected (op=config-resolve) +[2025-08-02T05:45:58.393Z] ERROR Database connection failed (op=startup, 5000ms) +[2025-08-02T05:45:58.393Z] DEBUG Processing mesh nodes (op=scan, mesh=/data/mesh, 1250ms, 42 nodes) diff --git a/flow-service/main.ts b/flow-service/main.ts index 6f9c89a..b9767a1 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -2,7 +2,7 @@ import { OpenAPIHono } from '@hono/zod-openapi'; import { apiReference } from 'npm:@scalar/hono-api-reference'; import { health } from './src/routes/health.ts'; import { createMarkdownFromOpenApi } from 'npm:@scalar/openapi-to-markdown'; -import { createServiceConfig } from './src/config/index.ts'; +import { createServiceConfig, singletonServiceConfigAccessor } from './src/config/index.ts'; import { logStartupConfiguration, logStartupUrls, @@ -11,9 +11,8 @@ import { handleCaughtError } from './src/utils/logger.ts'; import { MESH } from '../flow-core/src/mesh-constants.ts'; // Initialize configuration system -let config; try { - config = await createServiceConfig(); + await createServiceConfig(); } catch (error) { await handleCaughtError(error, 'Failed to initialize service configuration'); console.error( @@ -24,7 +23,7 @@ try { // Log service startup with configuration info try { - logStartupConfiguration(config); + logStartupConfiguration(); } catch (error) { await handleCaughtError(error, 'Failed to log startup configuration'); console.error('โš ๏ธ Configuration logging failed, but continuing startup...'); @@ -46,7 +45,7 @@ const content = { }, servers: [ { - url: `http://${config.host}:${config.port}`, + url: `http://${await singletonServiceConfigAccessor.getHost()}:${await singletonServiceConfigAccessor.getPort()}`, description: 'Configured server', }, ], @@ -83,15 +82,15 @@ app.route('/api', health); import { createMeshesRoutes } from './src/routes/meshes.ts'; import { createWeaveRoutes } from './src/routes/weave.ts'; -const meshes = createMeshesRoutes(config); -const weave = createWeaveRoutes(config); +const meshes = createMeshesRoutes(); +const weave = createWeaveRoutes(); app.route('/api', meshes); app.route('/api', weave); // Startup logging try { - logStartupUrls(config); + logStartupUrls(); } catch (error) { await handleCaughtError(error, 'Failed to log startup URLs'); console.error('โš ๏ธ URL logging failed, but continuing startup...'); @@ -99,8 +98,8 @@ try { try { Deno.serve({ - port: config.port, - hostname: config.host, + port: await singletonServiceConfigAccessor.getPort(), + hostname: await singletonServiceConfigAccessor.getHost(), }, app.fetch); } catch (error) { await handleCaughtError(error, 'Failed to start HTTP server'); diff --git a/flow-service/src/config/types.ts b/flow-service/src/config/config-types.ts similarity index 59% rename from flow-service/src/config/types.ts rename to flow-service/src/config/config-types.ts index 0125aba..57b9f8b 100644 --- a/flow-service/src/config/types.ts +++ b/flow-service/src/config/config-types.ts @@ -5,8 +5,10 @@ * Supports sparse input configurations that are validated against complete defaults. */ +import { NodeObject, ContextDefinition } from '../../../flow-core/src/deps.ts'; + // JSON-LD Context and Type Definitions -export interface JSONLDContext { +export interface FlowServiceContext extends ContextDefinition { readonly fsvc: 'https://semantic-flow.github.io/ontology/flow-service/'; readonly mesh: 'https://semantic-flow.github.io/ontology/mesh/'; readonly node: 'https://semantic-flow.github.io/ontology/node/'; @@ -14,14 +16,8 @@ export interface JSONLDContext { readonly conf: 'https://semantic-flow.github.io/ontology/config-flow/'; } -export interface JSONLDBase { - readonly '@context': JSONLDContext; - readonly '@type': string; - readonly '@id'?: string; -} - // Logging Configuration Types -export interface LogChannelConfig { +export interface LogChannelConfig extends NodeObject { readonly '@type': 'fsvc:LogChannelConfig'; readonly 'fsvc:logChannelEnabled': boolean; readonly 'fsvc:logLevel': 'debug' | 'info' | 'warn' | 'error'; @@ -33,13 +29,13 @@ export interface LogChannelConfig { readonly 'fsvc:logMaxFiles'?: number; readonly 'fsvc:logMaxFileSize'?: number; readonly 'fsvc:logRotationInterval'?: - | 'daily' - | 'weekly' - | 'monthly' - | 'size-based'; + | 'daily' + | 'weekly' + | 'monthly' + | 'size-based'; } -export interface LoggingConfig { +export interface LoggingConfig extends NodeObject { readonly '@type': 'fsvc:LoggingConfig'; readonly 'fsvc:hasConsoleChannel': LogChannelConfig; readonly 'fsvc:hasFileChannel': LogChannelConfig; @@ -47,7 +43,7 @@ export interface LoggingConfig { } // Contained Services Configuration -export interface ContainedServicesConfig { +export interface ContainedServicesConfig extends NodeObject { readonly '@type': 'fsvc:ContainedServicesConfig'; readonly 'fsvc:apiEnabled': boolean; readonly 'fsvc:sparqlEnabled': boolean; @@ -57,13 +53,13 @@ export interface ContainedServicesConfig { } // Node Configuration Types (for service defaults) -export interface TemplateMapping { +export interface TemplateMapping extends NodeObject { readonly '@type': 'conf:TemplateMapping'; readonly 'conf:hasResourcePageTemplate': string; } - -export interface NodeConfig extends JSONLDBase { - readonly '@type': 'conf:NodeConfig'; +// TODO rename to +export interface MeshRootNodeConfig extends NodeObject { + readonly '@type': 'conf:MeshRootNodeConfig'; readonly 'conf:versioningEnabled': boolean; readonly 'conf:distributionFormats': string[]; readonly 'conf:templateMappings'?: TemplateMapping; @@ -76,26 +72,26 @@ export interface NodeConfig extends JSONLDBase { } // Complete Service Configuration -export interface ServiceConfig extends JSONLDBase { +export interface ServiceConfig extends NodeObject { readonly '@type': 'fsvc:ServiceConfig'; readonly 'fsvc:port': number; readonly 'fsvc:host': string; readonly 'fsvc:meshPaths'?: string[]; readonly 'fsvc:hasLoggingConfig': LoggingConfig; readonly 'fsvc:hasContainedServices': ContainedServicesConfig; - readonly 'fsvc:nodeDefaults': NodeConfig; + readonly 'fsvc:rootMeshRootNodeConfigTemplate'?: MeshRootNodeConfig; readonly 'fsvc:defaultDelegationChain'?: DelegationChain; readonly 'fsvc:defaultAttributedTo'?: AttributedTo; } // Attribution Configuration -export interface AttributedTo { +export interface AttributedTo extends NodeObject { readonly '@id': string; - [key: string]: unknown; + //TODO definition } // Delegation Chain Configuration -export interface DelegationStep { +export interface DelegationStep extends NodeObject { readonly '@type': 'meta:DelegationStep'; readonly 'meta:stepOrder': number; readonly 'prov:agent': { @@ -103,26 +99,26 @@ export interface DelegationStep { }; } -export interface DelegationChain { +export interface DelegationChain extends NodeObject { readonly '@type': 'meta:DelegationChain'; readonly 'meta:hasStep': DelegationStep[]; } // Partial Types for Sparse Input Configuration (mutable for construction) -export interface ServiceConfigInput extends Partial { +export interface ServiceConfigInput extends Partial { '@type'?: 'fsvc:ServiceConfig'; 'fsvc:port'?: number; 'fsvc:host'?: string; 'fsvc:meshPaths'?: string[]; 'fsvc:hasLoggingConfig'?: Partial; 'fsvc:hasContainedServices'?: Partial; - 'fsvc:nodeDefaults'?: Partial; + 'fsvc:nodeDefaults'?: Partial; 'fsvc:defaultDelegationChain'?: Partial; 'fsvc:defaultAttributedTo'?: Partial; } -export interface NodeConfigInput extends Partial { - '@type'?: 'conf:NodeConfig'; +export interface MeshRootNodeConfigInput extends Partial { + '@type'?: 'conf:MeshRootNodeConfig'; 'conf:versioningEnabled'?: boolean; 'conf:distributionFormats'?: string[]; 'conf:templateMappings'?: Partial; @@ -134,15 +130,9 @@ export interface NodeConfigInput extends Partial { 'conf:defaultAttribution'?: string; } -// Configuration Context Types (Side-by-Side Pattern) -export interface ServiceConfigContext { - readonly inputOptions: ServiceConfigInput; - readonly defaultOptions: ServiceConfig; -} - -export interface NodeConfigContext { - readonly inputOptions: NodeConfigInput; - readonly defaultOptions: NodeConfig; +export interface MeshRootNodeConfigContext { + readonly inputOptions: MeshRootNodeConfigInput; + readonly defaultOptions: MeshRootNodeConfig; } // CLI Options Type @@ -155,6 +145,7 @@ export interface ServiceOptions { readonly sentryEnabled?: boolean; } + // Environment Variable Mapping export interface EnvironmentConfig { readonly FLOW_CONFIG_PATH?: string; @@ -200,74 +191,3 @@ export class ConfigValidationError extends ConfigError { // Utility Types for Configuration Access export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; -/** - * Retrieves the service port from the input options if specified; otherwise, returns the default port. - * - * @returns The configured service port number - */ -export function getServicePort(context: ServiceConfigContext): number { - return context.inputOptions['fsvc:port'] ?? - context.defaultOptions['fsvc:port']; -} - -/** - * Retrieves the service host from the input options if provided; otherwise, returns the default host. - * - * @returns The configured service host name or address. - */ -export function getServiceHost(context: ServiceConfigContext): string { - return context.inputOptions['fsvc:host'] ?? - context.defaultOptions['fsvc:host']; -} - -/** - * Retrieves the console log level from the service configuration context, preferring input options over defaults. - * - * @returns The log level for the console channel. - */ -export function getConsoleLogLevel(context: ServiceConfigContext): LogLevel { - return context.inputOptions['fsvc:hasLoggingConfig'] - ?.['fsvc:hasConsoleChannel']?.['fsvc:logLevel'] ?? - context - .defaultOptions['fsvc:hasLoggingConfig']['fsvc:hasConsoleChannel'][ - 'fsvc:logLevel' - ]; -} - -/** - * Determines whether file logging is enabled, preferring the input options if specified, otherwise using the default options. - * - * @returns `true` if file logging is enabled; otherwise, `false` - */ -export function getFileLogEnabled(context: ServiceConfigContext): boolean { - return context.inputOptions['fsvc:hasLoggingConfig']?.['fsvc:hasFileChannel'] - ?.['fsvc:logChannelEnabled'] ?? - context - .defaultOptions['fsvc:hasLoggingConfig']['fsvc:hasFileChannel'][ - 'fsvc:logChannelEnabled' - ]; -} - -/** - * Determines whether Sentry logging is enabled, preferring the input configuration if specified. - * - * @returns `true` if Sentry logging is enabled; otherwise, `false` - */ -export function getSentryEnabled(context: ServiceConfigContext): boolean { - return context.inputOptions['fsvc:hasLoggingConfig'] - ?.['fsvc:hasSentryChannel']?.['fsvc:logChannelEnabled'] ?? - context - .defaultOptions['fsvc:hasLoggingConfig']['fsvc:hasSentryChannel'][ - 'fsvc:logChannelEnabled' - ]; -} - -/** - * Determines whether versioning is enabled for a node, preferring input options over defaults. - * - * @returns `true` if versioning is enabled; otherwise, `false` - */ -export function getVersioningEnabled(context: NodeConfigContext): boolean { - return context.inputOptions['conf:versioningEnabled'] ?? - context.defaultOptions['conf:versioningEnabled']; -} diff --git a/flow-service/src/config/defaults.ts b/flow-service/src/config/defaults.ts index cdc7873..3a3f140 100644 --- a/flow-service/src/config/defaults.ts +++ b/flow-service/src/config/defaults.ts @@ -5,38 +5,41 @@ * Based on the existing JSON-LD configuration files. */ -import type { JSONLDContext, NodeConfig, ServiceConfig } from './types.ts'; -import { mergeConfigs } from '../utils/merge-configs.ts'; +import type { FlowServiceContext, MeshRootNodeConfig, ServiceConfig } from "./config-types.ts"; +import { mergeConfigs } from "../utils/merge-configs.ts"; // Standard JSON-LD Context -export const DEFAULT_CONTEXT: JSONLDContext = { - fsvc: 'https://semantic-flow.github.io/ontology/flow-service/', - mesh: 'https://semantic-flow.github.io/ontology/mesh/', - node: 'https://semantic-flow.github.io/ontology/node/', - flow: 'https://semantic-flow.github.io/ontology/flow/', - conf: 'https://semantic-flow.github.io/ontology/config-flow/', +// TODO: make local context use the config host and port +export const DEFAULT_CONTEXT: FlowServiceContext = { + "fsvc": "https://semantic-flow.github.io/ontology/flow-service/", + "mesh": "https://semantic-flow.github.io/ontology/mesh/", + "node": "https://semantic-flow.github.io/ontology/node/", + "flow": "https://semantic-flow.github.io/ontology/flow/", + "conf": "https://semantic-flow.github.io/ontology/config-flow/", + "local": "http://localhost/graph/" }; // Platform Node Configuration Defaults -export const PLATFORM_NODE_DEFAULTS: NodeConfig = { - '@context': DEFAULT_CONTEXT, - '@type': 'conf:NodeConfig', - '@id': 'conf:defaultConfig', - 'conf:versioningEnabled': true, - 'conf:configInheritanceEnabled': true, - 'conf:distributionFormats': [ - 'application/trig', - 'application/ld+json', +// TODO: Rename to indicate it's for the root MeshNodes of new Meshes +export const PLATFORM_NODE_DEFAULTS: MeshRootNodeConfig = { + "@id": "local:meshRootNodeConfig", + "@context": DEFAULT_CONTEXT, + "@type": "conf:MeshRootNodeConfig", + "conf:versioningEnabled": true, + "conf:configInheritanceEnabled": true, + "conf:distributionFormats": [ + "application/trig", + "application/ld+json", ], - 'conf:templateMappings': { - '@type': 'conf:TemplateMapping', - 'conf:hasResourcePageTemplate': - '/_assets/templates/default-resource-page.html', + "conf:templateMappings": { + "@type": "conf:TemplateMapping", + "conf:hasResourcePageTemplate": + "/_assets/_templates/default-resource-page.html", }, - 'conf:generateUnifiedDataset': false, - 'conf:generateAggregatedDataset': false, - 'conf:generateResourcePages': true, - 'conf:stylesheetPath': '/_assets/css/default-resource-page.css', + "conf:generateUnifiedDataset": false, + "conf:generateAggregatedDataset": false, + "conf:generateResourcePages": true, + "conf:stylesheetPath": "/_assets/css/default-resource-page.css", }; /** @@ -44,131 +47,133 @@ export const PLATFORM_NODE_DEFAULTS: NodeConfig = { * * This configuration extends the platform defaults with service-specific overrides. * The main differences from PLATFORM_NODE_DEFAULTS: - * - Template path is relative to service root (no leading slash) * - Excludes resource page generation settings (handled at service level) * - Omits @id to avoid conflicts in service context */ export const SERVICE_NODE_DEFAULTS: Omit< - NodeConfig, - '@id' | 'conf:generateResourcePages' | 'conf:stylesheetPath' + MeshRootNodeConfig, + "conf:generateResourcePages" | "conf:stylesheetPath" > = { - '@context': DEFAULT_CONTEXT, - '@type': 'conf:NodeConfig', - 'conf:versioningEnabled': true, - 'conf:configInheritanceEnabled': true, - 'conf:distributionFormats': [ - 'application/trig', - 'application/ld+json', + "@id": "local:meshRootNodeConfig", + "@context": DEFAULT_CONTEXT, + "@type": "conf:MeshRootNodeConfig", + "conf:versioningEnabled": true, + "conf:configInheritanceEnabled": true, + "conf:distributionFormats": [ + "application/trig", + "application/ld+json", ], - 'conf:templateMappings': { - '@type': 'conf:TemplateMapping', - 'conf:hasResourcePageTemplate': 'templates/default-resource.html', // Service-relative path + "conf:templateMappings": { + "@type": "conf:TemplateMapping", + "conf:hasResourcePageTemplate": "/_assets/_templates/default-resource-page.html", // Service-relative path }, - 'conf:generateUnifiedDataset': false, - 'conf:generateAggregatedDataset': false, + "conf:generateUnifiedDataset": false, + "conf:generateAggregatedDataset": false, }; // Platform Service Configuration Defaults export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { - '@context': DEFAULT_CONTEXT, - '@type': 'fsvc:ServiceConfig', - '@id': 'fsvc:defaultConfig', - 'fsvc:port': 3000, - 'fsvc:host': 'localhost', - 'fsvc:hasLoggingConfig': { - '@type': 'fsvc:LoggingConfig', - 'fsvc:hasConsoleChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'info', + "@id": "local:serviceConfig", + "@context": DEFAULT_CONTEXT, + "@type": "fsvc:ServiceConfig", + "fsvc:port": 3000, + "fsvc:host": "localhost", + "fsvc:hasLoggingConfig": { + "@id": "local:ServiceConfig#loggingConfig", + "@type": "fsvc:LoggingConfig", + "fsvc:hasConsoleChannel": { + "@id": "local:ServiceConfig#consoleChannel", + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "info", }, - 'fsvc:hasFileChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': false, - 'fsvc:logLevel': 'warn', - 'fsvc:logFormat': 'pretty', - 'fsvc:logFilePath': './logs/flow-service.log', - 'fsvc:logRetentionDays': 30, - 'fsvc:logMaxFiles': 10, - 'fsvc:logMaxFileSize': 10485760, - 'fsvc:logRotationInterval': 'daily', + "fsvc:hasFileChannel": { + "@id": "local:ServiceConfig#fileChannel", + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": false, + "fsvc:logLevel": "warn", + "fsvc:logFormat": "pretty", + "fsvc:logFilePath": "./logs/flow-service.log", + "fsvc:logRetentionDays": 30, + "fsvc:logMaxFiles": 10, + "fsvc:logMaxFileSize": 10485760, + "fsvc:logRotationInterval": "daily", }, - 'fsvc:hasSentryChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': false, - 'fsvc:logLevel': 'error', - 'fsvc:sentryLoggingEnabled': true, + "fsvc:hasSentryChannel": { + "@id": "local:ServiceConfig#sentryChannel", + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": false, + "fsvc:logLevel": "error", + "fsvc:sentryLoggingEnabled": true, }, }, - 'fsvc:hasContainedServices': { - '@type': 'fsvc:ContainedServicesConfig', - 'fsvc:apiEnabled': true, - 'fsvc:sparqlEnabled': true, - 'fsvc:queryWidgetEnabled': true, - 'fsvc:staticServerEnabled': true, - 'fsvc:apiDocsEnabled': true, - }, - // Reference the service-specific node defaults to avoid duplication - 'fsvc:nodeDefaults': SERVICE_NODE_DEFAULTS, - 'fsvc:defaultAttributedTo': { - '@id': 'https://orcid.org/0000-0002-1825-0097', + "fsvc:hasContainedServices": { + "@id": "local:ServiceConfig#containedServices", + "@type": "fsvc:ContainedServicesConfig", + "fsvc:apiEnabled": true, + "fsvc:sparqlEnabled": true, + "fsvc:queryWidgetEnabled": true, + "fsvc:staticServerEnabled": true, + "fsvc:apiDocsEnabled": true, }, }; // Development Environment Overrides export const DEVELOPMENT_SERVICE_OVERRIDES: Partial = { - 'fsvc:hasLoggingConfig': { - '@type': 'fsvc:LoggingConfig', - 'fsvc:hasConsoleChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'debug', // More verbose logging in development + "@id": "local:serviceConfig", + "fsvc:hasLoggingConfig": { + "@type": "fsvc:LoggingConfig", + "fsvc:hasConsoleChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "debug", // More verbose logging in development }, - 'fsvc:hasFileChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'info', - 'fsvc:logFormat': 'pretty', - 'fsvc:logFilePath': './logs/flow-service-dev.log', - 'fsvc:logRetentionDays': 7, - 'fsvc:logMaxFiles': 5, - 'fsvc:logMaxFileSize': 5242880, - 'fsvc:logRotationInterval': 'daily', + "fsvc:hasFileChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "info", + "fsvc:logFormat": "pretty", + "fsvc:logFilePath": "./logs/flow-service-dev.log", + "fsvc:logRetentionDays": 7, + "fsvc:logMaxFiles": 5, + "fsvc:logMaxFileSize": 5242880, + "fsvc:logRotationInterval": "daily", }, - 'fsvc:hasSentryChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'warn', - 'fsvc:sentryLoggingEnabled': true, + "fsvc:hasSentryChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "warn", + "fsvc:sentryLoggingEnabled": true, }, }, }; // Production Environment Overrides export const PRODUCTION_SERVICE_OVERRIDES: Partial = { - 'fsvc:hasLoggingConfig': { - '@type': 'fsvc:LoggingConfig', - 'fsvc:hasConsoleChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'info', // Less verbose in production + "@id": "local:serviceConfig", + "fsvc:hasLoggingConfig": { + "@type": "fsvc:LoggingConfig", + "fsvc:hasConsoleChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "info", // Less verbose in production }, - 'fsvc:hasFileChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'warn', - 'fsvc:logFormat': 'pretty', - 'fsvc:logFilePath': './logs/flow-service.log', - 'fsvc:logRetentionDays': 90, - 'fsvc:logMaxFiles': 20, - 'fsvc:logMaxFileSize': 52428800, - 'fsvc:logRotationInterval': 'daily', + "fsvc:hasFileChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "warn", + "fsvc:logFormat": "pretty", + "fsvc:logFilePath": "./logs/flow-service.log", + "fsvc:logRetentionDays": 90, + "fsvc:logMaxFiles": 20, + "fsvc:logMaxFileSize": 52428800, + "fsvc:logRotationInterval": "daily", }, - 'fsvc:hasSentryChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, // Enable Sentry in production - 'fsvc:logLevel': 'error', - 'fsvc:sentryLoggingEnabled': true, + "fsvc:hasSentryChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, // Enable Sentry in production + "fsvc:logLevel": "error", + "fsvc:sentryLoggingEnabled": true, }, }, }; @@ -182,13 +187,13 @@ export const PRODUCTION_SERVICE_OVERRIDES: Partial = { * @returns The effective service configuration for the given environment */ export function getEnvironmentDefaults(environment?: string): ServiceConfig { - const env = environment?.toLowerCase() || 'development'; + const env = environment?.toLowerCase() || "development"; let envOverrides: Partial = {}; switch (env) { - case 'production': - case 'prod': + case "production": + case "prod": envOverrides = PRODUCTION_SERVICE_OVERRIDES; break; default: diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index 22ce010..ca66f44 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -5,35 +5,38 @@ * and side-by-side configuration context. */ +/** + * Named graph terms for Quadstore config graphs + */ +export const CONFIG_GRAPH_NAMES = { + platformServiceDefaults: 'http://localhost/graph/platformServiceDefaults', + platformImplicitMeshRootNodeConfig: 'http://localhost/graph/platformImplicitMeshRootNodeConfig', + inputServiceConfig: 'http://localhost/graph/inputServiceConfig', + inputMeshRootNodeConfig: 'http://localhost/graph/inputMeshRootNodeConfig', + mergedServiceConfig: 'http://localhost/graph/mergedServiceConfig', + // Per-node effective configs could be stored with dynamic graph names +}; + + // Core Types export type { ContainedServicesConfig, - JSONLDContext, + FlowServiceContext, LogChannelConfig, LoggingConfig, LogLevel, - NodeConfig, - NodeConfigContext, - NodeConfigInput, + MeshRootNodeConfig, + MeshRootNodeConfigContext, + MeshRootNodeConfigInput, ServiceConfig, - ServiceConfigContext, ServiceConfigInput, ServiceOptions, TemplateMapping, -} from './types.ts'; +} from './config-types.ts'; // Error Types -export { ConfigError, ConfigValidationError } from './types.ts'; +export { ConfigError, ConfigValidationError } from './config-types.ts'; -// Helper Functions -export { - getConsoleLogLevel, - getFileLogEnabled, - getSentryEnabled, - getServiceHost, - getServicePort, - getVersioningEnabled, -} from './types.ts'; // Default Configurations export { @@ -50,76 +53,47 @@ export { getServiceConfigPath, loadEnvConfig } from './loaders/env-loader.ts'; // JSON-LD File Loading export { - cloneConfig, configExists, getNodeHierarchy, isConfigInheritanceEnabled, - loadNodeConfig, + loadMeshRootNodeConfig, loadServiceConfig, - saveNodeConfig, + saveMeshRootNodeConfig, saveServiceConfig, - validateJSONLD, + validateJsonLd, } from './loaders/jsonld-loader.ts'; // Service Configuration Resolution (Cascading Pattern) export { - getConfigValue, - mergeConfigContext, resolveServiceConfig, - ServiceConfigAccessor, - validateServiceConfig, } from './resolution/service-config-resolver.ts'; +export { validateServiceConfig } from './resolution/service-config-validator.ts'; + +export { singletonServiceConfigAccessor } from './resolution/service-config-accessor.ts'; + // Shared Utilities export { mergeConfigs } from '../utils/merge-configs.ts'; // Import the implementations for the helper functions -import type { ServiceConfig, ServiceOptions } from './types.ts'; -import { - mergeConfigContext, - resolveServiceConfig, - ServiceConfigAccessor, - validateServiceConfig, -} from './resolution/service-config-resolver.ts'; +import type { ServiceOptions } from './config-types.ts'; +import { resolveServiceConfig } from './resolution/service-config-resolver.ts'; +import { validateServiceConfig } from './resolution/service-config-validator.ts'; import { handleCaughtError } from '../utils/logger.ts'; /** - * Resolves and validates the service configuration context, returning a `ServiceConfigAccessor` for side-by-side configuration access. + * Resolves and validates the service configuration. * * @param cliOptions - Optional command-line options to influence configuration resolution - * @returns An accessor for retrieving configuration values from the resolved context */ export async function createServiceConfig( cliOptions?: ServiceOptions, -): Promise { +): Promise { try { - const context = await resolveServiceConfig(cliOptions); - validateServiceConfig(context); - return new ServiceConfigAccessor(context); + await resolveServiceConfig(cliOptions); + await validateServiceConfig(); } catch (error) { await handleCaughtError(error, `Failed to create service configuration`); throw error; } } - -/** - * Resolves, validates, and returns a fully merged service configuration object. - * - * @param cliOptions - Optional command-line options to influence configuration resolution - * @returns The complete, validated service configuration object with all context layers merged - */ -export async function getCompleteServiceConfig( - cliOptions?: ServiceOptions, -): Promise { - try { - const context = await resolveServiceConfig(cliOptions); - validateServiceConfig(context); - return mergeConfigContext(context); - } catch (error) { - await handleCaughtError( - error, - `Failed to get complete service configuration`, - ); - throw error; - } -} diff --git a/flow-service/src/config/loaders/env-loader.ts b/flow-service/src/config/loaders/env-loader.ts index 7971bb4..8306b7a 100644 --- a/flow-service/src/config/loaders/env-loader.ts +++ b/flow-service/src/config/loaders/env-loader.ts @@ -7,10 +7,10 @@ import type { EnvironmentConfig, - LogLevel, ServiceConfigInput, -} from '../types.ts'; -import { ConfigError } from '../types.ts'; +} from '../config-types.ts'; +import { ConfigError } from '../config-types.ts'; +import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; /** * Loads environment variables prefixed with `FLOW_` and assembles them into a structured `ServiceConfigInput` object. @@ -224,7 +224,7 @@ export function loadEnvConfig(): ServiceConfigInput { } if (hasNodeDefaults) { - nodeDefaults['@type'] = 'conf:NodeConfig'; + nodeDefaults['@type'] = 'conf:MeshRootNodeConfig'; configInput['fsvc:nodeDefaults'] = nodeDefaults; } @@ -260,26 +260,6 @@ function getEnvironmentVariables(): EnvironmentConfig { }; } -/** - * Validates and normalizes a log level string from an environment variable. - * - * @param level - The log level string to validate - * @returns The normalized log level if valid - * @throws ConfigError if the log level is not one of "debug", "info", "warn", or "error" - */ -function validateLogLevel(level: string): LogLevel { - const validLevels: LogLevel[] = ['debug', 'info', 'warn', 'error']; - const normalized = level.toLowerCase().trim() as LogLevel; - - if (!validLevels.includes(normalized)) { - throw new ConfigError( - `Invalid log level in environment variable: ${level}. Must be one of: ${ - validLevels.join(', ') - }`, - ); - } - return normalized; -} /** * Parses a string into a boolean value, supporting common true/false representations. diff --git a/flow-service/src/config/loaders/jsonld-loader.ts b/flow-service/src/config/loaders/jsonld-loader.ts index c10a06b..f06e227 100644 --- a/flow-service/src/config/loaders/jsonld-loader.ts +++ b/flow-service/src/config/loaders/jsonld-loader.ts @@ -5,11 +5,11 @@ * Supports both service and node configuration formats. */ -import type { NodeConfigInput, ServiceConfigInput } from '../types.ts'; -import { ConfigError } from '../types.ts'; +import type { MeshRootNodeConfigInput, ServiceConfigInput } from '../config-types.ts'; +import { ConfigError } from '../config-types.ts'; import { handleCaughtError } from '../../utils/logger.ts'; import { getCurrentConfigDistPath } from '../../../../flow-core/src/utils/mesh-path-utils.ts'; -import { dirname, resolve } from '../../../../flow-core/src/deps.ts'; +import { dirname, NodeObject, resolve } from '../../../../flow-core/src/deps.ts'; /** * Loads a service configuration from a JSON-LD file at the specified path. @@ -29,8 +29,7 @@ export async function loadServiceConfig( // Basic validation - ensure it's a service config if (parsedConfig['@type'] !== 'fsvc:ServiceConfig') { throw new ConfigError( - `Invalid service config type: expected "fsvc:ServiceConfig", got "${ - parsedConfig['@type'] + `Invalid service config type: expected "fsvc:ServiceConfig", got "${parsedConfig['@type'] }"`, ); } @@ -82,9 +81,9 @@ export async function loadServiceConfig( * @param nodePath - The directory path of the node whose configuration should be loaded * @returns The parsed node configuration object, or `null` if the configuration file does not exist */ -export async function loadNodeConfig( +export async function loadMeshRootNodeConfig( nodePath: string, -): Promise { +): Promise { // Resolve nodePath to absolute path to avoid relative path issues const absoluteNodePath = resolve(nodePath); const configPath = getCurrentConfigDistPath(absoluteNodePath); @@ -96,13 +95,12 @@ export async function loadNodeConfig( // Basic validation - ensure it's a node config if (parsedConfig['@type'] !== 'flow:ConfigDistribution') { throw new ConfigError( - `Invalid node config type: expected "flow:ConfigDistribution", got "${ - parsedConfig['@type'] + `Invalid node config type: expected "flow:ConfigDistribution", got "${parsedConfig['@type'] }"`, ); } - return parsedConfig as NodeConfigInput; + return parsedConfig as MeshRootNodeConfigInput; } catch (error) { if (error instanceof Deno.errors.NotFound) { // Node config doesn't exist - this is OK, return null @@ -177,9 +175,9 @@ export async function saveServiceConfig( * * Ensures the target directory exists before writing. Throws a ConfigError if saving fails. */ -export async function saveNodeConfig( +export async function saveMeshRootNodeConfig( nodePath: string, - config: NodeConfigInput, + config: MeshRootNodeConfigInput, ): Promise { const configPath = getCurrentConfigDistPath(nodePath); @@ -253,11 +251,11 @@ export async function isConfigInheritanceEnabled( nodePath: string, ): Promise { try { - const nodeConfig = await loadNodeConfig(nodePath); + const meshNodeConfig = await loadMeshRootNodeConfig(nodePath); // If the node has explicit inheritance setting, use it - if (nodeConfig?.['conf:configInheritanceEnabled'] !== undefined) { - return nodeConfig['conf:configInheritanceEnabled']; + if (meshNodeConfig?.['conf:configInheritanceEnabled'] !== undefined) { + return meshNodeConfig['conf:configInheritanceEnabled']; } // Default to true if not specified @@ -273,28 +271,12 @@ export async function isConfigInheritanceEnabled( * * Throws a ConfigError if the input is not an object or if required properties are missing. */ -export function validateJSONLD(data: unknown): void { - if (!data || typeof data !== 'object') { - throw new ConfigError('Configuration must be a valid JSON object'); - } - - const obj = data as Record; - - if (!obj['@type']) { +export function validateJsonLd(inputJsonLd: NodeObject): void { + if (!inputJsonLd['@type']) { throw new ConfigError('Configuration must have an "@type" property'); } - if (!obj['@context']) { + if (!inputJsonLd['@context']) { throw new ConfigError('Configuration must have an "@context" property'); } } - -/** - * Creates a deep copy of the given configuration object using JSON serialization. - * - * @param config - The configuration object to clone - * @returns A new object that is a deep clone of the input configuration - */ -export function cloneConfig(config: T): T { - return JSON.parse(JSON.stringify(config)); -} diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts new file mode 100644 index 0000000..64627e0 --- /dev/null +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -0,0 +1,56 @@ +import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; +import type { ServiceConfigInput, MeshRootNodeConfigInput } from '../config-types.ts'; +import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; +import { clearGraph, copyGraph, createNewGraphFromJsonLd } from '../../../../flow-core/src/utils/quadstore-utils.ts'; +import { CONFIG_GRAPH_NAMES } from '../index.ts'; +/** + * Load platform defaults into Quadstore graphs + */ +export async function loadPlatformDefaults(): Promise { + await createNewGraphFromJsonLd(PLATFORM_SERVICE_DEFAULTS, { graphName: CONFIG_GRAPH_NAMES.platformServiceDefaults }); + await createNewGraphFromJsonLd(PLATFORM_NODE_DEFAULTS, { graphName: CONFIG_GRAPH_NAMES.platformImplicitMeshRootNodeConfig }); +} + +/** + * Load input service config into Quadstore graph + */ +export async function loadInputServiceConfig(inputConfig: ServiceConfigInput): Promise { + await createNewGraphFromJsonLd(inputConfig, { graphName: CONFIG_GRAPH_NAMES.inputServiceConfig }); +} + +/** + * Load input node config overrides into Quadstore graph + */ +export async function loadInputMeshRootNodeConfig(inputMeshRootNodeConfig: MeshRootNodeConfigInput): Promise { + await createNewGraphFromJsonLd(inputMeshRootNodeConfig, { graphName: CONFIG_GRAPH_NAMES.inputMeshRootNodeConfig }); +} + +/** + * Merge input and platform graphs into merged service config graph + */ +export async function mergeServiceConfigGraphs( + { + store, + df, + } = defaultQuadstoreBundle +): Promise { + // Clear merged graph + const count = await clearGraph(df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig)); + if (count > 0) { + console.info(`Deleted ${count} quads from mergedServiceConfig graph`); + } + + // Copy input service config quads + await copyGraph(df.namedNode(CONFIG_GRAPH_NAMES.inputServiceConfig), df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig)); + + // Copy platform service defaults quads if not overridden + const platformQuads = store.match(undefined, undefined, undefined, df.namedNode(CONFIG_GRAPH_NAMES.platformServiceDefaults)); + for await (const q of platformQuads as any) { + const exists = await store.countQuads(q.subject, q.predicate, undefined, df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig)); + if (exists === 0) { + await store.put(df.quad(q.subject, q.predicate, q.object, df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig))); + } + } +} + + diff --git a/flow-service/src/config/node-config.default.jsonld b/flow-service/src/config/node-config.default.jsonld index 2a2ec9f..4db5b69 100644 --- a/flow-service/src/config/node-config.default.jsonld +++ b/flow-service/src/config/node-config.default.jsonld @@ -6,7 +6,7 @@ "conf": "https://semantic-flow.github.io/ontology/config-flow/" }, "@type": "flow:ConfigDistribution", - "@id": "conf:defaultNodeConfig", + "@id": "conf:defaultMeshRootNodeConfig", "conf:versioningEnabled": true, "conf:configInheritanceEnabled": true, "conf:distributionFormats": [ diff --git a/flow-service/src/config/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts new file mode 100644 index 0000000..7b4fed0 --- /dev/null +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -0,0 +1,85 @@ +import type { QuadstoreBundle } from '../../../../flow-core/src/types.ts'; +import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; +import { DataFactory } from '../../../../flow-core/src/deps.ts'; +import { CONFIG_GRAPH_NAMES } from '../index.ts'; +import { handleCaughtError } from '../../utils/logger.ts'; + +export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { + private bundle: QuadstoreBundle; + private df: DataFactory; + + constructor(bundle: QuadstoreBundle = defaultQuadstoreBundle) { + this.bundle = bundle; + this.df = bundle.df; + } + + + private async querySingleValue(sparql: string): Promise { + if (!this.bundle.engine) { + throw new Error('SPARQL engine not initialized in Quadstore bundle'); + } + try { + const bindingsStream = await this.bundle.engine.queryBindings(sparql, { sources: [this.bundle.store] }); + for await (const binding of bindingsStream as any) { + const value = binding.get('value'); + if (value) { + return value.value; + } + } + } catch (error) { + await handleCaughtError(error, 'Failed to execute SPARQL query'); + throw new Error('Failed to execute SPARQL query'); + } + return undefined; + } + + async getPort(): Promise { + const sparql = ` + PREFIX fsvc: + SELECT ?value WHERE { + GRAPH <${CONFIG_GRAPH_NAMES.mergedServiceConfig}> { + ?s fsvc:port ?value . + } + } LIMIT 1 + `; + const result = await this.querySingleValue(sparql); + return result ? Number(result) : undefined; + } + + async getHost(): Promise { + const sparql = ` + PREFIX fsvc: + SELECT ?value WHERE { + GRAPH <${CONFIG_GRAPH_NAMES.mergedServiceConfig}> { + ?s fsvc:host ?value . + } + } LIMIT 1 + `; + return this.querySingleValue(sparql); + } + + async getMeshPaths(): Promise { + const sparql = ` + PREFIX fsvc: + SELECT ?value WHERE { + GRAPH <${CONFIG_GRAPH_NAMES.mergedServiceConfig}> { + ?s fsvc:meshPaths ?value . + } + } + `; + if (!this.bundle.engine) { + throw new Error('SPARQL engine not initialized in Quadstore bundle'); + } + const values: string[] = []; + const bindingsStream = await this.bundle.engine.queryBindings(sparql, { sources: [this.bundle.store] }); + for await (const binding of bindingsStream as any) { + const value = binding.get('value'); + if (value) { + values.push(value.value); + } + } + return values; + } + + // Additional getters for other config values can be implemented similarly +})(); diff --git a/flow-service/src/config/resolution/service-config-loader.ts b/flow-service/src/config/resolution/service-config-loader.ts new file mode 100644 index 0000000..aaf17d8 --- /dev/null +++ b/flow-service/src/config/resolution/service-config-loader.ts @@ -0,0 +1,95 @@ +import type { ServiceConfigInput, ServiceOptions } from '../config-types.ts'; +import { getServiceConfigPath, loadEnvConfig } from '../loaders/env-loader.ts'; +import { loadServiceConfig } from '../loaders/jsonld-loader.ts'; +import { mergeConfigs } from '../../utils/merge-configs.ts'; +import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; + +/** + * Loads and merges service configuration input options from CLI, environment variables, and config file. + * + * @param cliOptions - Optional CLI-provided service options + * @returns Merged service configuration input options + */ + +//TODO: dct:conformsTo + +export async function loadServiceConfigInput( + cliOptions?: ServiceOptions, +): Promise { + // 1. Start with empty input options + let inputOptions: ServiceConfigInput = {}; + + // 2. Merge environment variables (cascading pattern) + const envConfig = loadEnvConfig(); + inputOptions = mergeConfigs(inputOptions, envConfig); + + // 3. Load and merge service config file (cascading pattern) + const serviceConfigPath = getServiceConfigPath(cliOptions?.configPath); + if (serviceConfigPath) { + const fileConfig = await loadServiceConfig(serviceConfigPath); + if (fileConfig) { + inputOptions = mergeConfigs(inputOptions, fileConfig); + } + } + + // 4. Apply CLI overrides (cascading pattern) + if (cliOptions) { + const cliConfig = convertCliOptionsToConfig(cliOptions); + inputOptions = mergeConfigs(inputOptions, cliConfig); + } + + return inputOptions; +} + +/** + * Converts CLI-provided service options into the internal ServiceConfigInput format. + * + * @param cliOptions - The service options provided via the command line interface + * @returns A partial service configuration object representing the CLI options in config format + */ +function convertCliOptionsToConfig( + cliOptions: ServiceOptions, +): ServiceConfigInput { + const config: ServiceConfigInput = {}; + + if (cliOptions.port !== undefined) { + config['fsvc:port'] = cliOptions.port; + } + + if (cliOptions.host !== undefined) { + config['fsvc:host'] = cliOptions.host; + } + + if (cliOptions.meshPaths !== undefined && cliOptions.meshPaths.length > 0) { + config['fsvc:meshPaths'] = cliOptions.meshPaths; + } + + if (cliOptions.logLevel !== undefined) { + config['fsvc:hasLoggingConfig'] = { + '@type': 'fsvc:LoggingConfig', + 'fsvc:hasConsoleChannel': { + '@type': 'fsvc:LogChannelConfig', + 'fsvc:logChannelEnabled': true, + 'fsvc:logLevel': validateLogLevel(cliOptions.logLevel), + }, + }; + } + + if (cliOptions.sentryEnabled !== undefined) { + if (!config['fsvc:hasLoggingConfig']) { + config['fsvc:hasLoggingConfig'] = { + '@type': 'fsvc:LoggingConfig', + }; + } + const loggingConfig = config['fsvc:hasLoggingConfig'] as Record< + string, + unknown + >; + loggingConfig['fsvc:hasSentryChannel'] = { + '@type': 'fsvc:LogChannelConfig', + 'fsvc:logChannelEnabled': cliOptions.sentryEnabled, + }; + } + + return config; +} diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index add0cdf..012c830 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -1,72 +1,59 @@ -/** - * Service Configuration Resolver - * - * Implements cascading configuration resolution for service configs. - * Merges CLI options โ†’ Environment variables โ†’ Config file โ†’ Platform defaults - */ - import type { - AttributedTo, - DelegationChain, - LogLevel, - ServiceConfigContext, ServiceConfigInput, ServiceOptions, -} from '../types.ts'; +} from '../config-types.ts'; import { getServiceConfigPath, loadEnvConfig } from '../loaders/env-loader.ts'; import { loadServiceConfig } from '../loaders/jsonld-loader.ts'; import { getEnvironmentDefaults, - PLATFORM_SERVICE_DEFAULTS, } from '../defaults.ts'; -import { ConfigError } from '../types.ts'; +import { ConfigError } from '../config-types.ts'; import { mergeConfigs } from '../../utils/merge-configs.ts'; import { handleCaughtError } from '../../utils/logger.ts'; +import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; /** * Asynchronously resolves the service configuration by merging CLI options, environment variables, configuration files, and environment-specific defaults in a defined precedence order. * - * Returns a context object containing both the merged input options and the default options, allowing for side-by-side comparison without merging them. - * * @param cliOptions - Optional CLI-provided service options to override other configuration sources - * @returns A context object with both input and default configuration options * @throws ConfigError if configuration resolution fails or an unexpected error occurs */ + +import { loadPlatformDefaults, loadInputServiceConfig, loadInputMeshRootNodeConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.ts'; + export async function resolveServiceConfig( cliOptions?: ServiceOptions, -): Promise { +): Promise { try { - // 1. Start with empty input options - let inputOptions: ServiceConfigInput = {}; + // Load platform defaults into Quadstore graphs + await loadPlatformDefaults(); - // 2. Merge environment variables (cascading pattern) + // Load environment config const envConfig = loadEnvConfig(); - inputOptions = mergeConfigs(inputOptions, envConfig); - // 3. Load and merge service config file (cascading pattern) + // Load file config if specified const serviceConfigPath = getServiceConfigPath(cliOptions?.configPath); + let fileConfig: ServiceConfigInput | undefined; if (serviceConfigPath) { - const fileConfig = await loadServiceConfig(serviceConfigPath); - if (fileConfig) { - inputOptions = mergeConfigs(inputOptions, fileConfig); - } + const loadedConfig = await loadServiceConfig(serviceConfigPath); + fileConfig = loadedConfig === null ? undefined : loadedConfig; } - // 4. Apply CLI overrides (cascading pattern) - if (cliOptions) { - const cliConfig = convertCliOptionsToConfig(cliOptions); - inputOptions = mergeConfigs(inputOptions, cliConfig); - } + // Convert CLI options to config + const cliConfig = cliOptions ? convertCliOptionsToConfig(cliOptions) : {}; - // 5. Get environment-specific defaults - const environment = Deno.env.get('FLOW_ENV') || 'development'; - const defaultOptions = getEnvironmentDefaults(environment); + // Merge input configs: env, file, CLI + const mergedInputConfig = mergeConfigs(mergeConfigs(envConfig, fileConfig ?? {}), cliConfig); + + // Load merged input config into Quadstore graph + await loadInputServiceConfig(mergedInputConfig); + + // TODO: Load input mesh node config if applicable + // await loadInputMeshRootNodeConfig(...); + + // Merge all graphs into mergedServiceConfig graph + await mergeServiceConfigGraphs(); - // 6. Return side-by-side context (no merge) - return { - inputOptions, - defaultOptions, - }; } catch (error) { if (error instanceof ConfigError) { await handleCaughtError(error, `Service configuration resolution failed`); @@ -83,23 +70,6 @@ export async function resolveServiceConfig( } } -/** - * Validates that the provided log level is one of the allowed values. - * - * @param level - The log level to validate, as a string or LogLevel - * @returns The validated log level as a LogLevel type - * @throws ConfigError if the log level is not valid - */ -function validateLogLevel(level: string | LogLevel): LogLevel { - const validLevels: LogLevel[] = ['debug', 'info', 'warn', 'error']; - if (!validLevels.includes(level as LogLevel)) { - throw new ConfigError( - `Invalid log level: ${level}. Must be one of: ${validLevels.join(', ')}`, - ); - } - return level as LogLevel; -} - /** * Converts CLI-provided service options into the internal ServiceConfigInput format. * @@ -154,172 +124,3 @@ function convertCliOptionsToConfig( return config; } - -/** - * Retrieves a configuration value from the context, returning the value from input options if defined, or falling back to the default options. - * - * @param context - The service configuration context containing both input and default options - * @param inputKey - The key to look up in the input options - * @param defaultKey - The key to look up in the default options if the input value is undefined - * @returns The resolved configuration value - */ -export function getConfigValue( - context: ServiceConfigContext, - inputKey: keyof ServiceConfigInput, - defaultKey: keyof typeof PLATFORM_SERVICE_DEFAULTS, -): T { - const inputValue = context.inputOptions[inputKey]; - const defaultValue = context.defaultOptions[defaultKey]; - - return (inputValue !== undefined ? inputValue : defaultValue) as T; -} - -/** - * Merges the default and input options from the configuration context into a single resolved configuration object. - * - * Use this function when a fully merged configuration is required, rather than separate input and default options. - * - * @returns The complete configuration object with input options overriding defaults. - */ -export function mergeConfigContext( - context: ServiceConfigContext, -): typeof PLATFORM_SERVICE_DEFAULTS { - return mergeConfigs( - context.defaultOptions, - context.inputOptions as Partial, - ); -} - -/** - * Validates that required service configuration values are present and correctly formatted. - * - * Checks that the port is within the valid range, the host is a non-empty string, and if Sentry logging is enabled, ensures a valid Sentry DSN is configured. - * - * @throws ConfigError if any required configuration is missing or invalid. - */ -export function validateServiceConfig(context: ServiceConfigContext): void { - const port = getConfigValue(context, 'fsvc:port', 'fsvc:port'); - const host = getConfigValue(context, 'fsvc:host', 'fsvc:host'); - - if (!port || port <= 0 || port > 65535) { - throw new ConfigError( - `Invalid port number: ${port}. Must be between 1 and 65535.`, - ); - } - - if (!host || host.trim().length === 0) { - throw new ConfigError(`Invalid host: ${host}. Host cannot be empty.`); - } - - // Validate Sentry configuration if enabled - const loggingConfig = context.inputOptions['fsvc:hasLoggingConfig'] || - context.defaultOptions['fsvc:hasLoggingConfig']; - const sentryChannel = loggingConfig?.['fsvc:hasSentryChannel']; - - if (sentryChannel && sentryChannel['fsvc:logChannelEnabled']) { - const sentryDsn = sentryChannel['fsvc:sentryDsn']; - if (!sentryDsn || !sentryDsn.startsWith('https://')) { - throw new ConfigError( - 'Sentry is enabled but no valid DSN is configured. Please set FLOW_SENTRY_DSN environment variable or configure it in the service config file.', - ); - } - } -} - -/** - * Get a typed configuration accessor for common service config values - */ -export class ServiceConfigAccessor { - constructor(private context: ServiceConfigContext) {} - - get port(): number { - return getConfigValue(this.context, 'fsvc:port', 'fsvc:port'); - } - - get host(): string { - return getConfigValue(this.context, 'fsvc:host', 'fsvc:host'); - } - - get meshPaths(): string[] { - return this.context.inputOptions['fsvc:meshPaths'] || []; - } - - get consoleLogLevel(): string { - const loggingConfig = this.context.inputOptions['fsvc:hasLoggingConfig'] || - this.context.defaultOptions['fsvc:hasLoggingConfig']; - const consoleChannel = loggingConfig?.['fsvc:hasConsoleChannel']; - return consoleChannel?.['fsvc:logLevel'] || 'info'; - } - - get fileLogEnabled(): boolean { - const loggingConfig = this.context.inputOptions['fsvc:hasLoggingConfig'] || - this.context.defaultOptions['fsvc:hasLoggingConfig']; - const fileChannel = loggingConfig?.['fsvc:hasFileChannel']; - return fileChannel?.['fsvc:logChannelEnabled'] || false; - } - - get fileLogLevel(): string { - const loggingConfig = this.context.inputOptions['fsvc:hasLoggingConfig'] || - this.context.defaultOptions['fsvc:hasLoggingConfig']; - const fileChannel = loggingConfig?.['fsvc:hasFileChannel']; - return fileChannel?.['fsvc:logLevel'] || 'warn'; - } - - get sentryEnabled(): boolean { - const loggingConfig = this.context.inputOptions['fsvc:hasLoggingConfig'] || - this.context.defaultOptions['fsvc:hasLoggingConfig']; - const sentryChannel = loggingConfig?.['fsvc:hasSentryChannel']; - return sentryChannel?.['fsvc:logChannelEnabled'] || false; - } - - get sentryLogLevel(): string { - const loggingConfig = this.context.inputOptions['fsvc:hasLoggingConfig'] || - this.context.defaultOptions['fsvc:hasLoggingConfig']; - const sentryChannel = loggingConfig?.['fsvc:hasSentryChannel']; - return sentryChannel?.['fsvc:logLevel'] || 'error'; - } - - get sentryDsn(): string | undefined { - const loggingConfig = this.context.inputOptions['fsvc:hasLoggingConfig'] || - this.context.defaultOptions['fsvc:hasLoggingConfig']; - const sentryChannel = loggingConfig?.['fsvc:hasSentryChannel']; - return sentryChannel?.['fsvc:sentryDsn']; - } - - get apiEnabled(): boolean { - const containedServices = - this.context.inputOptions['fsvc:hasContainedServices'] || - this.context.defaultOptions['fsvc:hasContainedServices']; - return containedServices['fsvc:apiEnabled'] ?? true; - } - - get sparqlEnabled(): boolean { - const containedServices = - this.context.inputOptions['fsvc:hasContainedServices'] || - this.context.defaultOptions['fsvc:hasContainedServices']; - return containedServices['fsvc:sparqlEnabled'] ?? true; - } - - get queryWidgetEnabled(): boolean { - const containedServices = - this.context.inputOptions['fsvc:hasContainedServices'] || - this.context.defaultOptions['fsvc:hasContainedServices']; - return containedServices['fsvc:queryWidgetEnabled'] ?? true; - } - - get defaultDelegationChain(): DelegationChain | undefined { - return getConfigValue( - this.context, - 'fsvc:defaultDelegationChain', - 'fsvc:defaultDelegationChain', - ); - } - - get defaultAttributedTo(): AttributedTo | undefined { - return getConfigValue( - this.context, - 'fsvc:defaultAttributedTo', - 'fsvc:defaultAttributedTo', - ); - } -} diff --git a/flow-service/src/config/resolution/service-config-validator.ts b/flow-service/src/config/resolution/service-config-validator.ts new file mode 100644 index 0000000..4f2ed4d --- /dev/null +++ b/flow-service/src/config/resolution/service-config-validator.ts @@ -0,0 +1,36 @@ + +import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; +import { DataFactory } from '../../../../flow-core/src/deps.ts'; +import { ConfigError } from '../config-types.ts'; +import { CONFIG_GRAPH_NAMES } from '../index.ts'; + +/** + * Validates essential service configuration values in the merged config graph. + * Throws ConfigError if validation fails. + */ +export async function validateServiceConfig(): Promise { + const { store, df } = defaultQuadstoreBundle; + const graph = df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig); + + // Validate port + const portQuads = store.match(undefined, df.namedNode('https://semantic-flow.github.io/ontology/flow-service/port'), undefined, graph); + for await (const quad of portQuads as any) { + const port = Number(quad.object.value); + if (isNaN(port) || port < 1 || port > 65535) { + throw new ConfigError(`Invalid port number: ${quad.object.value}. Must be between 1 and 65535.`); + } + } + + // Validate host presence + const hostQuads = store.match(undefined, df.namedNode('https://semantic-flow.github.io/ontology/flow-service/host'), undefined, graph); + let hostFound = false; + for await (const _ of hostQuads as any) { + hostFound = true; + break; + } + if (!hostFound) { + throw new ConfigError('Host is missing in service configuration.'); + } + + // Additional validations can be added here as needed +} diff --git a/flow-service/src/quadstore-default-bundle.ts b/flow-service/src/quadstore-default-bundle.ts new file mode 100644 index 0000000..293fa95 --- /dev/null +++ b/flow-service/src/quadstore-default-bundle.ts @@ -0,0 +1,11 @@ +import { Quadstore, MemoryLevel, DataFactory, Engine } from '../../flow-core/src/deps.ts'; +import { QuadstoreBundle } from '../../flow-core/src/types.ts'; + +const backend = new MemoryLevel(); +const df = new DataFactory(); +const store = new Quadstore({ backend, dataFactory: df }); +const engine = new Engine(store); + +await store.open(); + +export const defaultQuadstoreBundle: QuadstoreBundle = { store, df, backend, engine }; diff --git a/flow-service/src/routes/meshes.ts b/flow-service/src/routes/meshes.ts index 51c5337..c9ddbc1 100644 --- a/flow-service/src/routes/meshes.ts +++ b/flow-service/src/routes/meshes.ts @@ -10,18 +10,13 @@ import { import { normalizeFolderPath } from '../../../flow-core/src/utils/path-utils.ts'; import { MESH } from '../../../flow-core/src/mesh-constants.ts'; import { dirname, existsSync, join } from '../../../flow-core/src/deps.ts'; -import { ServiceConfigAccessor } from '../config/index.ts'; import { composeMetadataContent } from '../services/metadata-composer.ts'; import { initializeMeshRegistry } from '../utils/mesh-utils.ts'; //import { Context } from '@hono/hono'; -export const createMeshesRoutes = ( - config: ServiceConfigAccessor, -): OpenAPIHono => { +export const createMeshesRoutes = (): OpenAPIHono => { const meshes = new OpenAPIHono(); - const meshRegistry: Record = {}; - initializeMeshRegistry(config, meshRegistry); // Schemas for Mesh Registration (POST /api/meshes) const MeshRegistrationRequest = z.object({ @@ -136,9 +131,8 @@ export const createMeshesRoutes = ( if (meshRegistry[name]) { return c.json({ error: 'Bad Request', - message: `Mesh '${name}' is already registered at path: ${ - meshRegistry[name] - }`, + message: `Mesh '${name}' is already registered at path: ${meshRegistry[name] + }`, }, 400); } @@ -335,7 +329,6 @@ export const createMeshesRoutes = ( slug, nodeType, initialData, - config, startTime, ); diff --git a/flow-service/src/routes/weave.ts b/flow-service/src/routes/weave.ts index 64badc5..e709f4c 100644 --- a/flow-service/src/routes/weave.ts +++ b/flow-service/src/routes/weave.ts @@ -1,7 +1,6 @@ import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi'; import { logger } from '../utils/logger.ts'; import { MESH } from '../../../flow-core/src/mesh-constants.ts'; -import { ServiceConfigAccessor } from '../config/index.ts'; // Utility function to validate node specifier using QName rules (simplified regex for example) const isValidNodeSpecifier = (specifier: string): boolean => { @@ -18,7 +17,6 @@ const parseNodeSpecifier = (specifier: string): string[] => { }; export const createWeaveRoutes = ( - config: ServiceConfigAccessor, ): OpenAPIHono => { const weave = new OpenAPIHono(); @@ -92,8 +90,7 @@ export const createWeaveRoutes = ( const nodeSegments = parseNodeSpecifier(nodeSpecifier); logger.info( - `Starting weave process for node specifier: ${nodeSpecifier} (segments: ${ - nodeSegments.join(', ') + `Starting weave process for node specifier: ${nodeSpecifier} (segments: ${nodeSegments.join(', ') })`, ); diff --git a/flow-service/src/services/metadata-composer.ts b/flow-service/src/services/metadata-composer.ts index 431f662..3bb219c 100644 --- a/flow-service/src/services/metadata-composer.ts +++ b/flow-service/src/services/metadata-composer.ts @@ -1,4 +1,5 @@ -import { ServiceConfigAccessor } from '../config/index.ts'; +import { singletonServiceConfigAccessor as config } from '../config/resolution/service-config-accessor.ts'; + //import { MESH } from '../../../flow-core/src/mesh-constants.ts'; /** @@ -7,14 +8,12 @@ import { ServiceConfigAccessor } from '../config/index.ts'; * @param slug - The slug identifier for the node * @param nodeType - The type of the node (e.g., Namespace, Reference, Dataset) * @param initialData - An object containing the initial metadata for the node - * @param config - The service configuration accessor * @returns The metadata content JSON object */ export function composeMetadataContent( slug: string, nodeType: string, initialData: Record, - config: ServiceConfigAccessor, startedAtTime?: string, ): Record { const title = typeof initialData.title === 'string' diff --git a/flow-service/src/utils/logger.ts b/flow-service/src/utils/logger.ts index 1cd62a5..0b40741 100644 --- a/flow-service/src/utils/logger.ts +++ b/flow-service/src/utils/logger.ts @@ -218,7 +218,7 @@ function formatStructuredMessage( return JSON.stringify(baseEntry); } -function formatConsoleMessage( +export function formatConsoleMessage( level: LogLevel, message: string, context?: LogContext, diff --git a/flow-service/src/utils/mesh-utils.ts b/flow-service/src/utils/mesh-utils.ts index 39da494..35ea7f4 100644 --- a/flow-service/src/utils/mesh-utils.ts +++ b/flow-service/src/utils/mesh-utils.ts @@ -1,12 +1,11 @@ -import { ServiceConfigAccessor } from '../config/index.ts'; import { logger } from './logger.ts'; import { basename, dirname, existsSync } from '../../../flow-core/src/deps.ts'; +import { singletonServiceConfigAccessor as config } from '../config/resolution/service-config-accessor.ts'; export const initializeMeshRegistry = ( - config: ServiceConfigAccessor, meshRegistry: Record, ) => { - const meshPaths = config.meshPaths; + const meshPaths = config.getMeshPaths; if (meshPaths && Array.isArray(meshPaths)) { for (const meshPath of meshPaths) { const meshName = basename(meshPath); @@ -27,8 +26,7 @@ export const initializeMeshRegistry = ( if (meshRegistry[meshName]) { logger.warn( - `Mesh name collision detected: '${meshName}' already exists at path '${ - meshRegistry[meshName] + `Mesh name collision detected: '${meshName}' already exists at path '${meshRegistry[meshName] }'. Skipping '${meshPath}'.`, ); continue; diff --git a/flow-service/src/utils/startup-logger.ts b/flow-service/src/utils/startup-logger.ts index 73c43fe..7c2232e 100644 --- a/flow-service/src/utils/startup-logger.ts +++ b/flow-service/src/utils/startup-logger.ts @@ -4,7 +4,7 @@ * Provides formatted logging for service startup configuration display */ -import type { ServiceConfigAccessor } from '../config/resolution/service-config-resolver.ts'; +import { singletonServiceConfigAccessor as config } from '../config/index.ts'; import { MESH } from '../../../flow-core/src/mesh-constants.ts'; import { resolve } from '../../../flow-core/src/deps.ts'; @@ -13,7 +13,7 @@ import { resolve } from '../../../flow-core/src/deps.ts'; * * Outputs mesh paths, logging levels, and feature enablement statuses to the console for the provided configuration. */ -export function logStartupConfiguration(config: ServiceConfigAccessor): void { +export async function logStartupConfiguration(): Promise { const now = new Date(); const timestamp = now.toLocaleDateString('en-US', { year: 'numeric', @@ -29,8 +29,9 @@ export function logStartupConfiguration(config: ServiceConfigAccessor): void { `๐Ÿ”ง Flow Service initializing at ${timestamp} with configuration:`, ); - if (config.meshPaths.length > 0) { - for (const meshPath of config.meshPaths) { + const meshPaths = await config.getMeshPaths(); + if (meshPaths.length > 0) { + for (const meshPath of meshPaths) { const absolutePath = resolve(Deno.cwd(), meshPath); console.log(` Configured mesh path: ${absolutePath}`); } @@ -38,37 +39,31 @@ export function logStartupConfiguration(config: ServiceConfigAccessor): void { console.log(` Mesh Paths: none configured`); } - console.log(` Console Logging: ${config.consoleLogLevel}`); - console.log( - ` File Logging: ${ - config.fileLogEnabled ? config.fileLogLevel : 'disabled' - }`, - ); - console.log( - ` Sentry Logging: ${ - config.sentryEnabled ? config.sentryLogLevel : 'disabled' - }`, - ); + // TODO: Add methods to accessor for these properties or query directly + console.log(` Console Logging: info`); + console.log(` File Logging: disabled`); + console.log(` Sentry Logging: disabled`); + const enabledServices = []; - if (config.apiEnabled) enabledServices.push('API'); - if (config.sparqlEnabled) enabledServices.push('SPARQL Endpoint'); - if (config.queryWidgetEnabled) enabledServices.push('SPARQL GUI'); + // TODO: Query service enablement flags from config + // if (await config.apiEnabled()) enabledServices.push('API'); + // if (await config.sparqlEnabled()) enabledServices.push('SPARQL Endpoint'); + // if (await config.queryWidgetEnabled()) enabledServices.push('SPARQL GUI'); console.log( - ` Services: ${ - enabledServices.length > 0 ? enabledServices.join(', ') : 'none' - }`, + ` Services: ${enabledServices.length > 0 ? enabledServices.join(', ') : 'none'}`, ); } /** * Logs the root service URL and API documentation URL based on the provided configuration. * - * @param config - The configuration accessor containing host and port information */ -export function logStartupUrls(config: ServiceConfigAccessor): void { - const baseUrl = `http://${config.host}:${config.port}`; +export async function logStartupUrls(): Promise { + const host = await config.getHost(); + const port = await config.getPort(); + const baseUrl = `http://${host}:${port}`; console.log(`๐Ÿ“ Root: ${baseUrl}/`); - console.log(`๐Ÿ“š API documentation: ${baseUrl}${MESH.API_PORTAL_ROUTE}`); + console.log(`๏ฟฝ API documentation: ${baseUrl}${MESH.API_PORTAL_ROUTE}`); } diff --git a/flow-service/tests/integration/test-formatting-direct.ts b/flow-service/tests/integration/logging/formatting-direct.test.ts similarity index 74% rename from flow-service/tests/integration/test-formatting-direct.ts rename to flow-service/tests/integration/logging/formatting-direct.test.ts index 28ff04d..e3e956c 100644 --- a/flow-service/tests/integration/test-formatting-direct.ts +++ b/flow-service/tests/integration/logging/formatting-direct.test.ts @@ -7,19 +7,19 @@ // Import the formatting functions directly from the logger module // We'll need to extract them for testing -import { dirname, ensureDir } from '../../../flow-core/src/deps.ts'; +import { dirname, ensureDir } from '../../../../flow-core/src/deps.ts'; type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL'; interface LogContext { operation?: - | 'scan' - | 'weave' - | 'validate' - | 'config-resolve' - | 'api-request' - | 'startup' - | 'error-handling'; + | 'scan' + | 'weave' + | 'validate' + | 'config-resolve' + | 'api-request' + | 'startup' + | 'error-handling'; requestId?: string; meshPath?: string; nodePath?: string; @@ -37,41 +37,20 @@ interface LogContext { statusCode?: number; userAgent?: string; component?: - | 'mesh-scanner' - | 'api-handler' - | 'config-resolver' - | 'weave-processor'; + | 'mesh-scanner' + | 'api-handler' + | 'config-resolver' + | 'weave-processor'; [key: string]: unknown; } -// Copy the pretty formatting function -function formatPrettyMessage( - level: LogLevel, - message: string, - context?: LogContext, -): string { - const timestamp = new Date().toISOString(); - const levelPadded = level.padEnd(5); - - let contextStr = ''; - if (context && Object.keys(context).length > 0) { - const parts = []; - if (context.operation) parts.push(`op=${context.operation}`); - if (context.meshPath) parts.push(`mesh=${context.meshPath}`); - if (context.duration) parts.push(`${context.duration}ms`); - if (context.nodeCount) parts.push(`${context.nodeCount} nodes`); - if (context.configSource) parts.push(`source=${context.configSource}`); - if (context.component) parts.push(`[${context.component}]`); - - contextStr = parts.length > 0 ? ` (${parts.join(', ')})` : ''; - } +// Import the pretty formatting function from the logger module +import { formatConsoleMessage } from '../../../src/utils/logger.ts'; - return `[${timestamp}] ${levelPadded} ${message}${contextStr}`; -} // Simple file logger class SimpleFileLogger { - constructor(private logFile: string) {} + constructor(private logFile: string) { } async ensureLogDirectory(): Promise { try { @@ -143,7 +122,7 @@ const testCases = [ console.log('๐Ÿ“ Writing pretty formatted logs...'); for (const testCase of testCases) { - const formatted = formatPrettyMessage( + const formatted = formatConsoleMessage( testCase.level, testCase.message, testCase.context, diff --git a/flow-service/tests/integration/test-handle-caught-error.ts b/flow-service/tests/integration/logging/handle-caught-error.test.ts similarity index 93% rename from flow-service/tests/integration/test-handle-caught-error.ts rename to flow-service/tests/integration/logging/handle-caught-error.test.ts index 2b48848..54fbf80 100644 --- a/flow-service/tests/integration/test-handle-caught-error.ts +++ b/flow-service/tests/integration/logging/handle-caught-error.test.ts @@ -5,13 +5,13 @@ * This demonstrates the comprehensive error handling capabilities */ -import { handleCaughtError } from '../../src/utils/logger.ts'; +import { handleCaughtError } from '../../../src/utils/logger.ts'; import { ConfigurationError, FlowServiceError, ValidationError, -} from '../../src/utils/errors.ts'; -import { ConfigValidationError } from '../../src/config/types.ts'; +} from '../../../src/utils/errors.ts'; +import { ConfigValidationError } from '../../../src/config/config-types.ts'; console.log('๐Ÿงช Testing handleCaughtError function...\n'); diff --git a/flow-service/tests/integration/logging/log-retention-config.test.ts b/flow-service/tests/integration/logging/log-retention-config.test.ts new file mode 100644 index 0000000..770073d --- /dev/null +++ b/flow-service/tests/integration/logging/log-retention-config.test.ts @@ -0,0 +1,5 @@ +import { assertEquals, assert } from "https://deno.land/std@0.224.0/testing/asserts.ts"; +import { singletonServiceConfigAccessor as config } from "../../../src/config/resolution/service-config-accessor.ts"; +import { loadEnvConfig } from "../../../src/config/loaders/env-loader.ts"; + +// TODO: log retention/rotation tests diff --git a/flow-service/tests/integration/test-logger.ts b/flow-service/tests/integration/logging/logger.test.ts similarity index 96% rename from flow-service/tests/integration/test-logger.ts rename to flow-service/tests/integration/logging/logger.test.ts index 09c6476..99ac743 100644 --- a/flow-service/tests/integration/test-logger.ts +++ b/flow-service/tests/integration/logging/logger.test.ts @@ -4,7 +4,7 @@ * Simple test script to verify file logging functionality */ -import { logger } from '../../src/utils/logger.ts'; +import { logger } from '../../../src/utils/logger.ts'; async function testFileLogging() { console.log('๐Ÿงช Testing file logging functionality...\n'); diff --git a/flow-service/tests/integration/test-pretty-format-simple.ts b/flow-service/tests/integration/logging/pretty-format-simple.test.ts similarity index 96% rename from flow-service/tests/integration/test-pretty-format-simple.ts rename to flow-service/tests/integration/logging/pretty-format-simple.test.ts index f9824fa..5834260 100644 --- a/flow-service/tests/integration/test-pretty-format-simple.ts +++ b/flow-service/tests/integration/logging/pretty-format-simple.test.ts @@ -8,7 +8,7 @@ Deno.env.set('FLOW_SENTRY_ENABLED', 'false'); Deno.env.set('FLOW_ENV', 'development'); -import { logger } from '../../src/utils/logger.ts'; +import { logger } from '../../../src/utils/logger.ts'; console.log('๐Ÿงช Testing pretty file logging format (Sentry disabled)...\n'); diff --git a/flow-service/tests/integration/logging/sentry-init.test.ts b/flow-service/tests/integration/logging/sentry-init.test.ts new file mode 100644 index 0000000..da74115 --- /dev/null +++ b/flow-service/tests/integration/logging/sentry-init.test.ts @@ -0,0 +1,7 @@ +/** + * TODO: Test script to verify Sentry initialization and logging + */ + +import { initSentry, logger } from '../../../src/utils/logger.ts'; +import { singletonServiceConfigAccessor as config } from '../../../src/config/resolution/service-config-accessor.ts'; + diff --git a/flow-service/tests/integration/logging/sentry-logging.test.ts b/flow-service/tests/integration/logging/sentry-logging.test.ts new file mode 100644 index 0000000..a08aece --- /dev/null +++ b/flow-service/tests/integration/logging/sentry-logging.test.ts @@ -0,0 +1,6 @@ +/** + * TODO: Test script to verify Sentry logging integration with log retention configuration + */ + +import { logger } from '../../../src/utils/logger.ts'; +import { singletonServiceConfigAccessor as config } from '../../../src/config/resolution/service-config-accessor.ts'; diff --git a/flow-service/tests/integration/quadstore-accessor.test.ts b/flow-service/tests/integration/quadstore-accessor.test.ts new file mode 100644 index 0000000..6b89123 --- /dev/null +++ b/flow-service/tests/integration/quadstore-accessor.test.ts @@ -0,0 +1,44 @@ +import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts"; +import { createQuadstoreBundle } from "../../../flow-core/src/utils/quadstore/quadstore-factory.ts"; +import { copyGraph, clearGraph, putJsonLdToGraph } from "../../../flow-core/src/utils/quadstore-utils.ts"; + +Deno.test("Quadstore SPARQL query integration test", async () => { + const testQuadstoreBundle = await createQuadstoreBundle(); + const df = testQuadstoreBundle.df; + const store = testQuadstoreBundle.store; + const engine = testQuadstoreBundle.engine; + + const testGraph = df.namedNode("http://example.org/testGraph"); + + // Clear graph if exists + await clearGraph(testGraph, { store }); + + // Insert test data + const testData = { + "@context": { + ex: "http://example.org/", + }, + "@id": "ex:subject1", + "ex:predicate1": "object1", + }; + await putJsonLdToGraph(testData, testGraph.value, testQuadstoreBundle); + + // Query the graph using SPARQL via Comunica engine + const query = ` + PREFIX ex: + SELECT ?o WHERE { + GRAPH <${testGraph.value}> { + ex:subject1 ex:predicate1 ?o . + } + } + `; + + const result = []; + const bindingsStream = await engine!.queryBindings(query, { sources: [store] }); + for await (const binding of bindingsStream as any) { + result.push(binding.get("o").value); + } + + assertEquals(result.length, 1); + assertEquals(result[0], "object1"); +}); diff --git a/flow-service/tests/integration/quadstore-utils.test.ts b/flow-service/tests/integration/quadstore-utils.test.ts new file mode 100644 index 0000000..a5c3cc1 --- /dev/null +++ b/flow-service/tests/integration/quadstore-utils.test.ts @@ -0,0 +1,55 @@ +import { assertEquals } from '../../../flow-core/src/deps.ts'; +import { MemoryLevel, DataFactory, Quadstore } from '../../../flow-core/src/deps.ts'; +import { RDF } from '../../../flow-core/src/deps.ts'; +import { countQuadsInStream, clearGraph, copyGraph } from '../../../flow-core/src/utils/quadstore-utils.ts'; + +Deno.test('Quadstore clearGraph and copyGraph utilities', async () => { + const backend = new MemoryLevel(); + const df = new DataFactory(); + const store = new Quadstore({ backend, dataFactory: df }); + await store.open(); + + const testQuadstoreBundle = { store, df, backend }; + + const graphA: RDF.NamedNode = df.namedNode('urn:graphA'); + const graphB: RDF.NamedNode = df.namedNode('urn:graphB'); + + // Clear both graphs before test + await clearGraph(graphA, testQuadstoreBundle); + await clearGraph(graphB, testQuadstoreBundle); + + // Add quads to graphA + const subject = df.namedNode('http://example.com/subject'); + const predicate = df.namedNode('http://example.com/predicate'); + const object = df.namedNode('http://example.com/object'); + + await store.put(df.quad(subject, predicate, object, graphA)); + + // Verify graphA has 1 quad + let count = await countQuadsInGraph(graphA, store); + assertEquals(count, 1); + + // Copy graphA to graphB + await copyGraph(graphA, graphB, testQuadstoreBundle); + + // Verify graphB has 1 quad + count = await countQuadsInGraph(graphB, store); + assertEquals(count, 1); + + // Clear graphA + const deletedCount = await clearGraph(graphA, testQuadstoreBundle); + assertEquals(deletedCount, 1); + + // Verify graphA is empty + count = await countQuadsInGraph(graphA, store); + assertEquals(count, 0); + + // Verify graphB still has 1 quad + count = await countQuadsInGraph(graphB, store); + assertEquals(count, 1); +}); + +function countQuadsInGraph(graph: RDF.NamedNode, store: Quadstore): Promise { + const stream = store.match(undefined, undefined, undefined, graph); + return countQuadsInStream(stream); +} diff --git a/flow-service/tests/integration/test-log-retention-config.ts b/flow-service/tests/integration/test-log-retention-config.ts deleted file mode 100644 index bf51eaa..0000000 --- a/flow-service/tests/integration/test-log-retention-config.ts +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env -S deno run --allow-all - -/** - * Test script to verify log retention configuration integration - */ - -import { getCompleteServiceConfig } from '../../src/config/index.ts'; -import { loadEnvConfig } from '../../src/config/loaders/env-loader.ts'; - -async function testLogRetentionConfig() { - console.log('๐Ÿงช Testing log retention configuration...\n'); - - try { - // Disable Sentry for testing to avoid DSN requirement - Deno.env.set('FLOW_SENTRY_ENABLED', 'false'); - - // Test 1: Load complete service config with defaults - console.log('๐Ÿ“‹ Test 1: Loading complete service configuration...'); - const config = await getCompleteServiceConfig(); - - const fileChannel = config['fsvc:hasLoggingConfig']['fsvc:hasFileChannel']; - console.log('โœ… File channel configuration loaded:'); - console.log(' - Enabled:', fileChannel['fsvc:logChannelEnabled']); - console.log(' - Level:', fileChannel['fsvc:logLevel']); - console.log(' - Path:', fileChannel['fsvc:logFilePath']); - console.log(' - Retention Days:', fileChannel['fsvc:logRetentionDays']); - console.log(' - Max Files:', fileChannel['fsvc:logMaxFiles']); - console.log(' - Max File Size:', fileChannel['fsvc:logMaxFileSize']); - console.log( - ' - Rotation Interval:', - fileChannel['fsvc:logRotationInterval'], - ); - - // Test 2: Test environment variable loading - console.log('\n๐Ÿ“‹ Test 2: Testing environment variable loading...'); - - // Set some test environment variables - Deno.env.set('FLOW_LOG_RETENTION_DAYS', '15'); - Deno.env.set('FLOW_LOG_MAX_FILES', '5'); - Deno.env.set('FLOW_LOG_MAX_FILE_SIZE', '5242880'); // 5MB - Deno.env.set('FLOW_LOG_ROTATION_INTERVAL', 'weekly'); - Deno.env.set('FLOW_FILE_LOG_ENABLED', 'true'); - Deno.env.set('FLOW_FILE_LOG_LEVEL', 'debug'); - - const envConfig = loadEnvConfig(); - console.log('โœ… Environment configuration loaded:'); - console.log(' - Environment config keys:', Object.keys(envConfig)); - - if (envConfig['fsvc:hasLoggingConfig']) { - const envFileChannel = - envConfig['fsvc:hasLoggingConfig']['fsvc:hasFileChannel']; - if (envFileChannel && typeof envFileChannel === 'object') { - console.log(' - File channel from env:'); - console.log(' - Enabled:', envFileChannel['fsvc:logChannelEnabled']); - console.log(' - Level:', envFileChannel['fsvc:logLevel']); - console.log( - ' - Retention Days:', - envFileChannel['fsvc:logRetentionDays'], - ); - console.log(' - Max Files:', envFileChannel['fsvc:logMaxFiles']); - console.log( - ' - Max File Size:', - envFileChannel['fsvc:logMaxFileSize'], - ); - console.log( - ' - Rotation Interval:', - envFileChannel['fsvc:logRotationInterval'], - ); - } - } - - // Test 3: Test merged configuration - console.log( - '\n๐Ÿ“‹ Test 3: Testing merged configuration with environment overrides...', - ); - const mergedConfig = await getCompleteServiceConfig(); - const mergedFileChannel = - mergedConfig['fsvc:hasLoggingConfig']['fsvc:hasFileChannel']; - - console.log('โœ… Merged configuration:'); - console.log( - ' - Retention Days:', - mergedFileChannel['fsvc:logRetentionDays'], - ); - console.log(' - Max Files:', mergedFileChannel['fsvc:logMaxFiles']); - console.log(' - Max File Size:', mergedFileChannel['fsvc:logMaxFileSize']); - console.log( - ' - Rotation Interval:', - mergedFileChannel['fsvc:logRotationInterval'], - ); - - // Test 4: Test validation constraints - console.log('\n๐Ÿ“‹ Test 4: Testing validation constraints...'); - - // Test invalid retention days - Deno.env.set('FLOW_LOG_RETENTION_DAYS', '500'); // Should be rejected (>365) - const invalidConfig = loadEnvConfig(); - const invalidFileChannel = invalidConfig['fsvc:hasLoggingConfig'] - ?.['fsvc:hasFileChannel']; - - if (invalidFileChannel && typeof invalidFileChannel === 'object') { - const hasRetentionDays = 'fsvc:logRetentionDays' in invalidFileChannel; - console.log( - 'โœ… Invalid retention days (500) rejected:', - !hasRetentionDays, - ); - } - - // Test invalid rotation interval - Deno.env.set('FLOW_LOG_ROTATION_INTERVAL', 'invalid'); - const invalidConfig2 = loadEnvConfig(); - const invalidFileChannel2 = invalidConfig2['fsvc:hasLoggingConfig'] - ?.['fsvc:hasFileChannel']; - - if (invalidFileChannel2 && typeof invalidFileChannel2 === 'object') { - const hasRotationInterval = 'fsvc:logRotationInterval' in - invalidFileChannel2; - console.log( - 'โœ… Invalid rotation interval rejected:', - !hasRotationInterval, - ); - } - - // Clean up environment variables - Deno.env.delete('FLOW_LOG_RETENTION_DAYS'); - Deno.env.delete('FLOW_LOG_MAX_FILES'); - Deno.env.delete('FLOW_LOG_MAX_FILE_SIZE'); - Deno.env.delete('FLOW_LOG_ROTATION_INTERVAL'); - Deno.env.delete('FLOW_FILE_LOG_ENABLED'); - Deno.env.delete('FLOW_FILE_LOG_LEVEL'); - - console.log('\nโœ… All log retention configuration tests passed!'); - } catch (error) { - console.error('โŒ Configuration test failed:', error); - throw error; - } -} - -if (import.meta.main) { - await testLogRetentionConfig(); -} diff --git a/flow-service/tests/integration/test-sentry-init.ts b/flow-service/tests/integration/test-sentry-init.ts deleted file mode 100644 index 685fec7..0000000 --- a/flow-service/tests/integration/test-sentry-init.ts +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env -S deno run --allow-all - -/** - * Test script to verify Sentry initialization and logging - */ - -import { initSentry, logger } from '../../src/utils/logger.ts'; -import { getCompleteServiceConfig } from '../../src/config/index.ts'; - -async function testSentryInit() { - console.log('๐Ÿงช Testing Sentry initialization and logging...\n'); - - try { - // Set environment variables for Sentry - Deno.env.set('FLOW_SENTRY_ENABLED', 'true'); - - // Get DSN from environment (should be loaded from .env file) - const sentryDsn = Deno.env.get('FLOW_SENTRY_DSN'); - if (!sentryDsn) { - throw new Error( - 'FLOW_SENTRY_DSN not found in environment. Please check your .env file.', - ); - } - - // Ensure the DSN is set in the environment for this test - Deno.env.set('FLOW_SENTRY_DSN', sentryDsn); - - console.log('๐Ÿ“‹ Step 1: Loading service configuration...'); - const config = await getCompleteServiceConfig(); - - const sentryChannel = - config['fsvc:hasLoggingConfig']['fsvc:hasSentryChannel']; - console.log('โœ… Sentry channel configuration:'); - console.log(' - Enabled:', sentryChannel['fsvc:logChannelEnabled']); - console.log(' - Level:', sentryChannel['fsvc:logLevel']); - console.log( - ' - DSN:', - sentryChannel['fsvc:sentryDsn'] ? 'configured' : 'not configured', - ); - - console.log('\n๐Ÿ“‹ Step 2: Explicitly initializing Sentry...'); - initSentry(sentryChannel['fsvc:sentryDsn'] as string); - - // Wait a moment for Sentry to initialize - await new Promise((resolve) => setTimeout(resolve, 1000)); - - console.log('\n๐Ÿ“‹ Step 3: Testing log levels with explicit Sentry init...'); - - console.log('โš ๏ธ Sending WARN message to Sentry...'); - await logger.warn('Sentry test warning with log retention config', { - operation: 'config-resolve', - component: 'config-resolver', - testId: 'sentry-init-warn-test', - logRetentionDays: 30, - logMaxFiles: 10, - logRotationInterval: 'daily', - }); - - console.log('โŒ Sending ERROR message to Sentry...'); - await logger.error( - 'Sentry test error with log retention config', - new Error('Test error for Sentry with log retention'), - { - operation: 'config-resolve', - component: 'config-resolver', - testId: 'sentry-init-error-test', - logRetentionDays: 30, - logMaxFiles: 10, - logRotationInterval: 'daily', - }, - ); - - // Force a flush of any pending Sentry events - console.log('\n๐Ÿ“‹ Step 4: Waiting for Sentry events to be sent...'); - await new Promise((resolve) => setTimeout(resolve, 3000)); - - console.log('\nโœ… Sentry initialization test completed!'); - console.log('๐Ÿ“Š Check your Sentry dashboard for the test messages'); - console.log('๐Ÿ” Expected in Sentry: 1 WARN message and 1 ERROR message'); - } catch (error) { - console.error('โŒ Sentry initialization test failed:', error); - throw error; - } -} - -if (import.meta.main) { - await testSentryInit(); -} diff --git a/flow-service/tests/integration/test-sentry-logging.ts b/flow-service/tests/integration/test-sentry-logging.ts deleted file mode 100644 index b86e11a..0000000 --- a/flow-service/tests/integration/test-sentry-logging.ts +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env -S deno run --allow-all - -/** - * Test script to verify Sentry logging integration with log retention configuration - */ - -import { logger } from '../../src/utils/logger.ts'; -import { getCompleteServiceConfig } from '../../src/config/index.ts'; - -async function testSentryLogging() { - console.log('๐Ÿงช Testing Sentry logging integration...\n'); - - try { - // Set environment variables for Sentry - Deno.env.set('FLOW_SENTRY_ENABLED', 'true'); - - // Get DSN from environment (should be loaded from .env file) - const sentryDsn = Deno.env.get('FLOW_SENTRY_DSN'); - if (!sentryDsn) { - throw new Error( - 'FLOW_SENTRY_DSN not found in environment. Please check your .env file.', - ); - } - - // Ensure the DSN is set in the environment for this test - Deno.env.set('FLOW_SENTRY_DSN', sentryDsn); - - // Test 1: Load service configuration to verify Sentry is enabled - console.log('๐Ÿ“‹ Test 1: Loading service configuration...'); - const config = await getCompleteServiceConfig(); - - const sentryChannel = - config['fsvc:hasLoggingConfig']['fsvc:hasSentryChannel']; - console.log('โœ… Sentry channel configuration:'); - console.log(' - Enabled:', sentryChannel['fsvc:logChannelEnabled']); - console.log(' - Level:', sentryChannel['fsvc:logLevel']); - console.log( - ' - DSN:', - sentryChannel['fsvc:sentryDsn'] ? 'configured' : 'not configured', - ); - - // Test 2: Test logging at various levels - console.log('\n๐Ÿ“‹ Test 2: Testing log levels...'); - - console.log( - '๐Ÿ” Sending INFO message (should NOT go to Sentry - below warn level)', - ); - await logger.info('Test info message for log retention config', { - operation: 'config-resolve', - component: 'config-resolver', - testId: 'sentry-info-test', - }); - - console.log( - 'โš ๏ธ Sending WARN message (should go to Sentry - at warn level)', - ); - await logger.warn('Test warning message for log retention config', { - operation: 'config-resolve', - component: 'config-resolver', - testId: 'sentry-warn-test', - logRetentionDays: 30, - logMaxFiles: 10, - logRotationInterval: 'daily', - }); - - console.log( - 'โŒ Sending ERROR message (should go to Sentry - above warn level)', - ); - await logger.error( - 'Test error message for log retention config', - new Error('Test error for log retention system'), - { - operation: 'config-resolve', - component: 'config-resolver', - testId: 'sentry-error-test', - logRetentionDays: 30, - logMaxFiles: 10, - logRotationInterval: 'daily', - }, - ); - - // Test 3: Test with contextual logger - console.log('\n๐Ÿ“‹ Test 3: Testing contextual logger...'); - - const contextualLogger = logger.withContext({ - component: 'config-resolver', - operation: 'startup', - logRetentionDays: 15, - logMaxFiles: 5, - }); - - await contextualLogger.warn('Log retention configuration test warning', { - testId: 'contextual-warn-test', - logRotationInterval: 'weekly', - }); - - await contextualLogger.error( - 'Log retention configuration test error', - new Error('Test contextual error'), - { - testId: 'contextual-error-test', - logMaxFileSize: 10485760, - }, - ); - - console.log('\nโœ… Sentry logging tests completed!'); - console.log('๐Ÿ“Š Check your Sentry dashboard for the test messages'); - console.log('๐Ÿ” Expected messages in Sentry:'); - console.log(' - 1 WARN message (test warning)'); - console.log(' - 2 ERROR messages (test error + contextual error)'); - console.log(' - 1 contextual WARN message'); - console.log(' - INFO message should NOT appear in Sentry'); - } catch (error) { - console.error('โŒ Sentry logging test failed:', error); - throw error; - } -} - -if (import.meta.main) { - await testSentryLogging(); -} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7da4cc9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1514 @@ +{ + "name": "sflow-platform", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@typescript-eslint/parser": "^8.38.0", + "eslint": "^9.32.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.38.0", + "@typescript-eslint/types": "^8.38.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.38.0", + "@typescript-eslint/tsconfig-utils": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.38.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..493f0d3 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "@typescript-eslint/parser": "^8.38.0", + "eslint": "^9.32.0" + } +}