diff --git a/.ai-guidance.md b/.ai-guidance.md index 9f322bf..2947480 100644 --- a/.ai-guidance.md +++ b/.ai-guidance.md @@ -14,6 +14,14 @@ 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. + +## Don't guess about file paths + +With imports, exports, and other file-related constructions, confirm the path by executing a command to ensure that the path is correct. + ## 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..8703db5 100644 --- a/deno.lock +++ b/deno.lock @@ -1,1874 +1,6 @@ { "version": "5", - "specifiers": { - "jsr:@hono/hono@*": "4.8.5", - "jsr:@hono/hono@^4.8.5": "4.8.5", - "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:@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" - }, - "jsr": { - "@hono/hono@4.8.5": { - "integrity": "78f72e532f378e379915a7e1ae7bd8a171b02324bd37b70877fd35375e8c2d6b" - }, - "@std/path@1.1.0": { - "integrity": "ddc94f8e3c275627281cbc23341df6b8bcc874d70374f75fec2533521e3d6886" - } - }, - "npm": { - "@asteasolutions/zod-to-openapi@7.3.4_zod@3.24.1": { - "integrity": "sha512-/2rThQ5zPi9OzVwes6U7lK1+Yvug0iXu25olp7S0XsYmOqnyMfxH7gdSQjn/+DSOHRg7wnotwGJSyL+fBKdnEA==", - "dependencies": [ - "openapi3-ts", - "zod@3.24.1" - ] - }, - "@asteasolutions/zod-to-openapi@8.0.0_zod@4.0.5": { - "integrity": "sha512-C56hBPiraeSWUNLz8mB5Z0/0LdfaFD5d6WB/+hdUg0MiC7egTgvWRGh3M3jZ3CRl03l/NJWnmv5D3OUAz+JGeg==", - "dependencies": [ - "openapi3-ts", - "zod@4.0.5" - ] - }, - "@babel/helper-string-parser@7.27.1": { - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" - }, - "@babel/helper-validator-identifier@7.27.1": { - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==" - }, - "@babel/parser@7.28.0": { - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", - "dependencies": [ - "@babel/types" - ], - "bin": true - }, - "@babel/types@7.28.1": { - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", - "dependencies": [ - "@babel/helper-string-parser", - "@babel/helper-validator-identifier" - ] - }, - "@floating-ui/core@1.7.2": { - "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", - "dependencies": [ - "@floating-ui/utils" - ] - }, - "@floating-ui/dom@1.7.2": { - "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", - "dependencies": [ - "@floating-ui/core", - "@floating-ui/utils" - ] - }, - "@floating-ui/utils@0.2.10": { - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==" - }, - "@floating-ui/vue@1.1.7_vue@3.5.18": { - "integrity": "sha512-idmAtbAIigGXN2SI5gItiXYBYtNfDTP9yIiObxgu13dgtG7ARCHlNfnR29GxP4LI4o13oiwsJ8wVgghj1lNqcw==", - "dependencies": [ - "@floating-ui/dom", - "@floating-ui/utils", - "vue-demi" - ] - }, - "@headlessui/vue@1.7.23_vue@3.5.18": { - "integrity": "sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==", - "dependencies": [ - "@tanstack/vue-virtual", - "vue" - ] - }, - "@hono/zod-openapi@0.19.10_hono@4.8.5_zod@3.24.1": { - "integrity": "sha512-dpoS6DenvoJyvxtQ7Kd633FRZ/Qf74+4+o9s+zZI8pEqnbjdF/DtxIib08WDpCaWabMEJOL5TXpMgNEZvb7hpA==", - "dependencies": [ - "@asteasolutions/zod-to-openapi@7.3.4_zod@3.24.1", - "@hono/zod-validator@0.7.2_hono@4.8.5_zod@3.24.1", - "hono", - "openapi3-ts", - "zod@3.24.1" - ] - }, - "@hono/zod-openapi@1.0.2_hono@4.8.5_zod@4.0.5": { - "integrity": "sha512-zbxUZEnA+NaeotXRI2YPL5GEbz38DiO7Zp1nx/7yXOA2ITkcASYsYe/My/I38c44GCu+oUVM899zn4TBVn7JRg==", - "dependencies": [ - "@asteasolutions/zod-to-openapi@8.0.0_zod@4.0.5", - "@hono/zod-validator@0.7.2_hono@4.8.5_zod@4.0.5", - "hono", - "openapi3-ts", - "zod@4.0.5" - ] - }, - "@hono/zod-validator@0.7.2_hono@4.8.5_zod@3.24.1": { - "integrity": "sha512-ub5eL/NeZ4eLZawu78JpW/J+dugDAYhwqUIdp9KYScI6PZECij4Hx4UsrthlEUutqDDhPwRI0MscUfNkvn/mqQ==", - "dependencies": [ - "hono", - "zod@3.24.1" - ] - }, - "@hono/zod-validator@0.7.2_hono@4.8.5_zod@4.0.5": { - "integrity": "sha512-ub5eL/NeZ4eLZawu78JpW/J+dugDAYhwqUIdp9KYScI6PZECij4Hx4UsrthlEUutqDDhPwRI0MscUfNkvn/mqQ==", - "dependencies": [ - "hono", - "zod@4.0.5" - ] - }, - "@hyperjump/browser@1.3.1": { - "integrity": "sha512-Le5XZUjnVqVjkgLYv6yyWgALat/0HpB1XaCPuCZ+GCFki9NvXloSZITIJ0H+wRW7mb9At1SxvohKBbNQbrr/cw==", - "dependencies": [ - "@hyperjump/json-pointer", - "@hyperjump/uri", - "content-type", - "just-curry-it" - ] - }, - "@hyperjump/json-pointer@1.1.1": { - "integrity": "sha512-M0T3s7TC2JepoWPMZQn1W6eYhFh06OXwpMqL+8c5wMVpvnCKNsPgpu9u7WyCI03xVQti8JAeAy4RzUa6SYlJLA==" - }, - "@hyperjump/json-schema@1.16.2_@hyperjump+browser@1.3.1": { - "integrity": "sha512-MJNvaEFc79+h5rvBPgAJK4OHEUr0RqsKcLC5rc3V9FEsJyQAjnP910deRFoZCE068kX/NrAPPhunMgUMwonPtg==", - "dependencies": [ - "@hyperjump/browser", - "@hyperjump/json-pointer", - "@hyperjump/pact", - "@hyperjump/uri", - "content-type", - "json-stringify-deterministic", - "just-curry-it", - "uuid" - ] - }, - "@hyperjump/pact@1.4.0": { - "integrity": "sha512-01Q7VY6BcAkp9W31Fv+ciiZycxZHGlR2N6ba9BifgyclHYHdbaZgITo0U6QMhYRlem4k8pf8J31/tApxvqAz8A==" - }, - "@hyperjump/uri@1.3.1": { - "integrity": "sha512-2ecKymxf6prQMgrNpAvlx4RhsuM5+PFT6oh6uUTZdv5qmBv0RZvxv8LJ7oR30ZxGhdPdZAl4We/1NFc0nqHeAw==" - }, - "@internationalized/date@3.8.2": { - "integrity": "sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==", - "dependencies": [ - "@swc/helpers" - ] - }, - "@internationalized/number@3.6.4": { - "integrity": "sha512-P+/h+RDaiX8EGt3shB9AYM1+QgkvHmJ5rKi4/59k4sg9g58k9rqsRW0WxRO7jCoHyvVbFRRFKmVTdFYdehrxHg==", - "dependencies": [ - "@swc/helpers" - ] - }, - "@jridgewell/gen-mapping@0.3.12": { - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "dependencies": [ - "@jridgewell/sourcemap-codec", - "@jridgewell/trace-mapping" - ] - }, - "@jridgewell/resolve-uri@3.1.2": { - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" - }, - "@jridgewell/source-map@0.3.10": { - "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", - "dependencies": [ - "@jridgewell/gen-mapping", - "@jridgewell/trace-mapping" - ] - }, - "@jridgewell/sourcemap-codec@1.5.4": { - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==" - }, - "@jridgewell/trace-mapping@0.3.29": { - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "dependencies": [ - "@jridgewell/resolve-uri", - "@jridgewell/sourcemap-codec" - ] - }, - "@phosphor-icons/core@2.1.1": { - "integrity": "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==" - }, - "@scalar/code-highlight@0.1.8": { - "integrity": "sha512-SH7gqBVGotf9Hw2srAKd/s854Wjf5lpR4FgAf/l3AlCYC5QXJ4hbj+fk1/3tvzeRFm+wvS6/ah4FuiIzvDPOug==", - "dependencies": [ - "hast-util-to-text", - "highlight.js", - "highlightjs-curl", - "highlightjs-vue", - "lowlight", - "rehype-external-links", - "rehype-format", - "rehype-parse", - "rehype-raw", - "rehype-sanitize", - "rehype-stringify", - "remark-gfm", - "remark-parse", - "remark-rehype", - "remark-stringify", - "unified", - "unist-util-visit" - ] - }, - "@scalar/components@0.14.20_vue@3.5.18": { - "integrity": "sha512-wGHsxz3R6l+u7KASwvm4Y9d7byD2HjfWBbCVXAb+qQIDemaMDBNARjRCbXLpRB9UvYLxtgP/8fzdW+R0LEf4/A==", - "dependencies": [ - "@floating-ui/utils", - "@floating-ui/vue", - "@headlessui/vue", - "@scalar/code-highlight", - "@scalar/icons", - "@scalar/oas-utils", - "@scalar/themes", - "@scalar/use-hooks", - "@scalar/use-toasts", - "@vueuse/core", - "cva", - "nanoid@5.1.5", - "pretty-bytes", - "radix-vue", - "vue" - ] - }, - "@scalar/core@0.3.10": { - "integrity": "sha512-WKLaZk5zj65CbS0Ox0BMZ1GX9gFrVdwgV/cdRjT2Av8H5CizOSirXk4PwdsU8V8Q4gAltVy9cnt6yKc2ZnRY+w==", - "dependencies": [ - "@scalar/types" - ] - }, - "@scalar/helpers@0.0.6": { - "integrity": "sha512-VrkpJmEq3zLu0lYFVRplNLA54rZ9rBkiahEI8laVpLnzGl1QilIn2EhxBT9lK7/2jbPprCbTfgnZZv+zHgGYTw==" - }, - "@scalar/hono-api-reference@0.9.12_hono@4.8.5": { - "integrity": "sha512-ygbQsh+5QaRyAx851utApy12FW+wSRLuOBTpL2U5RVzDjvN6vMBZs119mqH1BmY6ltVn8k7DvS2Ooyx0ttah/A==", - "dependencies": [ - "@scalar/core", - "hono" - ] - }, - "@scalar/icons@0.4.6": { - "integrity": "sha512-ho9ee29Ik+Jv17bSaQEXvGn1e2ydNEyc7Sv8A8hqPq084aIOiTBtVFISPPt+k/O6z0SIRayfL8FEZqr93GyZPg==", - "dependencies": [ - "@phosphor-icons/core", - "@types/node", - "chalk", - "vue" - ] - }, - "@scalar/json-diff@0.0.4": { - "integrity": "sha512-4u2gtyhA119T+HdQ4dVKYzQOCOos5Iaj08C1H2Hi6WW64sTNJaZuh9hpVtY94e0I9feRiCyNEtWvmbOjOAxbTw==" - }, - "@scalar/oas-utils@0.4.15_@hyperjump+browser@1.3.1": { - "integrity": "sha512-C2kGex6fwHxmBQhyiAmuVJHlIOP/rYoojuROfWvUQSrfLELkIjE5ZTyh4fj9PhwhGbS9iWWVaaOytjeT+8n8AQ==", - "dependencies": [ - "@hyperjump/browser", - "@hyperjump/json-schema", - "@scalar/helpers", - "@scalar/object-utils", - "@scalar/openapi-types", - "@scalar/themes", - "@scalar/types", - "@scalar/workspace-store", - "@types/har-format", - "flatted", - "microdiff", - "nanoid@5.1.5", - "type-fest", - "yaml", - "zod@3.24.1" - ] - }, - "@scalar/object-utils@1.2.2": { - "integrity": "sha512-dnH/JeXLudjBfEISpYvnMrH3bLjpc06vWVx7UQmtNNWIIonJ+bDMg1ATnYon7q6m+V8aJZ9/To8SwYsunYRL9g==", - "dependencies": [ - "flatted", - "just-clone", - "ts-deepmerge", - "type-fest" - ] - }, - "@scalar/openapi-parser@0.18.2_ajv@8.17.1": { - "integrity": "sha512-Mb0rRgnn8QgrwcsOcZY3g4uDr49cSuYcmh87oT7gDuV9IPFg5Wc71fjXNx0HdTRy5k21ugS/lDSl8sL+YBudOw==", - "dependencies": [ - "ajv", - "ajv-draft-04", - "ajv-formats", - "jsonpointer", - "leven", - "yaml" - ] - }, - "@scalar/openapi-to-markdown@0.2.23": { - "integrity": "sha512-j47K57Q0pDT3C1kAHmewV4kIBInQTjlvG+y4W5QgJkE64mzb8n5aZuMqzSCpyEItX4BsMEasIPMDWBCIhiicpw==", - "dependencies": [ - "@scalar/components", - "@scalar/oas-utils", - "@scalar/openapi-parser", - "@scalar/openapi-types", - "@scalar/snippetz", - "@scalar/types", - "html-minifier-terser", - "object-to-xml", - "rehype-parse", - "rehype-remark", - "rehype-sanitize", - "rehype-stringify", - "remark-gfm", - "remark-stringify", - "unified", - "vue" - ] - }, - "@scalar/openapi-types@0.3.6": { - "integrity": "sha512-hkLIgDIQxSgZNYCqNkty2p/V8hjeMf711uT8IKIjNw8oaT/cjT1HC/3V0FVhJ6x2eaufir+WXt5Zqv/iVk5pyQ==", - "dependencies": [ - "zod@3.24.1" - ] - }, - "@scalar/snippetz@0.4.3": { - "integrity": "sha512-OcYI4uvz/SbIkGupa3SNS403+lCsp4RoPJRTP1Rd59lRX9Kyc8xXo8ZuJis06GYtlBP5rqshI+jFCIP/Ap+dFw==", - "dependencies": [ - "@scalar/types", - "stringify-object" - ] - }, - "@scalar/themes@0.13.11": { - "integrity": "sha512-9rdNfVDbDFUr6Pi9IHt6OKs7Ui6TtrH7tR+ymga+I+jw8Xsnm4dbD1G2TFk1wUaYLVYdiqyQcj+JT15OJEGlkw==", - "dependencies": [ - "@scalar/types", - "nanoid@5.1.5" - ] - }, - "@scalar/types@0.2.10": { - "integrity": "sha512-ZlOzYoYzzGRnMr6RufAnjksv94MyL4s4HElg9mdo9gQXPch24lNiE0pE0k6vxdB+ncgbGWZ1T6qCC/lJXMLL1Q==", - "dependencies": [ - "@scalar/openapi-types", - "nanoid@5.1.5", - "zod@3.24.1" - ] - }, - "@scalar/use-hooks@0.2.4_vue@3.5.18": { - "integrity": "sha512-TXviVV9Cfmei6g24QadnfuFj2r1YkZY56ufsSnwHgLNbtDRd9U9jXGIswXAuA+k7whaEVEgcoZ3Zmq2v5ZLF8w==", - "dependencies": [ - "@scalar/use-toasts", - "@vueuse/core", - "cva", - "tailwind-merge", - "vue", - "zod@3.24.1" - ] - }, - "@scalar/use-toasts@0.8.0": { - "integrity": "sha512-u+o77cdTNZ5ePqHPu8ZcFw1BLlISv+cthN0bR1zJHXmqBjvanFTy2kL+Gmv3eW9HxZiHdqycKVETlYd0mWiqJQ==", - "dependencies": [ - "nanoid@5.1.5", - "vue", - "vue-sonner" - ] - }, - "@scalar/workspace-store@0.10.2": { - "integrity": "sha512-p4aDfAsZIFBBQPk2CcD96PMc2HFLOtGi2XMRNp86cqpQMncIIAqDTYCydzcsYvMPysmckIAAyuztdKDOPnry7g==", - "dependencies": [ - "@scalar/code-highlight", - "@scalar/helpers", - "@scalar/json-diff", - "@scalar/openapi-parser", - "@scalar/openapi-types", - "@scalar/types", - "@sinclair/typebox", - "github-slugger", - "vue", - "yaml" - ] - }, - "@sentry/core@9.40.0": { - "integrity": "sha512-cZkuz6BDna6VXSqvlWnrRsaDx4QBKq1PcfQrqhVz8ljs0M7Gcl+Mtj8dCzUxx12fkYM62hQXG72DEGNlAQpH/Q==" - }, - "@sentry/deno@9.40.0": { - "integrity": "sha512-fpccmhlhJ8FtzIVg53VVOo/PbV+7brzfN7b3DLdCCFmbm8Dss01pftlgBljZAGbHBvaCmwLFI1Ik703RpXci5g==", - "dependencies": [ - "@sentry/core" - ] - }, - "@sinclair/typebox@0.34.37": { - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "@swc/helpers@0.5.17": { - "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", - "dependencies": [ - "tslib" - ] - }, - "@tanstack/virtual-core@3.13.12": { - "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==" - }, - "@tanstack/vue-virtual@3.13.12_vue@3.5.18": { - "integrity": "sha512-vhF7kEU9EXWXh+HdAwKJ2m3xaOnTTmgcdXcF2pim8g4GvI7eRrk2YRuV5nUlZnd/NbCIX4/Ja2OZu5EjJL06Ww==", - "dependencies": [ - "@tanstack/virtual-core", - "vue" - ] - }, - "@types/debug@4.1.12": { - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "dependencies": [ - "@types/ms" - ] - }, - "@types/har-format@1.2.16": { - "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==" - }, - "@types/hast@3.0.4": { - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "dependencies": [ - "@types/unist" - ] - }, - "@types/mdast@4.0.4": { - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dependencies": [ - "@types/unist" - ] - }, - "@types/ms@2.1.0": { - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" - }, - "@types/node@22.16.5": { - "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==", - "dependencies": [ - "undici-types" - ] - }, - "@types/unist@3.0.3": { - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" - }, - "@types/web-bluetooth@0.0.20": { - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" - }, - "@ungap/structured-clone@1.3.0": { - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" - }, - "@vue/compiler-core@3.5.18": { - "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==", - "dependencies": [ - "@babel/parser", - "@vue/shared", - "entities@4.5.0", - "estree-walker", - "source-map-js" - ] - }, - "@vue/compiler-dom@3.5.18": { - "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==", - "dependencies": [ - "@vue/compiler-core", - "@vue/shared" - ] - }, - "@vue/compiler-sfc@3.5.18": { - "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==", - "dependencies": [ - "@babel/parser", - "@vue/compiler-core", - "@vue/compiler-dom", - "@vue/compiler-ssr", - "@vue/shared", - "estree-walker", - "magic-string", - "postcss", - "source-map-js" - ] - }, - "@vue/compiler-ssr@3.5.18": { - "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==", - "dependencies": [ - "@vue/compiler-dom", - "@vue/shared" - ] - }, - "@vue/reactivity@3.5.18": { - "integrity": "sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==", - "dependencies": [ - "@vue/shared" - ] - }, - "@vue/runtime-core@3.5.18": { - "integrity": "sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==", - "dependencies": [ - "@vue/reactivity", - "@vue/shared" - ] - }, - "@vue/runtime-dom@3.5.18": { - "integrity": "sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==", - "dependencies": [ - "@vue/reactivity", - "@vue/runtime-core", - "@vue/shared", - "csstype" - ] - }, - "@vue/server-renderer@3.5.18_vue@3.5.18": { - "integrity": "sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==", - "dependencies": [ - "@vue/compiler-ssr", - "@vue/shared", - "vue" - ] - }, - "@vue/shared@3.5.18": { - "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==" - }, - "@vueuse/core@10.11.1_vue@3.5.18": { - "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", - "dependencies": [ - "@types/web-bluetooth", - "@vueuse/metadata", - "@vueuse/shared", - "vue-demi" - ] - }, - "@vueuse/metadata@10.11.1": { - "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==" - }, - "@vueuse/shared@10.11.1_vue@3.5.18": { - "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", - "dependencies": [ - "vue-demi" - ] - }, - "acorn@8.15.0": { - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "bin": true - }, - "ajv-draft-04@1.0.0_ajv@8.17.1": { - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", - "dependencies": [ - "ajv" - ], - "optionalPeers": [ - "ajv" - ] - }, - "ajv-formats@3.0.1_ajv@8.17.1": { - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dependencies": [ - "ajv" - ], - "optionalPeers": [ - "ajv" - ] - }, - "ajv@8.17.1": { - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dependencies": [ - "fast-deep-equal", - "fast-uri", - "json-schema-traverse", - "require-from-string" - ] - }, - "aria-hidden@1.2.6": { - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "dependencies": [ - "tslib" - ] - }, - "bail@2.0.2": { - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" - }, - "buffer-from@1.1.2": { - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "camel-case@4.1.2": { - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dependencies": [ - "pascal-case", - "tslib" - ] - }, - "ccount@2.0.1": { - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" - }, - "chalk@5.4.1": { - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==" - }, - "character-entities-html4@2.1.0": { - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==" - }, - "character-entities-legacy@3.0.0": { - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==" - }, - "character-entities@2.0.2": { - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==" - }, - "clean-css@5.3.3": { - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "dependencies": [ - "source-map" - ] - }, - "clsx@2.1.1": { - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" - }, - "comma-separated-tokens@2.0.3": { - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" - }, - "commander@10.0.1": { - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==" - }, - "commander@2.20.3": { - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "content-type@1.0.5": { - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "csstype@3.1.3": { - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" - }, - "cva@1.0.0-beta.2": { - "integrity": "sha512-dqcOFe247I5pKxfuzqfq3seLL5iMYsTgo40Uw7+pKZAntPgFtR7Tmy59P5IVIq/XgB0NQWoIvYDt9TwHkuK8Cg==", - "dependencies": [ - "clsx" - ] - }, - "dank-each@1.0.0": { - "integrity": "sha512-gMDy24y+3LlnAaHq4WFwRKliMZRkGp41Gy9JVsD1BO5tprb/lEh4afJlkankcTqRoppSaHRwgFQX61QjJ5ClfQ==" - }, - "dank-map@0.1.0": { - "integrity": "sha512-mQoLySkWc5bQM8XKXz0rIuISX/+12rSSfPojYlTVT6KPj3LsvfLURtrv0w+QEt1gRIKwp9mxnwOcL5nsOTkk2Q==" - }, - "debug@4.4.1": { - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dependencies": [ - "ms" - ] - }, - "decode-named-character-reference@1.2.0": { - "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", - "dependencies": [ - "character-entities" - ] - }, - "defu@6.1.4": { - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==" - }, - "dequal@2.0.3": { - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" - }, - "devlop@1.1.0": { - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dependencies": [ - "dequal" - ] - }, - "dot-case@3.0.4": { - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dependencies": [ - "no-case", - "tslib" - ] - }, - "entities@4.5.0": { - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" - }, - "entities@6.0.1": { - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==" - }, - "escape-string-regexp@5.0.0": { - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==" - }, - "estree-walker@2.0.2": { - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, - "extend@3.0.2": { - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "fast-deep-equal@3.1.3": { - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-uri@3.0.6": { - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==" - }, - "flatted@3.3.3": { - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" - }, - "get-own-enumerable-keys@1.0.0": { - "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==" - }, - "github-slugger@2.0.0": { - "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" - }, - "hast-util-embedded@3.0.0": { - "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", - "dependencies": [ - "@types/hast", - "hast-util-is-element" - ] - }, - "hast-util-format@1.1.0": { - "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", - "dependencies": [ - "@types/hast", - "hast-util-embedded", - "hast-util-minify-whitespace", - "hast-util-phrasing", - "hast-util-whitespace", - "html-whitespace-sensitive-tag-names", - "unist-util-visit-parents" - ] - }, - "hast-util-from-html@2.0.3": { - "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", - "dependencies": [ - "@types/hast", - "devlop", - "hast-util-from-parse5", - "parse5", - "vfile", - "vfile-message" - ] - }, - "hast-util-from-parse5@8.0.3": { - "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", - "dependencies": [ - "@types/hast", - "@types/unist", - "devlop", - "hastscript", - "property-information@7.1.0", - "vfile", - "vfile-location", - "web-namespaces" - ] - }, - "hast-util-has-property@3.0.0": { - "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", - "dependencies": [ - "@types/hast" - ] - }, - "hast-util-is-body-ok-link@3.0.1": { - "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", - "dependencies": [ - "@types/hast" - ] - }, - "hast-util-is-element@3.0.0": { - "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", - "dependencies": [ - "@types/hast" - ] - }, - "hast-util-minify-whitespace@1.0.1": { - "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", - "dependencies": [ - "@types/hast", - "hast-util-embedded", - "hast-util-is-element", - "hast-util-whitespace", - "unist-util-is" - ] - }, - "hast-util-parse-selector@4.0.0": { - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "dependencies": [ - "@types/hast" - ] - }, - "hast-util-phrasing@3.0.1": { - "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", - "dependencies": [ - "@types/hast", - "hast-util-embedded", - "hast-util-has-property", - "hast-util-is-body-ok-link", - "hast-util-is-element" - ] - }, - "hast-util-raw@9.1.0": { - "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", - "dependencies": [ - "@types/hast", - "@types/unist", - "@ungap/structured-clone", - "hast-util-from-parse5", - "hast-util-to-parse5", - "html-void-elements", - "mdast-util-to-hast", - "parse5", - "unist-util-position", - "unist-util-visit", - "vfile", - "web-namespaces", - "zwitch" - ] - }, - "hast-util-sanitize@5.0.2": { - "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==", - "dependencies": [ - "@types/hast", - "@ungap/structured-clone", - "unist-util-position" - ] - }, - "hast-util-to-html@9.0.5": { - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "dependencies": [ - "@types/hast", - "@types/unist", - "ccount", - "comma-separated-tokens", - "hast-util-whitespace", - "html-void-elements", - "mdast-util-to-hast", - "property-information@7.1.0", - "space-separated-tokens", - "stringify-entities", - "zwitch" - ] - }, - "hast-util-to-mdast@10.1.2": { - "integrity": "sha512-FiCRI7NmOvM4y+f5w32jPRzcxDIz+PUqDwEqn1A+1q2cdp3B8Gx7aVrXORdOKjMNDQsD1ogOr896+0jJHW1EFQ==", - "dependencies": [ - "@types/hast", - "@types/mdast", - "@ungap/structured-clone", - "hast-util-phrasing", - "hast-util-to-html", - "hast-util-to-text", - "hast-util-whitespace", - "mdast-util-phrasing", - "mdast-util-to-hast", - "mdast-util-to-string", - "rehype-minify-whitespace", - "trim-trailing-lines", - "unist-util-position", - "unist-util-visit" - ] - }, - "hast-util-to-parse5@8.0.0": { - "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", - "dependencies": [ - "@types/hast", - "comma-separated-tokens", - "devlop", - "property-information@6.5.0", - "space-separated-tokens", - "web-namespaces", - "zwitch" - ] - }, - "hast-util-to-text@4.0.2": { - "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", - "dependencies": [ - "@types/hast", - "@types/unist", - "hast-util-is-element", - "unist-util-find-after" - ] - }, - "hast-util-whitespace@3.0.0": { - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dependencies": [ - "@types/hast" - ] - }, - "hastscript@9.0.1": { - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", - "dependencies": [ - "@types/hast", - "comma-separated-tokens", - "hast-util-parse-selector", - "property-information@7.1.0", - "space-separated-tokens" - ] - }, - "highlight.js@11.11.1": { - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==" - }, - "highlightjs-curl@1.3.0": { - "integrity": "sha512-50UEfZq1KR0Lfk2Tr6xb/MUIZH3h10oNC0OTy9g7WELcs5Fgy/mKN1vEhuKTkKbdo8vr5F9GXstu2eLhApfQ3A==" - }, - "highlightjs-vue@1.0.0": { - "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==" - }, - "hono@4.8.5": { - "integrity": "sha512-Up2cQbtNz1s111qpnnECdTGqSIUIhZJMLikdKkshebQSEBcoUKq6XJayLGqSZWidiH0zfHRCJqFu062Mz5UuRA==" - }, - "html-minifier-terser@7.2.0": { - "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", - "dependencies": [ - "camel-case", - "clean-css", - "commander@10.0.1", - "entities@4.5.0", - "param-case", - "relateurl", - "terser" - ], - "bin": true - }, - "html-void-elements@3.0.0": { - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==" - }, - "html-whitespace-sensitive-tag-names@3.0.1": { - "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==" - }, - "is-absolute-url@4.0.1": { - "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==" - }, - "is-obj@3.0.0": { - "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==" - }, - "is-plain-obj@4.1.0": { - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" - }, - "is-regexp@3.1.0": { - "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==" - }, - "json-schema-traverse@1.0.0": { - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "json-stringify-deterministic@1.0.12": { - "integrity": "sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g==" - }, - "jsonpointer@5.0.1": { - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==" - }, - "just-clone@6.2.0": { - "integrity": "sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==" - }, - "just-curry-it@5.3.0": { - "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==" - }, - "leven@4.0.0": { - "integrity": "sha512-puehA3YKku3osqPlNuzGDUHq8WpwXupUg1V6NXdV38G+gr+gkBwFC8g1b/+YcIvp8gnqVIus+eJCH/eGsRmJNw==" - }, - "longest-streak@3.1.0": { - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==" - }, - "lower-case@2.0.2": { - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dependencies": [ - "tslib" - ] - }, - "lowlight@3.3.0": { - "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", - "dependencies": [ - "@types/hast", - "devlop", - "highlight.js" - ] - }, - "magic-string@0.30.17": { - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dependencies": [ - "@jridgewell/sourcemap-codec" - ] - }, - "markdown-table@3.0.4": { - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==" - }, - "mdast-util-find-and-replace@3.0.2": { - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "dependencies": [ - "@types/mdast", - "escape-string-regexp", - "unist-util-is", - "unist-util-visit-parents" - ] - }, - "mdast-util-from-markdown@2.0.2": { - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", - "dependencies": [ - "@types/mdast", - "@types/unist", - "decode-named-character-reference", - "devlop", - "mdast-util-to-string", - "micromark", - "micromark-util-decode-numeric-character-reference", - "micromark-util-decode-string", - "micromark-util-normalize-identifier", - "micromark-util-symbol", - "micromark-util-types", - "unist-util-stringify-position" - ] - }, - "mdast-util-gfm-autolink-literal@2.0.1": { - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "dependencies": [ - "@types/mdast", - "ccount", - "devlop", - "mdast-util-find-and-replace", - "micromark-util-character" - ] - }, - "mdast-util-gfm-footnote@2.1.0": { - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "dependencies": [ - "@types/mdast", - "devlop", - "mdast-util-from-markdown", - "mdast-util-to-markdown", - "micromark-util-normalize-identifier" - ] - }, - "mdast-util-gfm-strikethrough@2.0.0": { - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "dependencies": [ - "@types/mdast", - "mdast-util-from-markdown", - "mdast-util-to-markdown" - ] - }, - "mdast-util-gfm-table@2.0.0": { - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "dependencies": [ - "@types/mdast", - "devlop", - "markdown-table", - "mdast-util-from-markdown", - "mdast-util-to-markdown" - ] - }, - "mdast-util-gfm-task-list-item@2.0.0": { - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "dependencies": [ - "@types/mdast", - "devlop", - "mdast-util-from-markdown", - "mdast-util-to-markdown" - ] - }, - "mdast-util-gfm@3.1.0": { - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "dependencies": [ - "mdast-util-from-markdown", - "mdast-util-gfm-autolink-literal", - "mdast-util-gfm-footnote", - "mdast-util-gfm-strikethrough", - "mdast-util-gfm-table", - "mdast-util-gfm-task-list-item", - "mdast-util-to-markdown" - ] - }, - "mdast-util-phrasing@4.1.0": { - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "dependencies": [ - "@types/mdast", - "unist-util-is" - ] - }, - "mdast-util-to-hast@13.2.0": { - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", - "dependencies": [ - "@types/hast", - "@types/mdast", - "@ungap/structured-clone", - "devlop", - "micromark-util-sanitize-uri", - "trim-lines", - "unist-util-position", - "unist-util-visit", - "vfile" - ] - }, - "mdast-util-to-markdown@2.1.2": { - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "dependencies": [ - "@types/mdast", - "@types/unist", - "longest-streak", - "mdast-util-phrasing", - "mdast-util-to-string", - "micromark-util-classify-character", - "micromark-util-decode-string", - "unist-util-visit", - "zwitch" - ] - }, - "mdast-util-to-string@4.0.0": { - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "dependencies": [ - "@types/mdast" - ] - }, - "microdiff@1.5.0": { - "integrity": "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==" - }, - "micromark-core-commonmark@2.0.3": { - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "dependencies": [ - "decode-named-character-reference", - "devlop", - "micromark-factory-destination", - "micromark-factory-label", - "micromark-factory-space", - "micromark-factory-title", - "micromark-factory-whitespace", - "micromark-util-character", - "micromark-util-chunked", - "micromark-util-classify-character", - "micromark-util-html-tag-name", - "micromark-util-normalize-identifier", - "micromark-util-resolve-all", - "micromark-util-subtokenize", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-extension-gfm-autolink-literal@2.1.0": { - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "dependencies": [ - "micromark-util-character", - "micromark-util-sanitize-uri", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-extension-gfm-footnote@2.1.0": { - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "dependencies": [ - "devlop", - "micromark-core-commonmark", - "micromark-factory-space", - "micromark-util-character", - "micromark-util-normalize-identifier", - "micromark-util-sanitize-uri", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-extension-gfm-strikethrough@2.1.0": { - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "dependencies": [ - "devlop", - "micromark-util-chunked", - "micromark-util-classify-character", - "micromark-util-resolve-all", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-extension-gfm-table@2.1.1": { - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "dependencies": [ - "devlop", - "micromark-factory-space", - "micromark-util-character", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-extension-gfm-tagfilter@2.0.0": { - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "dependencies": [ - "micromark-util-types" - ] - }, - "micromark-extension-gfm-task-list-item@2.1.0": { - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "dependencies": [ - "devlop", - "micromark-factory-space", - "micromark-util-character", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-extension-gfm@3.0.0": { - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "dependencies": [ - "micromark-extension-gfm-autolink-literal", - "micromark-extension-gfm-footnote", - "micromark-extension-gfm-strikethrough", - "micromark-extension-gfm-table", - "micromark-extension-gfm-tagfilter", - "micromark-extension-gfm-task-list-item", - "micromark-util-combine-extensions", - "micromark-util-types" - ] - }, - "micromark-factory-destination@2.0.1": { - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "dependencies": [ - "micromark-util-character", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-factory-label@2.0.1": { - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "dependencies": [ - "devlop", - "micromark-util-character", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-factory-space@2.0.1": { - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "dependencies": [ - "micromark-util-character", - "micromark-util-types" - ] - }, - "micromark-factory-title@2.0.1": { - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "dependencies": [ - "micromark-factory-space", - "micromark-util-character", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-factory-whitespace@2.0.1": { - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "dependencies": [ - "micromark-factory-space", - "micromark-util-character", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-util-character@2.1.1": { - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dependencies": [ - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-util-chunked@2.0.1": { - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "dependencies": [ - "micromark-util-symbol" - ] - }, - "micromark-util-classify-character@2.0.1": { - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "dependencies": [ - "micromark-util-character", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-util-combine-extensions@2.0.1": { - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "dependencies": [ - "micromark-util-chunked", - "micromark-util-types" - ] - }, - "micromark-util-decode-numeric-character-reference@2.0.2": { - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "dependencies": [ - "micromark-util-symbol" - ] - }, - "micromark-util-decode-string@2.0.1": { - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "dependencies": [ - "decode-named-character-reference", - "micromark-util-character", - "micromark-util-decode-numeric-character-reference", - "micromark-util-symbol" - ] - }, - "micromark-util-encode@2.0.1": { - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==" - }, - "micromark-util-html-tag-name@2.0.1": { - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==" - }, - "micromark-util-normalize-identifier@2.0.1": { - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "dependencies": [ - "micromark-util-symbol" - ] - }, - "micromark-util-resolve-all@2.0.1": { - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "dependencies": [ - "micromark-util-types" - ] - }, - "micromark-util-sanitize-uri@2.0.1": { - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "dependencies": [ - "micromark-util-character", - "micromark-util-encode", - "micromark-util-symbol" - ] - }, - "micromark-util-subtokenize@2.1.0": { - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "dependencies": [ - "devlop", - "micromark-util-chunked", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "micromark-util-symbol@2.0.1": { - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==" - }, - "micromark-util-types@2.0.2": { - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==" - }, - "micromark@4.0.2": { - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "dependencies": [ - "@types/debug", - "debug", - "decode-named-character-reference", - "devlop", - "micromark-core-commonmark", - "micromark-factory-space", - "micromark-util-character", - "micromark-util-chunked", - "micromark-util-combine-extensions", - "micromark-util-decode-numeric-character-reference", - "micromark-util-encode", - "micromark-util-normalize-identifier", - "micromark-util-resolve-all", - "micromark-util-sanitize-uri", - "micromark-util-subtokenize", - "micromark-util-symbol", - "micromark-util-types" - ] - }, - "ms@2.1.3": { - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "nanoid@3.3.11": { - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "bin": true - }, - "nanoid@5.1.5": { - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "bin": true - }, - "no-case@3.0.4": { - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dependencies": [ - "lower-case", - "tslib" - ] - }, - "object-to-xml@2.0.0": { - "integrity": "sha512-bArXy7WCF1V9R88/zF9adSZSeFQnFmmKhMqNuNLAxqrbkvzcWP8HgnaRCcVJsfvIgvpdHiYd0qzJi7LM7QFfcQ==", - "dependencies": [ - "dank-each", - "dank-map", - "sanitizer" - ] - }, - "openapi3-ts@4.5.0": { - "integrity": "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==", - "dependencies": [ - "yaml" - ] - }, - "param-case@3.0.4": { - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dependencies": [ - "dot-case", - "tslib" - ] - }, - "parse5@7.3.0": { - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dependencies": [ - "entities@6.0.1" - ] - }, - "pascal-case@3.1.2": { - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dependencies": [ - "no-case", - "tslib" - ] - }, - "picocolors@1.1.1": { - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "postcss@8.5.6": { - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dependencies": [ - "nanoid@3.3.11", - "picocolors", - "source-map-js" - ] - }, - "pretty-bytes@6.1.1": { - "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==" - }, - "property-information@6.5.0": { - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==" - }, - "property-information@7.1.0": { - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" - }, - "radix-vue@1.9.17_vue@3.5.18": { - "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==", - "dependencies": [ - "@floating-ui/dom", - "@floating-ui/vue", - "@internationalized/date", - "@internationalized/number", - "@tanstack/vue-virtual", - "@vueuse/core", - "@vueuse/shared", - "aria-hidden", - "defu", - "fast-deep-equal", - "nanoid@5.1.5", - "vue" - ] - }, - "rehype-external-links@3.0.0": { - "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==", - "dependencies": [ - "@types/hast", - "@ungap/structured-clone", - "hast-util-is-element", - "is-absolute-url", - "space-separated-tokens", - "unist-util-visit" - ] - }, - "rehype-format@5.0.1": { - "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", - "dependencies": [ - "@types/hast", - "hast-util-format" - ] - }, - "rehype-minify-whitespace@6.0.2": { - "integrity": "sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==", - "dependencies": [ - "@types/hast", - "hast-util-minify-whitespace" - ] - }, - "rehype-parse@9.0.1": { - "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", - "dependencies": [ - "@types/hast", - "hast-util-from-html", - "unified" - ] - }, - "rehype-raw@7.0.0": { - "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", - "dependencies": [ - "@types/hast", - "hast-util-raw", - "vfile" - ] - }, - "rehype-remark@10.0.1": { - "integrity": "sha512-EmDndlb5NVwXGfUa4c9GPK+lXeItTilLhE6ADSaQuHr4JUlKw9MidzGzx4HpqZrNCt6vnHmEifXQiiA+CEnjYQ==", - "dependencies": [ - "@types/hast", - "@types/mdast", - "hast-util-to-mdast", - "unified", - "vfile" - ] - }, - "rehype-sanitize@6.0.0": { - "integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==", - "dependencies": [ - "@types/hast", - "hast-util-sanitize" - ] - }, - "rehype-stringify@10.0.1": { - "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", - "dependencies": [ - "@types/hast", - "hast-util-to-html", - "unified" - ] - }, - "relateurl@0.2.7": { - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==" - }, - "remark-gfm@4.0.1": { - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "dependencies": [ - "@types/mdast", - "mdast-util-gfm", - "micromark-extension-gfm", - "remark-parse", - "remark-stringify", - "unified" - ] - }, - "remark-parse@11.0.0": { - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "dependencies": [ - "@types/mdast", - "mdast-util-from-markdown", - "micromark-util-types", - "unified" - ] - }, - "remark-rehype@11.1.2": { - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", - "dependencies": [ - "@types/hast", - "@types/mdast", - "mdast-util-to-hast", - "unified", - "vfile" - ] - }, - "remark-stringify@11.0.0": { - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "dependencies": [ - "@types/mdast", - "mdast-util-to-markdown", - "unified" - ] - }, - "require-from-string@2.0.2": { - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" - }, - "sanitizer@0.1.3": { - "integrity": "sha512-j05vL56tR90rsYqm9ZD05v6K4HI7t4yMDEvvU0x4f+IADXM9Jx1x9mzatxOs5drJq6dGhugxDW99mcPvXVLl+Q==" - }, - "source-map-js@1.2.1": { - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" - }, - "source-map-support@0.5.21": { - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": [ - "buffer-from", - "source-map" - ] - }, - "source-map@0.6.1": { - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "space-separated-tokens@2.0.2": { - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" - }, - "stringify-entities@4.0.4": { - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dependencies": [ - "character-entities-html4", - "character-entities-legacy" - ] - }, - "stringify-object@5.0.0": { - "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", - "dependencies": [ - "get-own-enumerable-keys", - "is-obj", - "is-regexp" - ] - }, - "tailwind-merge@2.6.0": { - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==" - }, - "terser@5.43.1": { - "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", - "dependencies": [ - "@jridgewell/source-map", - "acorn", - "commander@2.20.3", - "source-map-support" - ], - "bin": true - }, - "trim-lines@3.0.1": { - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" - }, - "trim-trailing-lines@2.1.0": { - "integrity": "sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==" - }, - "trough@2.2.0": { - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==" - }, - "ts-deepmerge@7.0.3": { - "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==" - }, - "tslib@2.8.1": { - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "type-fest@4.41.0": { - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==" - }, - "undici-types@6.21.0": { - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" - }, - "unified@11.0.5": { - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "dependencies": [ - "@types/unist", - "bail", - "devlop", - "extend", - "is-plain-obj", - "trough", - "vfile" - ] - }, - "unist-util-find-after@5.0.0": { - "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", - "dependencies": [ - "@types/unist", - "unist-util-is" - ] - }, - "unist-util-is@6.0.0": { - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", - "dependencies": [ - "@types/unist" - ] - }, - "unist-util-position@5.0.0": { - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dependencies": [ - "@types/unist" - ] - }, - "unist-util-stringify-position@4.0.0": { - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": [ - "@types/unist" - ] - }, - "unist-util-visit-parents@6.0.1": { - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "dependencies": [ - "@types/unist", - "unist-util-is" - ] - }, - "unist-util-visit@5.0.0": { - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "dependencies": [ - "@types/unist", - "unist-util-is", - "unist-util-visit-parents" - ] - }, - "uuid@9.0.1": { - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "bin": true - }, - "vfile-location@5.0.3": { - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "dependencies": [ - "@types/unist", - "vfile" - ] - }, - "vfile-message@4.0.2": { - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dependencies": [ - "@types/unist", - "unist-util-stringify-position" - ] - }, - "vfile@6.0.3": { - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dependencies": [ - "@types/unist", - "vfile-message" - ] - }, - "vue-demi@0.14.10_vue@3.5.18": { - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "dependencies": [ - "vue" - ], - "scripts": true, - "bin": true - }, - "vue-sonner@1.3.2": { - "integrity": "sha512-UbZ48E9VIya3ToiRHAZUbodKute/z/M1iT8/3fU8zEbwBRE11AKuHikssv18LMk2gTTr6eMQT4qf6JoLHWuj/A==" - }, - "vue@3.5.18": { - "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==", - "dependencies": [ - "@vue/compiler-dom", - "@vue/compiler-sfc", - "@vue/runtime-dom", - "@vue/server-renderer", - "@vue/shared" - ] - }, - "web-namespaces@2.0.1": { - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==" - }, - "yaml@2.8.0": { - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "bin": true - }, - "zod@3.24.1": { - "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==" - }, - "zod@4.0.5": { - "integrity": "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==" - }, - "zwitch@2.0.4": { - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" - } - }, "remote": { - "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", - "https://deno.land/std@0.129.0/async/deferred.ts": "bc18e28108252c9f67dfca2bbc4587c3cbf3aeb6e155f8c864ca8ecff992b98a", - "https://deno.land/std@0.129.0/async/delay.ts": "cbbdf1c87d1aed8edc7bae13592fb3e27e3106e0748f089c263390d4f49e5f6c", - "https://deno.land/std@0.129.0/async/mod.ts": "2240c6841157738414331f47dee09bb8c0482c5b1980b6e3234dd03515c8132f", - "https://deno.land/std@0.129.0/async/mux_async_iterator.ts": "f4d1d259b0c694d381770ddaaa4b799a94843eba80c17f4a2ec2949168e52d1e", - "https://deno.land/std@0.129.0/async/pool.ts": "97b0dd27c69544e374df857a40902e74e39532f226005543eabacb551e277082", - "https://deno.land/std@0.129.0/async/tee.ts": "1341feb1f5b1a96f8628d0f8fc07d8c43d3813423f18a63bf1b4785568d21b1f", - "https://deno.land/std@0.129.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", - "https://deno.land/std@0.129.0/http/http_status.ts": "71838ad33cb03ff2575f5e8fdea5f930979cdff8a81c11ade497f11d4c026019", - "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.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", - "https://deno.land/std@0.208.0/fs/copy.ts": "ca19e4837965914471df38fbd61e16f9e8adfe89f9cffb0c83615c83ea3fc2bf", - "https://deno.land/std@0.208.0/fs/empty_dir.ts": "7fba29ef2d03f3503cd512616efc0535984cf1bbe7ca9d098e8b4d0d88910120", - "https://deno.land/std@0.208.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", - "https://deno.land/std@0.208.0/fs/ensure_file.ts": "39ac83cc283a20ec2735e956adf5de3e8a3334e0b6820547b5772f71c49ae083", - "https://deno.land/std@0.208.0/fs/ensure_link.ts": "c15e69c48556d78aae31b83e0c0ece04b7b8bc0951412f5b759aceb6fde7f0ac", - "https://deno.land/std@0.208.0/fs/ensure_symlink.ts": "b389c8568f0656d145ac7ece472afe710815cccbb2ebfd19da7978379ae143fe", - "https://deno.land/std@0.208.0/fs/eol.ts": "8565e1e076c5baced170236617150a7833668658e000205d896fc54084309ce1", - "https://deno.land/std@0.208.0/fs/exists.ts": "cb59a853d84871d87acab0e7936a4dac11282957f8e195102c5a7acb42546bb8", - "https://deno.land/std@0.208.0/fs/expand_glob.ts": "4f98c508fc9e40d6311d2f7fd88aaad05235cc506388c22dda315e095305811d", - "https://deno.land/std@0.208.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898", - "https://deno.land/std@0.208.0/fs/move.ts": "b4f8f46730b40c32ea3c0bc8eb0fd0e8139249a698883c7b3756424cf19785c9", - "https://deno.land/std@0.208.0/fs/walk.ts": "c1e6b43f72a46e89b630140308bd51a4795d416a416b4cfb7cd4bd1e25946723", - "https://deno.land/std@0.208.0/path/_common/assert_path.ts": "061e4d093d4ba5aebceb2c4da3318bfe3289e868570e9d3a8e327d91c2958946", - "https://deno.land/std@0.208.0/path/_common/basename.ts": "0d978ff818f339cd3b1d09dc914881f4d15617432ae519c1b8fdc09ff8d3789a", - "https://deno.land/std@0.208.0/path/_common/common.ts": "9e4233b2eeb50f8b2ae10ecc2108f58583aea6fd3e8907827020282dc2b76143", - "https://deno.land/std@0.208.0/path/_common/constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", - "https://deno.land/std@0.208.0/path/_common/dirname.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", - "https://deno.land/std@0.208.0/path/_common/format.ts": "11aa62e316dfbf22c126917f5e03ea5fe2ee707386555a8f513d27ad5756cf96", - "https://deno.land/std@0.208.0/path/_common/from_file_url.ts": "ef1bf3197d2efbf0297a2bdbf3a61d804b18f2bcce45548ae112313ec5be3c22", - "https://deno.land/std@0.208.0/path/_common/glob_to_reg_exp.ts": "5c3c2b79fc2294ec803d102bd9855c451c150021f452046312819fbb6d4dc156", - "https://deno.land/std@0.208.0/path/_common/normalize.ts": "2ba7fb4cc9fafb0f38028f434179579ce61d4d9e51296fad22b701c3d3cd7397", - "https://deno.land/std@0.208.0/path/_common/normalize_string.ts": "88c472f28ae49525f9fe82de8c8816d93442d46a30d6bb5063b07ff8a89ff589", - "https://deno.land/std@0.208.0/path/_common/relative.ts": "1af19d787a2a84b8c534cc487424fe101f614982ae4851382c978ab2216186b4", - "https://deno.land/std@0.208.0/path/_common/strip_trailing_separators.ts": "7ffc7c287e97bdeeee31b155828686967f222cd73f9e5780bfe7dfb1b58c6c65", - "https://deno.land/std@0.208.0/path/_common/to_file_url.ts": "a8cdd1633bc9175b7eebd3613266d7c0b6ae0fb0cff24120b6092ac31662f9ae", - "https://deno.land/std@0.208.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", - "https://deno.land/std@0.208.0/path/_os.ts": "30b0c2875f360c9296dbe6b7f2d528f0f9c741cecad2e97f803f5219e91b40a2", - "https://deno.land/std@0.208.0/path/basename.ts": "04bb5ef3e86bba8a35603b8f3b69537112cdd19ce64b77f2522006da2977a5f3", - "https://deno.land/std@0.208.0/path/common.ts": "f4d061c7d0b95a65c2a1a52439edec393e906b40f1caf4604c389fae7caa80f5", - "https://deno.land/std@0.208.0/path/dirname.ts": "88a0a71c21debafc4da7a4cd44fd32e899462df458fbca152390887d41c40361", - "https://deno.land/std@0.208.0/path/extname.ts": "2da4e2490f3b48b7121d19fb4c91681a5e11bd6bd99df4f6f47d7a71bb6ecdf2", - "https://deno.land/std@0.208.0/path/format.ts": "3457530cc85d1b4bab175f9ae73998b34fd456c830d01883169af0681b8894fb", - "https://deno.land/std@0.208.0/path/from_file_url.ts": "e7fa233ea1dff9641e8d566153a24d95010110185a6f418dd2e32320926043f8", - "https://deno.land/std@0.208.0/path/glob.ts": "a00a81a55c02bbe074ab21a50b6495c6f7795f54cd718c824adaa92c6c9b7419", - "https://deno.land/std@0.208.0/path/glob_to_regexp.ts": "74d7448c471e293d03f05ccb968df4365fed6aaa508506b6325a8efdc01d8271", - "https://deno.land/std@0.208.0/path/is_absolute.ts": "67232b41b860571c5b7537f4954c88d86ae2ba45e883ee37d3dec27b74909d13", - "https://deno.land/std@0.208.0/path/is_glob.ts": "567dce5c6656bdedfc6b3ee6c0833e1e4db2b8dff6e62148e94a917f289c06ad", - "https://deno.land/std@0.208.0/path/join.ts": "98d3d76c819af4a11a81d5ba2dbb319f1ce9d63fc2b615597d4bcfddd4a89a09", - "https://deno.land/std@0.208.0/path/join_globs.ts": "9b84d5103b63d3dbed4b2cf8b12477b2ad415c7d343f1488505162dc0e5f4db8", - "https://deno.land/std@0.208.0/path/mod.ts": "3defabebc98279e62b392fee7a6937adc932a8f4dcd2471441e36c15b97b00e0", - "https://deno.land/std@0.208.0/path/normalize.ts": "aa95be9a92c7bd4f9dc0ba51e942a1973e2b93d266cd74f5ca751c136d520b66", - "https://deno.land/std@0.208.0/path/normalize_glob.ts": "674baa82e1c00b6cb153bbca36e06f8e0337cb8062db6d905ab5de16076ca46b", - "https://deno.land/std@0.208.0/path/parse.ts": "d87ff0deef3fb495bc0d862278ff96da5a06acf0625ca27769fc52ac0d3d6ece", - "https://deno.land/std@0.208.0/path/posix/_util.ts": "ecf49560fedd7dd376c6156cc5565cad97c1abe9824f4417adebc7acc36c93e5", - "https://deno.land/std@0.208.0/path/posix/basename.ts": "a630aeb8fd8e27356b1823b9dedd505e30085015407caa3396332752f6b8406a", - "https://deno.land/std@0.208.0/path/posix/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", - "https://deno.land/std@0.208.0/path/posix/dirname.ts": "f48c9c42cc670803b505478b7ef162c7cfa9d8e751b59d278b2ec59470531472", - "https://deno.land/std@0.208.0/path/posix/extname.ts": "ee7f6571a9c0a37f9218fbf510c440d1685a7c13082c348d701396cc795e0be0", - "https://deno.land/std@0.208.0/path/posix/format.ts": "b94876f77e61bfe1f147d5ccb46a920636cd3cef8be43df330f0052b03875968", - "https://deno.land/std@0.208.0/path/posix/from_file_url.ts": "b97287a83e6407ac27bdf3ab621db3fccbf1c27df0a1b1f20e1e1b5acf38a379", - "https://deno.land/std@0.208.0/path/posix/glob_to_regexp.ts": "6ed00c71fbfe0ccc35977c35444f94e82200b721905a60bd1278b1b768d68b1a", - "https://deno.land/std@0.208.0/path/posix/is_absolute.ts": "159900a3422d11069d48395568217eb7fc105ceda2683d03d9b7c0f0769e01b8", - "https://deno.land/std@0.208.0/path/posix/is_glob.ts": "ec4fbc604b9db8487f7b56ab0e759b24a971ab6a45f7b0b698bc39b8b9f9680f", - "https://deno.land/std@0.208.0/path/posix/join.ts": "0c0d84bdc344876930126640011ec1b888e6facf74153ffad9ef26813aa2a076", - "https://deno.land/std@0.208.0/path/posix/join_globs.ts": "f4838d54b1f60a34a40625a3293f6e583135348be1b2974341ac04743cb26121", - "https://deno.land/std@0.208.0/path/posix/mod.ts": "f1b08a7f64294b7de87fc37190d63b6ce5b02889af9290c9703afe01951360ae", - "https://deno.land/std@0.208.0/path/posix/normalize.ts": "11de90a94ab7148cc46e5a288f7d732aade1d616bc8c862f5560fa18ff987b4b", - "https://deno.land/std@0.208.0/path/posix/normalize_glob.ts": "10a1840c628ebbab679254d5fa1c20e59106102354fb648a1765aed72eb9f3f9", - "https://deno.land/std@0.208.0/path/posix/parse.ts": "199208f373dd93a792e9c585352bfc73a6293411bed6da6d3bc4f4ef90b04c8e", - "https://deno.land/std@0.208.0/path/posix/relative.ts": "e2f230608b0f083e6deaa06e063943e5accb3320c28aef8d87528fbb7fe6504c", - "https://deno.land/std@0.208.0/path/posix/resolve.ts": "51579d83159d5c719518c9ae50812a63959bbcb7561d79acbdb2c3682236e285", - "https://deno.land/std@0.208.0/path/posix/separator.ts": "0b6573b5f3269a3164d8edc9cefc33a02dd51003731c561008c8bb60220ebac1", - "https://deno.land/std@0.208.0/path/posix/to_file_url.ts": "08d43ea839ee75e9b8b1538376cfe95911070a655cd312bc9a00f88ef14967b6", - "https://deno.land/std@0.208.0/path/posix/to_namespaced_path.ts": "c9228a0e74fd37e76622cd7b142b8416663a9b87db643302fa0926b5a5c83bdc", - "https://deno.land/std@0.208.0/path/relative.ts": "23d45ede8b7ac464a8299663a43488aad6b561414e7cbbe4790775590db6349c", - "https://deno.land/std@0.208.0/path/resolve.ts": "5b184efc87155a0af9fa305ff68a109e28de9aee81fc3e77cd01380f19daf867", - "https://deno.land/std@0.208.0/path/separator.ts": "40a3e9a4ad10bef23bc2cd6c610291b6c502a06237c2c4cd034a15ca78dedc1f", - "https://deno.land/std@0.208.0/path/to_file_url.ts": "edaafa089e0bce386e1b2d47afe7c72e379ff93b28a5829a5885e4b6c626d864", - "https://deno.land/std@0.208.0/path/to_namespaced_path.ts": "cf8734848aac3c7527d1689d2adf82132b1618eff3cc523a775068847416b22a", - "https://deno.land/std@0.208.0/path/windows/_util.ts": "f32b9444554c8863b9b4814025c700492a2b57ff2369d015360970a1b1099d54", - "https://deno.land/std@0.208.0/path/windows/basename.ts": "8a9dbf7353d50afbc5b221af36c02a72c2d1b2b5b9f7c65bf6a5a2a0baf88ad3", - "https://deno.land/std@0.208.0/path/windows/common.ts": "e781d395dc76f6282e3f7dd8de13194abb8b04a82d109593141abc6e95755c8b", - "https://deno.land/std@0.208.0/path/windows/dirname.ts": "5c2aa541384bf0bd9aca821275d2a8690e8238fa846198ef5c7515ce31a01a94", - "https://deno.land/std@0.208.0/path/windows/extname.ts": "07f4fa1b40d06a827446b3e3bcc8d619c5546b079b8ed0c77040bbef716c7614", - "https://deno.land/std@0.208.0/path/windows/format.ts": "343019130d78f172a5c49fdc7e64686a7faf41553268961e7b6c92a6d6548edf", - "https://deno.land/std@0.208.0/path/windows/from_file_url.ts": "d53335c12b0725893d768be3ac6bf0112cc5b639d2deb0171b35988493b46199", - "https://deno.land/std@0.208.0/path/windows/glob_to_regexp.ts": "290755e18ec6c1a4f4d711c3390537358e8e3179581e66261a0cf348b1a13395", - "https://deno.land/std@0.208.0/path/windows/is_absolute.ts": "245b56b5f355ede8664bd7f080c910a97e2169972d23075554ae14d73722c53c", - "https://deno.land/std@0.208.0/path/windows/is_glob.ts": "ec4fbc604b9db8487f7b56ab0e759b24a971ab6a45f7b0b698bc39b8b9f9680f", - "https://deno.land/std@0.208.0/path/windows/join.ts": "e6600bf88edeeef4e2276e155b8de1d5dec0435fd526ba2dc4d37986b2882f16", - "https://deno.land/std@0.208.0/path/windows/join_globs.ts": "f4838d54b1f60a34a40625a3293f6e583135348be1b2974341ac04743cb26121", - "https://deno.land/std@0.208.0/path/windows/mod.ts": "d7040f461465c2c21c1c68fc988ef0bdddd499912138cde3abf6ad60c7fb3814", - "https://deno.land/std@0.208.0/path/windows/normalize.ts": "9deebbf40c81ef540b7b945d4ccd7a6a2c5a5992f791e6d3377043031e164e69", - "https://deno.land/std@0.208.0/path/windows/normalize_glob.ts": "344ff5ed45430495b9a3d695567291e50e00b1b3b04ea56712a2acf07ab5c128", - "https://deno.land/std@0.208.0/path/windows/parse.ts": "120faf778fe1f22056f33ded069b68e12447668fcfa19540c0129561428d3ae5", - "https://deno.land/std@0.208.0/path/windows/relative.ts": "026855cd2c36c8f28f1df3c6fbd8f2449a2aa21f48797a74700c5d872b86d649", - "https://deno.land/std@0.208.0/path/windows/resolve.ts": "5ff441ab18a2346abadf778121128ee71bda4d0898513d4639a6ca04edca366b", - "https://deno.land/std@0.208.0/path/windows/separator.ts": "ae21f27015f10510ed1ac4a0ba9c4c9c967cbdd9d9e776a3e4967553c397bd5d", - "https://deno.land/std@0.208.0/path/windows/to_file_url.ts": "8e9ea9e1ff364aa06fa72999204229952d0a279dbb876b7b838b2b2fea55cce3", - "https://deno.land/std@0.208.0/path/windows/to_namespaced_path.ts": "e0f4d4a5e77f28a5708c1a33ff24360f35637ba6d8f103d19661255ef7bfd50d", - "https://deno.land/std@0.213.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", - "https://deno.land/std@0.213.0/assert/_diff.ts": "dcc63d94ca289aec80644030cf88ccbf7acaa6fbd7b0f22add93616b36593840", - "https://deno.land/std@0.213.0/assert/_format.ts": "0ba808961bf678437fb486b56405b6fefad2cf87b5809667c781ddee8c32aff4", - "https://deno.land/std@0.213.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", - "https://deno.land/std@0.213.0/assert/assert_almost_equals.ts": "8b96b7385cc117668b0720115eb6ee73d04c9bcb2f5d2344d674918c9113688f", - "https://deno.land/std@0.213.0/assert/assert_array_includes.ts": "1688d76317fd45b7e93ef9e2765f112fdf2b7c9821016cdfb380b9445374aed1", - "https://deno.land/std@0.213.0/assert/assert_equals.ts": "4497c56fe7d2993b0d447926702802fc0becb44e319079e8eca39b482ee01b4e", - "https://deno.land/std@0.213.0/assert/assert_exists.ts": "24a7bf965e634f909242cd09fbaf38bde6b791128ece08e33ab08586a7cc55c9", - "https://deno.land/std@0.213.0/assert/assert_false.ts": "6f382568e5128c0f855e5f7dbda8624c1ed9af4fcc33ef4a9afeeedcdce99769", - "https://deno.land/std@0.213.0/assert/assert_greater.ts": "4945cf5729f1a38874d7e589e0fe5cc5cd5abe5573ca2ddca9d3791aa891856c", - "https://deno.land/std@0.213.0/assert/assert_greater_or_equal.ts": "573ed8823283b8d94b7443eb69a849a3c369a8eb9666b2d1db50c33763a5d219", - "https://deno.land/std@0.213.0/assert/assert_instance_of.ts": "72dc1faff1e248692d873c89382fa1579dd7b53b56d52f37f9874a75b11ba444", - "https://deno.land/std@0.213.0/assert/assert_is_error.ts": "6596f2b5ba89ba2fe9b074f75e9318cda97a2381e59d476812e30077fbdb6ed2", - "https://deno.land/std@0.213.0/assert/assert_less.ts": "2b4b3fe7910f65f7be52212f19c3977ecb8ba5b2d6d0a296c83cde42920bb005", - "https://deno.land/std@0.213.0/assert/assert_less_or_equal.ts": "b93d212fe669fbde959e35b3437ac9a4468f2e6b77377e7b6ea2cfdd825d38a0", - "https://deno.land/std@0.213.0/assert/assert_match.ts": "ec2d9680ed3e7b9746ec57ec923a17eef6d476202f339ad91d22277d7f1d16e1", - "https://deno.land/std@0.213.0/assert/assert_not_equals.ts": "f3edda73043bc2c9fae6cbfaa957d5c69bbe76f5291a5b0466ed132c8789df4c", - "https://deno.land/std@0.213.0/assert/assert_not_instance_of.ts": "8f720d92d83775c40b2542a8d76c60c2d4aeddaf8713c8d11df8984af2604931", - "https://deno.land/std@0.213.0/assert/assert_not_match.ts": "b4b7c77f146963e2b673c1ce4846473703409eb93f5ab0eb60f6e6f8aeffe39f", - "https://deno.land/std@0.213.0/assert/assert_not_strict_equals.ts": "da0b8ab60a45d5a9371088378e5313f624799470c3b54c76e8b8abeec40a77be", - "https://deno.land/std@0.213.0/assert/assert_object_match.ts": "e85e5eef62a56ce364c3afdd27978ccab979288a3e772e6855c270a7b118fa49", - "https://deno.land/std@0.213.0/assert/assert_rejects.ts": "e9e0c8d9c3e164c7ac962c37b3be50577c5a2010db107ed272c4c1afb1269f54", - "https://deno.land/std@0.213.0/assert/assert_strict_equals.ts": "0425a98f70badccb151644c902384c12771a93e65f8ff610244b8147b03a2366", - "https://deno.land/std@0.213.0/assert/assert_string_includes.ts": "dfb072a890167146f8e5bdd6fde887ce4657098e9f71f12716ef37f35fb6f4a7", - "https://deno.land/std@0.213.0/assert/assert_throws.ts": "edddd86b39606c342164b49ad88dd39a26e72a26655e07545d172f164b617fa7", - "https://deno.land/std@0.213.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8", - "https://deno.land/std@0.213.0/assert/equal.ts": "fae5e8a52a11d3ac694bbe1a53e13a7969e3f60791262312e91a3e741ae519e2", - "https://deno.land/std@0.213.0/assert/fail.ts": "f310e51992bac8e54f5fd8e44d098638434b2edb802383690e0d7a9be1979f1c", - "https://deno.land/std@0.213.0/assert/mod.ts": "325df8c0683ad83a873b9691aa66b812d6275fc9fec0b2d180ac68a2c5efed3b", - "https://deno.land/std@0.213.0/assert/unimplemented.ts": "47ca67d1c6dc53abd0bd729b71a31e0825fc452dbcd4fde4ca06789d5644e7fd", - "https://deno.land/std@0.213.0/assert/unreachable.ts": "38cfecb95d8b06906022d2f9474794fca4161a994f83354fd079cac9032b5145", - "https://deno.land/std@0.213.0/async/delay.ts": "8e1d18fe8b28ff95885e2bc54eccec1713f57f756053576d8228e6ca110793ad", - "https://deno.land/std@0.213.0/fmt/colors.ts": "aeaee795471b56fc62a3cb2e174ed33e91551b535f44677f6320336aabb54fbb", - "https://deno.land/std@0.213.0/http/server.ts": "6dce295abc169d0956ae00432441331b3425afad4d79e8b3475739be2f04d614", - "https://deno.land/std@0.213.0/http/status.ts": "ed61b4882af2514a81aefd3245e8df4c47b9a8e54929a903577643d2d1ebf514", "https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", "https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293", @@ -1899,91 +31,116 @@ "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/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", - "https://deno.land/x/opine@2.1.2/src/utils/mergeDescriptors.ts": "1fe498d4a1a8dcfd3570f9ca5e0647590d86d029b3c340bfcfdb57002851e41b", - "https://deno.land/x/opine@2.3.4/src/methods.ts": "0481daecc6068d24e9e5391818baddf555ab803d39a465dcd259161f8bd8ee49", - "https://deno.land/x/opine@2.3.4/src/utils/mergeDescriptors.ts": "1fe498d4a1a8dcfd3570f9ca5e0647590d86d029b3c340bfcfdb57002851e41b", - "https://deno.land/x/superdeno@4.8.0/deps.ts": "e2cf96d1129c38e1daf87a405878fa51d5771a0ba9b1c250baa75db0a8cb99df", - "https://deno.land/x/superdeno@4.8.0/mod.ts": "fa91c501867a4302a4bc92d63cbf934fe5475ebb7bf58335338e001147263c87", - "https://deno.land/x/superdeno@4.8.0/src/close.ts": "8bd4ab602ebbb048d06697d0c48c30be5f78ab9ad673850965e8014d78cca7a8", - "https://deno.land/x/superdeno@4.8.0/src/superagent.ts": "8f60187f9278b154ef6bccf09a5ff7d45f81103ad0ce02d45518a6bbe63ce764", - "https://deno.land/x/superdeno@4.8.0/src/superdeno.ts": "2e2cd4898961ac7688f0c2a4b210bf560a338f6601bd231d74bf8a0956880311", - "https://deno.land/x/superdeno@4.8.0/src/test.ts": "89386c2c207179cd1854baf40f37cd1c4a5d312f4605d8cdc4d495ff3fd14d10", - "https://deno.land/x/superdeno@4.8.0/src/types.ts": "0fc4a0a1acf4c3acba3e3d956a9e8917ce32cd47dbb8e75ffadec21a43a8e52f", - "https://deno.land/x/superdeno@4.8.0/src/utils.ts": "458c1699f73e348745b9b4d081e005dbc12d050fd2899ebf4f919515823ba1c0", - "https://deno.land/x/superdeno@4.8.0/src/xhrSham.js": "91f28215c07af47155c7abe711a097d67d89916a80d666ef6d26fab3e5179af2", - "https://deno.land/x/superdeno@4.8.0/version.ts": "c84185c6cf0c198d406ae8892cc56a981002cce5d09e9b6293718bf02ad1d2c4", - "https://deno.land/x/superdeno@4.9.0/deps.ts": "acb88a5969aae0bcc82e053cb433cd183a10cc656495caa634b6e22a79156c4e", - "https://deno.land/x/superdeno@4.9.0/mod.ts": "fa91c501867a4302a4bc92d63cbf934fe5475ebb7bf58335338e001147263c87", - "https://deno.land/x/superdeno@4.9.0/src/close.ts": "8bd4ab602ebbb048d06697d0c48c30be5f78ab9ad673850965e8014d78cca7a8", - "https://deno.land/x/superdeno@4.9.0/src/superagent.ts": "8f60187f9278b154ef6bccf09a5ff7d45f81103ad0ce02d45518a6bbe63ce764", - "https://deno.land/x/superdeno@4.9.0/src/superdeno.ts": "2e2cd4898961ac7688f0c2a4b210bf560a338f6601bd231d74bf8a0956880311", - "https://deno.land/x/superdeno@4.9.0/src/test.ts": "1ab3c8c98160af8c3b30e097809d5c57bdd38d7b42c703f3f170f8452ad06c0f", - "https://deno.land/x/superdeno@4.9.0/src/types.ts": "9a48cdfafad3cea2212e1be29cdd2055e7d3d467437c9048012797323335abbb", - "https://deno.land/x/superdeno@4.9.0/src/utils.ts": "09a2e65cc5cc2a261b885f0e66ee84e96e978181975a0728636d20e48b67bd89", - "https://deno.land/x/superdeno@4.9.0/src/xhrSham.js": "6a35aed77bbe98324fe3b4d7430463b7cd6d3b43445ffdccd1fc327dc59dd3c6", - "https://deno.land/x/superdeno@4.9.0/version.ts": "4f8ba8f2a6b201e8e96818d3ab5c43aef1db751523c4b79160500664b72f87de", - "https://deno.land/x/superoak@4.7.0/deps.ts": "814a22b7182bff7ec6457f753f186ce6c12453759581d711359f76ea14f7caa1", - "https://deno.land/x/superoak@4.7.0/mod.ts": "6d4ea8a5a48c9007f2e947934889c06259d3ebb5569515bcb0432036a22449cd", - "https://deno.land/x/superoak@4.7.0/src/superoak.ts": "9c08a3211c4d1f7bb89e88fc3f242536fce654c157aa6db52d3c24f033bb3d28", - "https://deno.land/x/superoak@4.7.0/version.ts": "7f24547dd88673cc8521f5aed9bf0e1634ba4170707a792ddb6f95d0e5ec8a8c", - "https://deno.land/x/superoak@4.8.0/deps.ts": "d716c0b36fdac6458f6984ce80f69d0b645c7e0ac8461024a40ead5ed3fcd08d", - "https://deno.land/x/superoak@4.8.0/mod.ts": "6d4ea8a5a48c9007f2e947934889c06259d3ebb5569515bcb0432036a22449cd", - "https://deno.land/x/superoak@4.8.0/src/superoak.ts": "9c08a3211c4d1f7bb89e88fc3f242536fce654c157aa6db52d3c24f033bb3d28", - "https://deno.land/x/superoak@4.8.0/version.ts": "04c6f6264eee938902c17d82f3f05b288e5e21e5d0bf8edab09d594c5031122d", - "https://jspm.dev/npm:call-bind@1.0.5!cjs": "09f8399c727fc1e9d58fdafc0a729b45bf37b7ee0c11d9d0b39abe37ac42ccf5", - "https://jspm.dev/npm:call-bind@1.0.5/callBound!cjs": "55fa05e2b115eeaef9ff684e3df12de253e6644a40ad09b5722f3a9a8df8f645", - "https://jspm.dev/npm:call-bind@1/callBound!cjs": "9cf2ef160025d392767618c2f0cb72d32cf14caa3fbeb493c6df9bde9d7fca8d", - "https://jspm.dev/npm:component-emitter@1!cjs": "26c2994a5fcac1cd9156b00be96c5e2f006dd76338095a96006ac3a47c6c327d", - "https://jspm.dev/npm:component-emitter@1.3.0!cjs": "757cafefb0bf5639f3f90b2267a7d168e03631e731c2a79fca847b735695e196", - "https://jspm.dev/npm:define-data-property@1!cjs": "37b65cb06c826730306a5f766de69da37b96076c96ea11a47667e9429623f937", - "https://jspm.dev/npm:define-data-property@1.1.1!cjs": "4ac6fa4b9d7ba7ccc83ffa350c58112ee878a450a97375217f66508d5673c822", - "https://jspm.dev/npm:fast-safe-stringify@2!cjs": "d8dd0803af23f037ffb44c13e18333131af27ebe582f55fd498b6e3c8f6d5a9d", - "https://jspm.dev/npm:fast-safe-stringify@2.1.1!cjs": "8a14a2de8a07a719c74aa63ffa5ff635fc55e9ee5d5a79fbc2e087dc4aa1940e", - "https://jspm.dev/npm:function-bind@1!cjs": "73fbc50bf85e8a6ca150609e98c396301c1ae5a1603e50ce8c64e95f646e0ce0", - "https://jspm.dev/npm:function-bind@1.1.2!cjs": "bbb663bc4e50f400a8ca0de9e0bfdaaa7022695f86b2806a48dc1afc5b4195a8", - "https://jspm.dev/npm:function-bind@1.1.2/implementation!cjs": "ebdc0ec85854db19d7e21081b368891394f86e21c6d786273c327762cb46ea6a", - "https://jspm.dev/npm:get-intrinsic@1!cjs": "f6d9266edc586632e8f6d8d6c5ca28fb2c0d5ee9c9d9252df9aafd57eda9fcea", - "https://jspm.dev/npm:get-intrinsic@1.2.2!cjs": "723fcebc493a45d5af8ecb366020a6cc2ce9bd4759bad699c1172015cb193f65", - "https://jspm.dev/npm:gopd@1!cjs": "c220469947b77de2c5e4b115beda16397bf6133c5b873b8e24e85b902ee6dc82", - "https://jspm.dev/npm:gopd@1.0.1!cjs": "b38da4f4b49cfef31e3aa8d62fdd136cf0fe99a5df6c603a426f97248f3cf4ab", - "https://jspm.dev/npm:has-property-descriptors@1!cjs": "b1a828f75a22a5614b136dd3da1be98cc744a2cd6bfed9bd8c338a8d51a570d1", - "https://jspm.dev/npm:has-property-descriptors@1.0.1!cjs": "f8da64823507f597f3cb44a2f3576e350df72e1033ef5e7a5b30d771e81c0819", - "https://jspm.dev/npm:has-proto@1!cjs": "78a2914e5525d531426c5d69fd5aa23671ec359c6c527b9791327f60ad1b6682", - "https://jspm.dev/npm:has-proto@1.0.1!cjs": "0a9d605f1d310f859265780011d6343a7869cadf3a9e02fd6cc949c2924b528c", - "https://jspm.dev/npm:has-symbols@1!cjs": "48faf647d225b64fa235ccc3e5a848e72221b0230935e421066a5de39aa89c3a", - "https://jspm.dev/npm:has-symbols@1.0.3!cjs": "36965f84e4e0ea1abeddb6928d0719a2648e61ceb9825df185b40d05cddb64df", - "https://jspm.dev/npm:has-symbols@1.0.3/shams!cjs": "669673e1dc7691c0b397580760121d57f3a5c5101dd70be2e8dd7d2a044de2e9", - "https://jspm.dev/npm:hasown@2!cjs": "9a39af846b167cae93b7a40f1ba4c97255bb5b07a1481da853a29bb68d24e603", - "https://jspm.dev/npm:hasown@2.0.0!cjs": "f52fd2477e345530f759465a984023f23d8261c4a54970e619daf1da6a2e85f5", - "https://jspm.dev/npm:object-inspect@1!cjs": "dc197b471ed55ecf2eabeb8da9aaee277e97831e65192531432a4ec2346211d9", - "https://jspm.dev/npm:object-inspect@1.13.1!cjs": "cec116e5c2b7d6b75e178d2541d70475d716ad912e3d5599e5c2d97284a9cb3e", - "https://jspm.dev/npm:qs@6!cjs": "210de1e090ac836c2495c19dfea88fc74b49de1b308241f8c9490d27ab6e0195", - "https://jspm.dev/npm:qs@6.11.2!cjs": "5da52fff60f7b1a6b1c73cdea2d9fc5d5588fa6c551b2a0ea2a1ebbb2a5e559f", - "https://jspm.dev/npm:qs@6.11.2/_/e71c21de.js": "cfe49eb949fb7291803f1ed2f4c0a244b8fca3b6936f5082fc97581a0663e427", - "https://jspm.dev/npm:qs@6.11.2/lib/stringify!cjs": "35d39c5871af151efe9ccca8e4ebecbf0282f97287b5fad56ebac369f69c2581", - "https://jspm.dev/npm:set-function-length@1!cjs": "b4c766d874ba261ff0c11aa18a6bf4510ecf8da09a7219da83a62772e0bc1b41", - "https://jspm.dev/npm:set-function-length@1.1.1!cjs": "f52607660d1f50e19e645ab49e6a4adf27fa4ae909867ec9950e993c430e4ca1", - "https://jspm.dev/npm:side-channel@1!cjs": "a07dfe7165af0d7f916d089490c38839397abcd8b218e4566b270858c9a0ea04", - "https://jspm.dev/npm:side-channel@1.0.4!cjs": "db65b31b6f9e67d57f04e26d71eb5b376306f5a89ab46fae1278c3ffefb19663", - "https://jspm.dev/npm:superagent@6.1.0!cjs": "fcf1c0b17cb3ff899b59ae178fc4ab74ad3b592d7fa8b44b16394001758e3176", - "https://jspm.dev/npm:superagent@6.1.0/lib/agent-base!cjs": "cfe465965a55d80114d835143717413945d0bbc46355d0f7f8200a89902ed006", - "https://jspm.dev/npm:superagent@6.1.0/lib/is-object!cjs": "95f67ff49b42fd5e82114b9d54a4b3fe1ac98813aed7ceaf53d314983f59820a", - "https://jspm.dev/npm:superagent@6.1.0/lib/request-base!cjs": "e361c341aa75d7417c918bc8fb697d0ccf96101e039dd2f00e5e45c01c534caa", - "https://jspm.dev/npm:superagent@6.1.0/lib/response-base!cjs": "00ac549f34d73c2753caa798aa7eb781051179013e3418ff0868a1e1904a8913", - "https://jspm.dev/npm:superagent@6.1.0/lib/utils!cjs": "ea706523553983c96ef4ab2f191c61c53fb8b78ad8ff2472b48f1385e896c030", - "https://jspm.dev/superagent@6.1.0": "4b3082d71252c42abd3930d85d1f3c4b2e937e0fab2b5f1c9d19eac20dea89a9" + "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" }, "workspace": { "dependencies": [ "jsr:@hono/hono@^4.8.5", "npm:@hono/zod-openapi@^1.0.2", "npm:@scalar/openapi-to-markdown@~0.2.23" - ] + ], + "packageJson": { + "dependencies": [ + "npm:@typescript-eslint/parser@^8.38.0", + "npm:eslint@^9.32.0", + "npm:quadstore@^15.4.0-beta.0" + ] + } } } 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/docs/error-handling-usage.md b/flow-core/docs/error-handling-usage.md new file mode 100644 index 0000000..1b74cb3 --- /dev/null +++ b/flow-core/docs/error-handling-usage.md @@ -0,0 +1,422 @@ +# Error Handling with LogContext Integration + +This document describes how to use the enhanced error handling system in the Semantic Flow platform, which provides structured error reporting with comprehensive LogContext integration. + +## Overview + +The enhanced error handling system provides two main functions: +- `handleError(error, message, context)` - For handling Error objects +- `handleCaughtError(error, message, context)` - For handling any thrown value (errors, strings, objects, etc.) + +Both functions accept an optional LogContext parameter that provides structured context information for better error tracking and debugging. + +## Basic Usage + +### Simple Error Handling + +```typescript +import { handleError, handleCaughtError } from '@flow/core/utils/logger'; + +// Handle a known Error object +const error = new Error('Database connection failed'); +await handleError(error, 'Failed to connect to database'); + +// Handle any caught error +try { + // some operation +} catch (e) { + await handleCaughtError(e, 'Operation failed'); +} +``` + +### Error Handling with LogContext + +```typescript +import { handleError, handleCaughtError, type LogContext } from '@flow/core/utils/logger'; + +// Define context for the operation +const context: LogContext = { + operation: 'startup', + component: 'database-connector', + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '1.0.0' + }, + performanceMetrics: { + duration: 5000 + }, + errorContext: { + errorCode: 'DB_CONNECTION_TIMEOUT' + }, + metadata: { + connectionString: 'postgresql://localhost:5432/flow', + retryAttempts: 3 + } +}; + +try { + await connectToDatabase(); +} catch (e) { + await handleCaughtError(e, 'Database connection failed during startup', context); +} +``` + +## LogContext Structure + +The LogContext interface provides comprehensive context information: + +```typescript +interface LogContext { + /** Operation being performed (e.g., 'startup', 'config-resolve', 'api-request') */ + operation?: string; + + /** Unique identifier for tracking related log entries */ + operationId?: string; + + /** Component or module generating the log entry */ + component?: string; + + /** Performance metrics for operation timing */ + performanceMetrics?: { + startTime?: number; + duration?: number; + memoryUsage?: number; + }; + + /** Service-specific context */ + serviceContext?: { + serviceName?: string; + serviceVersion?: string; + environment?: string; + instanceId?: string; + }; + + /** Configuration context when dealing with config operations */ + configContext?: { + configPath?: string; + configType?: string; + validationStage?: string; + }; + + /** Error context when logging errors */ + errorContext?: { + errorType?: string; + errorCode?: string; + stackTrace?: string; + originalError?: Error; + }; + + /** API request context for HTTP operations */ + apiContext?: { + requestId?: string; + method?: string; + path?: string; + statusCode?: number; + userAgent?: string; + ip?: string; + }; + + /** Additional arbitrary metadata */ + metadata?: Record; +} +``` + +## Common Usage Patterns + +### Startup Error Handling + +```typescript +const context: LogContext = { + operation: 'startup', + component: 'service-initialization', + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '1.0.0' + } +}; + +try { + await initializeService(); +} catch (e) { + await handleCaughtError(e, 'Service initialization failed', context); + process.exit(1); +} +``` + +### Configuration Error Handling + +```typescript +const context: LogContext = { + operation: 'config-resolve', + component: 'config-loader', + configContext: { + configPath: '/app/config.json', + configType: 'file' + }, + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '1.0.0' + } +}; + +try { + const config = await loadConfiguration(); +} catch (e) { + await handleCaughtError(e, 'Failed to load configuration', context); +} +``` + +### API Request Error Handling + +```typescript +const context: LogContext = { + operation: 'api-request', + component: 'api-handler', + operationId: requestId, + apiContext: { + requestId, + method: req.method, + path: req.url, + statusCode: 500, + userAgent: req.headers['user-agent'] + }, + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '1.0.0' + }, + performanceMetrics: { + duration: Date.now() - startTime + } +}; + +try { + const result = await processApiRequest(req); + res.json(result); +} catch (e) { + await handleCaughtError(e, 'API request processing failed', context); + res.status(500).json({ error: 'Internal server error' }); +} +``` + +### Mesh Processing Error Handling + +```typescript +const context: LogContext = { + operation: 'scan', + component: 'mesh-scanner', + operationId: `scan-${Date.now()}`, + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '1.0.0' + }, + performanceMetrics: { + startTime: Date.now() + }, + metadata: { + meshPath: '/data/meshes', + scanDepth: 'recursive' + } +}; + +try { + const meshes = await scanMeshDirectory('/data/meshes'); +} catch (e) { + await handleCaughtError(e, 'Mesh scanning failed', { + ...context, + performanceMetrics: { + ...context.performanceMetrics, + duration: Date.now() - context.performanceMetrics!.startTime! + } + }); +} +``` + +## Error Types and Handling + +### Custom Flow Service Errors + +```typescript +import { FlowServiceError, ValidationError, ConfigurationError } from '@flow/service/utils/errors'; + +// These errors provide additional structured information +try { + throw new ValidationError('Invalid field value', 'port', { + providedValue: 'invalid', + expectedType: 'number' + }); +} catch (e) { + const context: LogContext = { + operation: 'config-validate', + component: 'validator', + errorContext: { + errorCode: 'FIELD_VALIDATION_ERROR' + } + }; + await handleCaughtError(e, 'Configuration validation failed', context); +} +``` + +### JavaScript Built-in Errors + +```typescript +// Standard JavaScript errors are handled gracefully +try { + JSON.parse('invalid json'); +} catch (e) { + const context: LogContext = { + operation: 'config-parse', + component: 'json-parser', + errorContext: { + errorType: 'SyntaxError' + } + }; + await handleCaughtError(e, 'JSON parsing failed', context); +} +``` + +### Non-Error Values + +```typescript +// The system handles thrown strings, numbers, objects, etc. +try { + throw "Something went wrong!"; +} catch (e) { + const context: LogContext = { + operation: 'unknown', + component: 'string-error-handler' + }; + await handleCaughtError(e, 'String error caught', context); +} +``` + +## Context Merging + +LogContext objects can be merged to combine different sources of context: + +```typescript +import { mergeLogContext } from '@flow/core/utils/logger'; + +const baseContext: LogContext = { + operation: 'weave', + component: 'weave-processor', + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '1.0.0' + } +}; + +const operationContext: LogContext = { + operationId: 'weave-123', + performanceMetrics: { + startTime: Date.now() + } +}; + +const mergedContext = mergeLogContext(baseContext, operationContext); + +try { + await processWeaveOperation(); +} catch (e) { + await handleCaughtError(e, 'Weave processing failed', mergedContext); +} +``` + +## Best Practices + +### 1. Always Provide Operation Context +```typescript +// Good +const context: LogContext = { + operation: 'config-load', + component: 'config-loader' +}; + +// Avoid +const context: LogContext = { + metadata: { someData: true } +}; +``` + +### 2. Include Performance Metrics for Long Operations +```typescript +const startTime = Date.now(); +try { + await longRunningOperation(); +} catch (e) { + const context: LogContext = { + operation: 'data-processing', + component: 'processor', + performanceMetrics: { + duration: Date.now() - startTime + } + }; + await handleCaughtError(e, 'Processing failed', context); +} +``` + +### 3. Use Unique Operation IDs for Tracking +```typescript +const operationId = `scan-${Date.now()}-${Math.random()}`; +const context: LogContext = { + operation: 'scan', + operationId, + component: 'mesh-scanner' +}; +``` + +### 4. Include Relevant Request Context for API Operations +```typescript +const context: LogContext = { + operation: 'api-request', + component: 'api-handler', + apiContext: { + requestId: req.id, + method: req.method, + path: req.path, + userAgent: req.get('User-Agent') + } +}; +``` + +### 5. Add Debugging Metadata +```typescript +const context: LogContext = { + operation: 'mesh-process', + component: 'mesh-processor', + metadata: { + meshCount: meshes.length, + processingMode: 'parallel', + memoryBefore: process.memoryUsage().heapUsed + } +}; +``` + +## Integration with Logging and Monitoring + +The error handling system integrates with: + +- **Console Logging**: Formatted error messages with context +- **File Logging**: Structured JSON logs with full context +- **Sentry Integration**: Automatic error reporting with context as tags +- **Performance Monitoring**: Duration and memory usage tracking + +## Error Handler Configuration + +Error handlers can be configured through logger configuration: + +```typescript +import { createEnhancedLogger } from '@flow/core/utils/logger'; + +const logger = createEnhancedLogger({ + enableConsole: true, + enableFile: true, + enableSentry: true, + serviceContext: { + serviceName: 'my-service', + serviceVersion: '1.0.0', + environment: 'production' + } +}); +``` + +This ensures all error handling uses the configured service context and integrates with the appropriate logging destinations (console, file, Sentry). diff --git a/flow-core/src/deps.ts b/flow-core/src/deps.ts index 89051b2..098869f 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -12,3 +12,29 @@ 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'; + +// Sentry for error reporting and logging +export * as Sentry from 'npm:@sentry/deno'; 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/logger/error-handlers.ts b/flow-core/src/utils/logger/error-handlers.ts new file mode 100644 index 0000000..2a3362a --- /dev/null +++ b/flow-core/src/utils/logger/error-handlers.ts @@ -0,0 +1,466 @@ +/** + * @fileoverview Enhanced error handling utilities with LogContext integration. + * Provides comprehensive error handling with structured logging context for + * better error tracking and debugging across the platform. + */ + +import type { LogContext, ErrorHandlingOptions } from './types.ts'; +import { createEnhancedLogger } from './structured-logger.ts'; +import { mergeLogContext } from './formatters.ts'; + +/** + * Default logger instance for error handling + */ +const errorLogger = createEnhancedLogger(); + +/** + * Enhanced error handler that accepts LogContext for structured error reporting. + * Replaces the basic handleError function with LogContext-aware error handling. + * + * @param error - The error to handle (unknown type for maximum flexibility) + * @param context - Optional context string describing where the error occurred + * @param logContext - Optional LogContext with structured information about the operation + * @param meta - Optional additional metadata + * @param options - Optional error handling options + */ +export async function handleError( + error: unknown, + context?: string, + logContext?: LogContext, + meta?: Record, + options?: ErrorHandlingOptions, +): Promise { + try { + const contextMessage = context ? `${context}: ` : ''; + + // Extract error information for structured context + const errorContext = error instanceof Error ? { + errorType: error.constructor.name, + errorCode: (error as any).code, + stackTrace: error.stack, + originalError: error + } : { + errorType: typeof error, + }; + + // Create comprehensive error context + const enhancedContext: LogContext = mergeLogContext( + logContext, + { + operation: logContext?.operation || 'error-handling', + errorContext, + metadata: { + ...meta, + errorValue: error instanceof Error ? error.message : String(error), + }, + }, + ); + + if (error instanceof Error) { + await errorLogger.error( + `${contextMessage}${error.message}`, + error, + enhancedContext, + ); + } else { + await errorLogger.error( + `${contextMessage}Unknown error occurred`, + undefined, + enhancedContext, + ); + } + + // Handle additional options if provided + if (options?.rethrow) { + throw error; + } + } catch (loggingError) { + // Fallback error handling if logging itself fails + console.error('Failed to log error properly:', loggingError); + console.error('Original error was:', error); + + if (options?.rethrow) { + throw error; + } + } +} + +/** + * Comprehensive error handler following weave application pattern with LogContext support. + * Handles different error types with detailed logging including stack traces, causes, and context. + * Enhanced version of handleCaughtError with LogContext integration. + * + * @param e - The caught error of unknown type + * @param customMessage - Optional custom message to prefix error details + * @param logContext - Optional LogContext with structured information about the operation + * @param options - Optional error handling options + */ +export async function handleCaughtError( + e: unknown, + customMessage?: string, + logContext?: LogContext, + options?: ErrorHandlingOptions, +): Promise { + try { + // Create base context for error handling + const baseContext: LogContext = mergeLogContext( + logContext, + { + operation: logContext?.operation || 'error-handling', + metadata: { customMessage }, + }, + ); + + // Format error message with context if provided + const formatErrorMsg = (errorType: string, msg: string) => { + if (customMessage) { + return `${customMessage} ${errorType}: ${msg}`; + } + return `${errorType}: ${msg}`; + }; + + // Log detailed error information with enhanced context + const logDetailedError = async (error: Error, type = 'Error') => { + const errorContext = { + errorType: error.constructor.name, + errorCode: (error as any).code, + stackTrace: error.stack, + originalError: error + }; + + const enhancedContext: LogContext = mergeLogContext( + baseContext, + { + errorContext, + metadata: { + ...baseContext.metadata, + errorName: error.name, + errorMessage: error.message, + }, + }, + ); + + await errorLogger.error( + formatErrorMsg(type, error.message), + error, + enhancedContext, + ); + + // Log stack trace if available + if (error.stack) { + await errorLogger.debug('Stack trace:', { + ...baseContext, + metadata: { + ...baseContext.metadata, + stackTrace: error.stack + }, + }); + } + + // Log cause if available + if (error.cause) { + await errorLogger.debug('Caused by:', { + ...baseContext, + metadata: { + ...baseContext.metadata, + errorCause: Deno.inspect(error.cause, { colors: false }) + }, + }); + } + + // Log additional error details + await errorLogger.debug('Error details:', { + ...baseContext, + metadata: { + ...baseContext.metadata, + errorDetails: Deno.inspect(error, { colors: false }) + }, + }); + }; + + // Handle specific error types with enhanced context + if (e instanceof Error) { + // Check for custom Flow Service error types first + if (e.name === 'FlowServiceError') { + const fsError = e as Error & { code?: string; context?: unknown }; + const errorContext = { + errorType: 'FlowServiceError', + errorCode: fsError.code, + stackTrace: e.stack, + originalError: e + }; + + await errorLogger.error( + formatErrorMsg('FlowServiceError', e.message), + e, + mergeLogContext(baseContext, { + errorContext, + metadata: { + ...baseContext.metadata, + serviceErrorContext: fsError.context, + }, + }), + ); + + if (e.stack) { + await errorLogger.debug('FlowServiceError stack trace:', { + ...baseContext, + metadata: { + ...baseContext.metadata, + stackTrace: e.stack + }, + }); + } + + if (fsError.context) { + await errorLogger.debug('FlowServiceError context:', { + ...baseContext, + metadata: { + ...baseContext.metadata, + serviceContext: Deno.inspect(fsError.context, { colors: false }) + }, + }); + } + } else if (e.name === 'ValidationError') { + const valError = e as Error & { + code?: string; + field?: string; + context?: unknown; + }; + const errorContext = { + errorType: 'ValidationError', + errorCode: valError.code, + stackTrace: e.stack, + originalError: e + }; + + await errorLogger.error( + formatErrorMsg('ValidationError', e.message), + e, + mergeLogContext(baseContext, { + errorContext, + metadata: { + ...baseContext.metadata, + field: valError.field, + validationContext: valError.context, + }, + }), + ); + + if (e.stack) { + await errorLogger.debug('ValidationError stack trace:', { + ...baseContext, + metadata: { + ...baseContext.metadata, + stackTrace: e.stack + }, + }); + } + } else if ( + e.name === 'ConfigurationError' || e.name === 'ConfigError' || + e.name === 'ConfigValidationError' + ) { + const configError = e as Error & { + code?: string; + errors?: string[]; + context?: unknown; + }; + const errorContext = { + errorType: e.name, + errorCode: configError.code, + stackTrace: e.stack, + originalError: e + }; + + await errorLogger.error( + formatErrorMsg('ConfigurationError', e.message), + e, + mergeLogContext(baseContext, { + errorContext, + metadata: { + ...baseContext.metadata, + validationErrors: configError.errors, + configContext: configError.context, + }, + }), + ); + + if (e.stack) { + await errorLogger.debug('ConfigurationError stack trace:', { + ...baseContext, + metadata: { + ...baseContext.metadata, + stackTrace: e.stack + }, + }); + } + + if (configError.errors) { + await errorLogger.debug('Configuration validation errors:', { + ...baseContext, + metadata: { + ...baseContext.metadata, + validationErrors: configError.errors + }, + }); + } + } else if (['TypeError', 'ReferenceError', 'SyntaxError', 'RangeError'].includes(e.name)) { + await logDetailedError(e, e.name); + } else { + // Generic Error handling + await logDetailedError(e, 'Error'); + } + } else if (typeof e === 'string') { + // Handle string errors + await errorLogger.error( + formatErrorMsg('StringError', e), + undefined, + mergeLogContext(baseContext, { + errorContext: { + errorType: 'StringError', + }, + metadata: { + ...baseContext.metadata, + errorValue: e, + }, + }), + ); + } else if (typeof e === 'number') { + // Handle numeric errors + await errorLogger.error( + formatErrorMsg('NumericError', String(e)), + undefined, + mergeLogContext(baseContext, { + errorContext: { + errorType: 'NumericError', + }, + metadata: { + ...baseContext.metadata, + errorValue: e, + }, + }), + ); + } else if (e === null) { + // Handle null errors + await errorLogger.error( + formatErrorMsg('NullError', 'null value thrown'), + undefined, + mergeLogContext(baseContext, { + errorContext: { errorType: 'NullError' }, + }), + ); + } else if (e === undefined) { + // Handle undefined errors + await errorLogger.error( + formatErrorMsg('UndefinedError', 'undefined value thrown'), + undefined, + mergeLogContext(baseContext, { + errorContext: { errorType: 'UndefinedError' }, + }), + ); + } else { + // Handle any other unknown error types + await errorLogger.error( + formatErrorMsg('UnknownError', 'Unknown error type'), + undefined, + mergeLogContext(baseContext, { + errorContext: { + errorType: 'UnknownError', + }, + metadata: { + ...baseContext.metadata, + errorValue: Deno.inspect(e, { colors: false }), + }, + }), + ); + + // Log detailed inspection of unknown error + await errorLogger.debug('Unknown error details:', { + ...baseContext, + metadata: { + ...baseContext.metadata, + unknownError: Deno.inspect(e, { colors: false, depth: 3 }), + }, + }); + } + + // Handle additional options if provided + if (options?.rethrow) { + throw e; + } + } catch (loggingError) { + // Fallback error handling if logging itself fails + console.error('Failed to log error properly:', loggingError); + console.error('Original error was:', e); + + if (options?.rethrow) { + throw e; + } + } +} + +/** + * Convenience wrapper for async operations that automatically handles caught errors + * with LogContext support. + * + * @param operation - The async operation to wrap + * @param context - Context description for error messages + * @param logContext - LogContext for structured logging + * @param options - Error handling options + * @returns Promise that resolves to the operation result or undefined if error occurs + */ +export async function withErrorHandling( + operation: () => Promise, + context?: string, + logContext?: LogContext, + options?: ErrorHandlingOptions, +): Promise { + try { + return await operation(); + } catch (error) { + await handleCaughtError(error, context, logContext, options); + return undefined; + } +} + +/** + * Create an error handler with pre-filled LogContext for consistent error reporting + * within a specific operation or component. + * + * @param baseContext - Base LogContext to apply to all error handling + * @returns Object with error handling functions that use the base context + */ +export function createContextualErrorHandler(baseContext: LogContext) { + return { + handleError: async ( + error: unknown, + context?: string, + additionalContext?: LogContext, + meta?: Record, + options?: ErrorHandlingOptions, + ) => { + const mergedContext = mergeLogContext(baseContext, additionalContext); + return handleError(error, context, mergedContext, meta, options); + }, + + handleCaughtError: async ( + error: unknown, + customMessage?: string, + additionalContext?: LogContext, + options?: ErrorHandlingOptions, + ) => { + const mergedContext = mergeLogContext(baseContext, additionalContext); + return handleCaughtError(error, customMessage, mergedContext, options); + }, + + withErrorHandling: async ( + operation: () => Promise, + context?: string, + additionalContext?: LogContext, + options?: ErrorHandlingOptions, + ): Promise => { + const mergedContext = mergeLogContext(baseContext, additionalContext); + return withErrorHandling(operation, context, mergedContext, options); + }, + }; +} diff --git a/flow-core/src/utils/logger/file-logger.ts b/flow-core/src/utils/logger/file-logger.ts new file mode 100644 index 0000000..86c4b11 --- /dev/null +++ b/flow-core/src/utils/logger/file-logger.ts @@ -0,0 +1,201 @@ +/** + * @fileoverview File logging functionality with rotation and queuing support. + * Provides reliable file-based logging with automatic daily rotation and error handling. + */ + +import { dirname, ensureDir } from '../../deps.ts'; + +/** + * File logger with rotation, queuing, and error handling capabilities + */ +export class FileLogger { + private logFile: string; + private isWriting = false; + private writeQueue: string[] = []; + private readonly maxQueueSize: number; + + /** + * Create a new FileLogger instance + * @param logFilePath - Path to the log file + * @param maxQueueSize - Maximum number of queued log entries (default: 10000) + */ + constructor(logFilePath: string, maxQueueSize = 10000) { + this.logFile = logFilePath; + this.maxQueueSize = maxQueueSize; + } + + /** + * Ensure the log directory exists + */ + async ensureLogDirectory(): Promise { + try { + const dir = dirname(this.logFile); + await ensureDir(dir); + } catch (error) { + console.error(`Failed to create log directory: ${error}`); + } + } + + /** + * Write content to the log file (queued and batched for performance) + * @param content - Content to write to the log file + */ + async writeToFile(content: string): Promise { + if (this.writeQueue.length >= this.maxQueueSize) { + console.error( + `Log queue full (${this.maxQueueSize} items), dropping oldest entries`, + ); + this.writeQueue = this.writeQueue.slice( + -Math.floor(this.maxQueueSize / 2), + ); + } + this.writeQueue.push(content); + if (!this.isWriting) { + await this.processWriteQueue(); + } + } + + /** + * Process the write queue, writing all pending entries to the file + */ + private async processWriteQueue(): Promise { + if (this.writeQueue.length === 0) return; + + this.isWriting = true; + + try { + await this.ensureLogDirectory(); + + const content = this.writeQueue.join('\n') + '\n'; + this.writeQueue = []; + + await Deno.writeTextFile(this.logFile, content, { append: true }); + } catch (error) { + console.error(`Failed to write to log file: ${error}`); + // Graceful degradation - continue without crashing + } finally { + this.isWriting = false; + + // Process any new entries that were added while writing + if (this.writeQueue.length > 0) { + await this.processWriteQueue(); + } + } + } + + /** + * Check if log rotation is needed and rotate if necessary + */ + async rotateIfNeeded(): Promise { + try { + const stats = await Deno.stat(this.logFile); + const now = new Date(); + const fileDate = new Date(stats.mtime || stats.birthtime || now); + + // Check if file is from a different day + if (fileDate.toDateString() !== now.toDateString()) { + await this.performDailyRotation(); + } + } catch (error) { + // File doesn't exist yet, no rotation needed + if (!(error instanceof Deno.errors.NotFound)) { + console.error(`Failed to check log file for rotation: ${error}`); + } + } + } + + /** + * Perform daily log rotation + */ + private async performDailyRotation(): Promise { + try { + const now = new Date(); + const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000); + const rotatedName = this.logFile.replace( + '.log', + `-${yesterday.toISOString().split('T')[0]}.log`, + ); + + await Deno.rename(this.logFile, rotatedName); + console.log(`Log rotated to: ${rotatedName}`); + } catch (error) { + console.error(`Failed to rotate log file: ${error}`); + } + } + + /** + * Flush any pending writes and close the logger + */ + async close(): Promise { + // Process any remaining queue items + while (this.writeQueue.length > 0 && !this.isWriting) { + await this.processWriteQueue(); + } + } + + /** + * Get the current log file path + */ + getLogFilePath(): string { + return this.logFile; + } + + /** + * Get the current queue size + */ + getQueueSize(): number { + return this.writeQueue.length; + } + + /** + * Check if the logger is currently writing + */ + isCurrentlyWriting(): boolean { + return this.isWriting; + } +} + +/** + * Factory function to create a FileLogger based on environment configuration + * @returns FileLogger instance or null if file logging is disabled + */ +export async function createFileLogger(): Promise { + try { + // Check environment variable first to avoid config dependency during logger initialization + const fileLogPath = Deno.env.get('FLOW_FILE_LOG_PATH'); + const fileLogEnabled = Deno.env.get('FLOW_FILE_LOG_ENABLED'); + + if (fileLogEnabled === 'true' && fileLogPath) { + const fileLogger = new FileLogger(fileLogPath); + await fileLogger.rotateIfNeeded(); + return fileLogger; + } + } catch (error) { + // Use console.error here to avoid circular dependency with error handlers + console.error(`Failed to initialize file logger: ${error}`); + } + return null; +} + +/** + * Global file logger instance (lazy-loaded) + */ +let globalFileLogger: FileLogger | null | Promise = null; + +/** + * Get the global file logger instance (creates it if needed) + * @returns FileLogger instance or null if disabled + */ +export async function getGlobalFileLogger(): Promise { + if (globalFileLogger === null) { + globalFileLogger = createFileLogger(); + } + return await globalFileLogger; +} + +/** + * Reset the global file logger (useful for testing) + */ +export function resetGlobalFileLogger(): void { + globalFileLogger = null; +} diff --git a/flow-core/src/utils/logger/formatters.ts b/flow-core/src/utils/logger/formatters.ts new file mode 100644 index 0000000..3abd4f5 --- /dev/null +++ b/flow-core/src/utils/logger/formatters.ts @@ -0,0 +1,255 @@ +/** + * @fileoverview Message formatting utilities for structured logging. + * Provides consistent formatting for console output, file logging, and structured data. + */ + +import type { LogContext } from './types.ts'; + +/** + * Log level type using string literals + */ +export type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL'; + +/** + * Console color codes for terminal output + */ +const colors = { + reset: '\x1b[0m', + red: '\x1b[31m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + green: '\x1b[32m', + gray: '\x1b[90m', + bold: '\x1b[1m', +} as const; + +/** + * Log level numeric values for comparison + */ +export const LogLevels = { + DEBUG: 10, + INFO: 20, + WARN: 30, + ERROR: 40, + CRITICAL: 50, +} as const; + +/** + * Apply color codes to text for terminal output + * @param color - Color to apply + * @param text - Text to colorize + * @returns Colorized text (only in development mode) + */ +export function colorize(color: keyof typeof colors, text: string): string { + const isDevelopment = Deno.env.get('FLOW_ENV') !== 'production'; + if (!isDevelopment) return text; + return `${colors[color]}${text}${colors.reset}`; +} + +/** + * Check if a log level should be output based on current configuration + * @param level - Log level to check + * @returns True if the level should be logged + */ +export function shouldLog(level: LogLevel): boolean { + const configLevel = Deno.env.get('FLOW_LOG_LEVEL') || + (Deno.env.get('FLOW_ENV') !== 'production' ? 'DEBUG' : 'INFO'); + const currentLevel = LogLevels[configLevel as LogLevel] || LogLevels.INFO; + return LogLevels[level] >= currentLevel; +} + +/** + * Safely spread an unknown object for logging + * @param obj - Object to spread + * @returns Record with string keys, or empty object if input is invalid + */ +export function safeSpread(obj?: unknown): Record { + if (!obj || typeof obj !== 'object') return {}; + return obj as Record; +} + +/** + * Format a message for structured JSON logging (file output, external systems) + * @param level - Log level + * @param message - Log message + * @param context - Optional logging context + * @param serviceContext - Service-specific context + * @returns JSON string formatted log entry + */ +export function formatStructuredMessage( + level: LogLevel, + message: string, + context?: LogContext, + serviceContext?: { + serviceName?: string; + serviceVersion?: string; + environment?: string; + instanceId?: string; + }, +): string { + const timestamp = new Date().toISOString(); + const baseEntry = { + timestamp, + level: level.toLowerCase(), + message, + service: serviceContext?.serviceName || 'flow-service', + version: serviceContext?.serviceVersion || Deno.env.get('FLOW_VERSION'), + environment: serviceContext?.environment || Deno.env.get('FLOW_ENV') || 'development', + instanceId: serviceContext?.instanceId, + ...context, + }; + + return JSON.stringify(baseEntry); +} + +/** + * Format a message for console output with colors and readable context + * @param level - Log level + * @param message - Log message + * @param context - Optional logging context + * @returns Formatted string for console output + */ +export function formatConsoleMessage( + level: LogLevel, + message: string, + context?: LogContext, +): string { + const isDevelopment = Deno.env.get('FLOW_ENV') !== 'production'; + const timestamp = new Date().toISOString(); + const prefix = `[${timestamp}] ${level.padEnd(5)}`; + + if (isDevelopment) { + const coloredLevel = level === 'ERROR' || level === 'CRITICAL' + ? colorize('red', level) + : level === 'WARN' + ? colorize('yellow', level) + : level === 'DEBUG' + ? colorize('blue', level) + : colorize('green', level); + + const coloredPrefix = colorize('gray', `[${timestamp}]`) + + ` ${coloredLevel.padEnd(5)}`; + let contextStr = ''; + + if (context && Object.keys(context).length > 0) { + const parts = []; + if (context.operation) parts.push(`op=${context.operation}`); + if (context.meshId) parts.push(`mesh=${context.meshId}`); + if (context.performanceMetrics?.duration) parts.push(`${context.performanceMetrics.duration}ms`); + if (context.metadata?.nodeCount) parts.push(`${context.metadata.nodeCount} nodes`); + if (context.component) parts.push(`[${context.component}]`); + if (context.operationId) parts.push(`id=${context.operationId}`); + + contextStr = parts.length > 0 ? ` (${parts.join(', ')})` : ''; + } + + return `${coloredPrefix} ${message}${contextStr}`; + } + + return context + ? `${prefix} ${message} ${JSON.stringify(context)}` + : `${prefix} ${message}`; +} + +/** + * Extract key context information for error reporting + * @param context - Full logging context + * @returns Simplified context suitable for error reporting + */ +export function extractErrorContext(context?: LogContext): Record { + if (!context) return {}; + + return { + operation: context.operation, + operationId: context.operationId, + component: context.component, + functionName: context.functionName, + meshId: context.meshId, + nodeId: context.nodeId, + errorCode: context.errorContext?.errorCode, + errorType: context.errorContext?.errorType, + requestId: context.apiContext?.requestId, + endpoint: context.apiContext?.path, + statusCode: context.apiContext?.statusCode, + }; +} + +/** + * Create a summary context string for compact logging + * @param context - Full logging context + * @returns Compact string representation of key context + */ +export function createContextSummary(context?: LogContext): string { + if (!context) return ''; + + const parts = []; + + if (context.operation) parts.push(`op:${context.operation}`); + if (context.operationId) parts.push(`id:${context.operationId.slice(0, 8)}`); + if (context.component) parts.push(`comp:${context.component}`); + if (context.meshId) parts.push(`mesh:${context.meshId}`); + if (context.apiContext?.requestId) parts.push(`req:${context.apiContext.requestId.slice(0, 8)}`); + + return parts.length > 0 ? `[${parts.join('|')}]` : ''; +} + +/** + * Merge multiple LogContext objects with proper precedence + * @param contexts - Array of LogContext objects to merge (later entries take precedence) + * @returns Merged LogContext + */ +export function mergeLogContext(...contexts: (LogContext | undefined)[]): LogContext { + const result: LogContext = {}; + + for (const context of contexts) { + if (!context) continue; + + // Simple properties - later values override + Object.assign(result, context); + + // Special handling for nested objects + if (context.performanceMetrics) { + result.performanceMetrics = { + ...result.performanceMetrics, + ...context.performanceMetrics, + }; + } + + if (context.configContext) { + result.configContext = { + ...result.configContext, + ...context.configContext, + }; + } + + if (context.errorContext) { + result.errorContext = { + ...result.errorContext, + ...context.errorContext, + }; + } + + if (context.apiContext) { + result.apiContext = { + ...result.apiContext, + ...context.apiContext, + }; + } + + if (context.serviceContext) { + result.serviceContext = { + ...result.serviceContext, + ...context.serviceContext, + }; + } + + if (context.metadata) { + result.metadata = { + ...result.metadata, + ...context.metadata, + }; + } + } + + return result; +} diff --git a/flow-core/src/utils/logger/index.ts b/flow-core/src/utils/logger/index.ts new file mode 100644 index 0000000..ce91a22 --- /dev/null +++ b/flow-core/src/utils/logger/index.ts @@ -0,0 +1,127 @@ +/** + * @fileoverview Main export file for the flow-core logging system. + * Provides a comprehensive logging solution with structured context, file logging, and Sentry integration. + */ + +// Export all types and interfaces +export type { + LogContext, + StructuredLogger, + LoggerConfig, + ErrorSeverity, + ErrorHandlingOptions, +} from './types.ts'; + +export { LogLevel } from './types.ts'; + +// Export formatting utilities +export type { LogLevel as LogLevelString } from './formatters.ts'; +export { + colorize, + shouldLog, + safeSpread, + formatStructuredMessage, + formatConsoleMessage, + extractErrorContext, + createContextSummary, + mergeLogContext, +} from './formatters.ts'; + +// Export main logger implementations and factory functions +export { + StructuredLoggerImpl, + EnhancedStructuredLogger, + createLogger, + createEnhancedLogger, +} from './structured-logger.ts'; + +// Export file logging utilities +export type { FileLogger } from './file-logger.ts'; +export { + createFileLogger, + getGlobalFileLogger, + resetGlobalFileLogger, +} from './file-logger.ts'; + +// Export Sentry integration utilities +export { + initSentry, + isSentryEnabled, + reportErrorToSentry, + reportMessageToSentry, + setSentryUser, + addSentryBreadcrumb, +} from './sentry-logger.ts'; + +// Export error handling utilities +export { + handleError, + handleCaughtError, +} from './error-handlers.ts'; + +// Import types for function signatures +import type { StructuredLogger } from './types.ts'; +import type { EnhancedStructuredLogger } from './structured-logger.ts'; +import { createLogger, createEnhancedLogger } from './structured-logger.ts'; + +/** + * Create a default logger instance with standard configuration + * @param appName - Application name for context + * @param appVersion - Application version for context + * @returns Configured StructuredLogger instance + */ +export function createDefaultLogger( + appName: string, + appVersion?: string, +): StructuredLogger { + return createLogger( + { + enableConsole: true, + enableFile: true, + enableSentry: true, + serviceContext: { + serviceName: appName, + serviceVersion: appVersion || 'unknown', + environment: Deno.env.get('DENO_ENV') || 'development', + }, + }, + { + serviceContext: { + serviceName: appName, + serviceVersion: appVersion || 'unknown', + environment: Deno.env.get('DENO_ENV') || 'development', + }, + }, + ); +} + +/** + * Create a default enhanced logger instance with Error object support + * @param appName - Application name for context + * @param appVersion - Application version for context + * @returns Configured EnhancedStructuredLogger instance + */ +export function createDefaultEnhancedLogger( + appName: string, + appVersion?: string, +): EnhancedStructuredLogger { + return createEnhancedLogger( + { + enableConsole: true, + enableFile: true, + enableSentry: true, + serviceContext: { + serviceName: appName, + serviceVersion: appVersion || 'unknown', + environment: Deno.env.get('DENO_ENV') || 'development', + }, + }, + { + serviceContext: { + serviceName: appName, + serviceVersion: appVersion || 'unknown', + environment: Deno.env.get('DENO_ENV') || 'development', + }, + }, + ); +} diff --git a/flow-core/src/utils/logger/sentry-logger.ts b/flow-core/src/utils/logger/sentry-logger.ts new file mode 100644 index 0000000..228bd7a --- /dev/null +++ b/flow-core/src/utils/logger/sentry-logger.ts @@ -0,0 +1,303 @@ +/** + * @fileoverview Sentry integration for error reporting and logging. + * Provides centralized Sentry configuration and error reporting functionality. + */ + +import { Sentry } from '../../deps.ts'; +import type { LogContext } from './types.ts'; +import type { LogLevel } from './formatters.ts'; +import { extractErrorContext } from './formatters.ts'; + +/** + * Sentry initialization state + */ +let sentryInitialized = false; + +/** + * Sentry configuration options + */ +export interface SentryConfig { + /** Sentry DSN for error reporting */ + dsn: string; + /** Environment name (development, staging, production) */ + environment?: string; + /** Release version identifier */ + release?: string; + /** Debug mode for Sentry (enables verbose logging) */ + debug?: boolean; + /** Traces sample rate (0.0 to 1.0) */ + tracesSampleRate?: number; + /** Enable experimental logs feature */ + enableLogs?: boolean; +} + +/** + * Initialize Sentry with the provided configuration + * @param config - Sentry configuration options + * @param dsn - Optional DSN override + */ +export function initSentry(config?: SentryConfig, dsn?: string): void { + const isDevelopment = Deno.env.get('FLOW_ENV') !== 'production'; + const sentryDsn = dsn || config?.dsn || Deno.env.get('FLOW_SENTRY_DSN'); + const sentryEnabled = Deno.env.get('FLOW_SENTRY_ENABLED') === 'true' && !!sentryDsn; + + console.log(`šŸ”§ Sentry enabled in config: ${sentryEnabled}`); + + if (sentryEnabled && !sentryInitialized) { + console.log(`šŸ”§ Initializing Sentry...`); + try { + Sentry.init({ + dsn: sentryDsn, + environment: config?.environment || (isDevelopment ? 'development' : 'production'), + debug: config?.debug ?? isDevelopment, + tracesSampleRate: config?.tracesSampleRate ?? (isDevelopment ? 1.0 : 0.1), + release: config?.release || Deno.env.get('FLOW_VERSION'), + // Enable logs to be sent to Sentry (experimental feature) + _experiments: { + enableLogs: config?.enableLogs ?? true + }, + }); + sentryInitialized = true; + console.log(`šŸ”§ DEBUG: Sentry initialized successfully`); + } catch (error) { + // Use console.error here to avoid circular dependency with error handlers + console.error(`šŸ”§ ERROR: Failed to initialize Sentry: ${error}`); + console.error(`šŸ”§ ERROR: Sentry will remain disabled`); + } + } else if (Deno.env.get('FLOW_SENTRY_ENABLED') !== 'true') { + console.log(`šŸ”§ Sentry disabled - FLOW_SENTRY_ENABLED is not 'true'`); + } else if (!sentryDsn) { + console.log(`šŸ”§ Sentry disabled - no DSN provided`); + } else { + console.log(`šŸ”§ Sentry already initialized, skipping`); + } +} + +/** + * Check if Sentry is currently initialized and enabled + * @returns True if Sentry is ready to accept events + */ +export function isSentryEnabled(): boolean { + return sentryInitialized; +} + +/** + * Report an error to Sentry with structured context + * @param error - Error object to report + * @param level - Log level for the error + * @param context - Logging context for additional information + * @param message - Optional custom message + */ +export function reportErrorToSentry( + error: Error, + level: LogLevel, + context?: LogContext, + message?: string, +): void { + if (!sentryInitialized) return; + + try { + const sentryLevel = mapLogLevelToSentry(level); + const errorContext = extractErrorContext(context); + + Sentry.captureException(error, { + level: sentryLevel, + tags: { + source: 'flow-platform', + component: context?.component, + operation: context?.operation, + meshId: context?.meshId, + nodeId: context?.nodeId, + }, + extra: { + message: message || error.message, + operationId: context?.operationId, + functionName: context?.functionName, + ...errorContext, + ...context?.metadata, + }, + contexts: { + operation: { + name: context?.operation, + id: context?.operationId, + startTime: context?.performanceMetrics?.startTime, + duration: context?.performanceMetrics?.duration, + }, + mesh: { + id: context?.meshId, + name: context?.meshName, + }, + node: { + id: context?.nodeId, + name: context?.nodeName, + }, + api: context?.apiContext ? { + requestId: context.apiContext.requestId, + method: context.apiContext.method, + path: context.apiContext.path, + statusCode: context.apiContext.statusCode, + userAgent: context.apiContext.userAgent, + ip: context.apiContext.ip, + } : undefined, + config: context?.configContext ? { + configPath: context.configContext.configPath, + configType: context.configContext.configType, + validationStage: context.configContext.validationStage, + } : undefined, + }, + }); + } catch (err) { + console.error(`Failed to report error to Sentry: ${err}`); + } +} + +/** + * Send a message to Sentry + * @param message - Message to send + * @param level - Log level for the message + * @param context - Optional logging context + */ +export function reportMessageToSentry( + message: string, + level: LogLevel, + context?: LogContext, +): void { + if (!sentryInitialized) return; + + try { + const sentryLevel = mapLogLevelToSentry(level); + + Sentry.captureMessage(message, sentryLevel); + + if (context) { + const contextKey = `${level.toLowerCase()}_context`; + Sentry.setContext(contextKey, extractErrorContext(context)); + } + } catch (err) { + console.error(`Failed to send message to Sentry: ${err}`); + } +} + +/** + * Set user context in Sentry + * @param user - User information + */ +export function setSentryUser(user: { + id?: string; + email?: string; + username?: string; + ip_address?: string; +}): void { + if (!sentryInitialized) return; + + try { + Sentry.setUser(user); + } catch (err) { + console.error(`Failed to set Sentry user: ${err}`); + } +} + +/** + * Add breadcrumb to Sentry for debugging + * @param message - Breadcrumb message + * @param category - Breadcrumb category + * @param level - Breadcrumb level + * @param data - Additional data + */ +export function addSentryBreadcrumb( + message: string, + category?: string, + level?: 'debug' | 'info' | 'warning' | 'error' | 'fatal', + data?: Record, +): void { + if (!sentryInitialized) return; + + try { + Sentry.addBreadcrumb({ + message, + category: category || 'default', + level: level || 'info', + data, + timestamp: Date.now() / 1000, + }); + } catch (err) { + console.error(`Failed to add Sentry breadcrumb: ${err}`); + } +} + +/** + * Set a tag in Sentry + * @param key - Tag key + * @param value - Tag value + */ +export function setSentryTag(key: string, value: string): void { + if (!sentryInitialized) return; + + try { + Sentry.setTag(key, value); + } catch (err) { + console.error(`Failed to set Sentry tag: ${err}`); + } +} + +/** + * Set multiple tags in Sentry + * @param tags - Object with tag key-value pairs + */ +export function setSentryTags(tags: Record): void { + if (!sentryInitialized) return; + + try { + Sentry.setTags(tags); + } catch (err) { + console.error(`Failed to set Sentry tags: ${err}`); + } +} + +/** + * Close Sentry and flush any pending events + * @param timeout - Timeout in milliseconds (default: 2000) + * @returns Promise that resolves when all events are sent + */ +export async function closeSentry(timeout = 2000): Promise { + if (!sentryInitialized) return true; + + try { + return await Sentry.close(timeout); + } catch (err) { + console.error(`Failed to close Sentry: ${err}`); + return false; + } +} + +/** + * Reset Sentry state (useful for testing) + */ +export function resetSentry(): void { + sentryInitialized = false; +} + +/** + * Map internal log level to Sentry severity level + * @param level - Internal log level + * @returns Sentry severity level + */ +function mapLogLevelToSentry(level: LogLevel): 'debug' | 'info' | 'warning' | 'error' | 'fatal' { + switch (level) { + case 'DEBUG': + return 'debug'; + case 'INFO': + return 'info'; + case 'WARN': + return 'warning'; + case 'ERROR': + return 'error'; + case 'CRITICAL': + return 'fatal'; + default: + return 'info'; + } +} + +// Re-export Sentry for direct usage when needed +export { Sentry }; diff --git a/flow-core/src/utils/logger/structured-logger.ts b/flow-core/src/utils/logger/structured-logger.ts new file mode 100644 index 0000000..df70804 --- /dev/null +++ b/flow-core/src/utils/logger/structured-logger.ts @@ -0,0 +1,349 @@ +/** + * @fileoverview Main structured logger implementation that coordinates all logging channels. + * Integrates console output, file logging, and Sentry error reporting with structured context. + */ + +import type { LogContext, StructuredLogger, LoggerConfig } from './types.ts'; +import type { LogLevel } from './formatters.ts'; +import { + formatConsoleMessage, + formatStructuredMessage, + shouldLog, + mergeLogContext, +} from './formatters.ts'; +import { getGlobalFileLogger, type FileLogger } from './file-logger.ts'; +import { + isSentryEnabled, + reportErrorToSentry, + reportMessageToSentry, +} from './sentry-logger.ts'; + +/** + * Main structured logger implementation that coordinates all logging outputs + */ +export class StructuredLoggerImpl implements StructuredLogger { + private baseContext: LogContext; + private config: LoggerConfig; + + /** + * Create a new StructuredLogger instance + * @param baseContext - Base context applied to all log entries + * @param config - Logger configuration + */ + constructor(baseContext: LogContext = {}, config: LoggerConfig = {}) { + this.baseContext = baseContext; + this.config = { + enableConsole: true, + enableFile: true, + enableSentry: true, + ...config, + }; + } + + /** + * Log debug information + */ + async debug( + message: string, + context?: LogContext, + meta?: Record, + ): Promise { + if (!shouldLog('DEBUG')) return; + const mergedContext = this.mergeContexts(context, meta); + await this.writeToAllChannels('DEBUG', message, mergedContext); + } + + /** + * Log informational messages + */ + async info( + message: string, + context?: LogContext, + meta?: Record, + ): Promise { + if (!shouldLog('INFO')) return; + const mergedContext = this.mergeContexts(context, meta); + await this.writeToAllChannels('INFO', message, mergedContext); + } + + /** + * Log warning messages + */ + async warn( + message: string, + context?: LogContext, + meta?: Record, + ): Promise { + if (!shouldLog('WARN')) return; + const mergedContext = this.mergeContexts(context, meta); + await this.writeToAllChannels('WARN', message, mergedContext); + } + + /** + * Log error messages + */ + async error( + message: string, + context?: LogContext, + meta?: Record, + ): Promise { + if (!shouldLog('ERROR')) return; + const mergedContext = this.mergeContexts(context, meta); + await this.writeToAllChannels('ERROR', message, mergedContext); + } + + /** + * Log critical error messages + */ + async critical( + message: string, + context?: LogContext, + meta?: Record, + ): Promise { + const mergedContext = this.mergeContexts(context, meta); + await this.writeToAllChannels('CRITICAL', message, mergedContext); + } + + /** + * Create a contextual logger with pre-filled context + */ + withContext(baseContext: LogContext): StructuredLogger { + const mergedBaseContext = mergeLogContext(this.baseContext, baseContext); + return new StructuredLoggerImpl(mergedBaseContext, this.config); + } + + /** + * Create an operation-scoped logger with operation tracking + */ + forOperation(operationName: string, operationId?: string): StructuredLogger { + return this.withContext({ + operation: operationName, + operationId: operationId || crypto.randomUUID(), + performanceMetrics: { + startTime: Date.now(), + }, + }); + } + + /** + * Create a component-scoped logger + */ + forComponent(componentName: string): StructuredLogger { + return this.withContext({ + component: componentName, + }); + } + + /** + * Write log entry to all enabled channels (console, file, Sentry) + */ + protected async writeToAllChannels( + level: LogLevel, + message: string, + context?: LogContext, + error?: Error, + ): Promise { + // Write to console if enabled + if (this.config.enableConsole !== false) { + await this.writeToConsole(level, message, context); + } + + // Write to file if enabled + if (this.config.enableFile !== false) { + await this.writeToFile(level, message, context); + } + + // Write to Sentry if enabled + if (this.config.enableSentry !== false && isSentryEnabled()) { + await this.writeToSentry(level, message, context, error); + } + } + + /** + * Write log entry to console + */ + private async writeToConsole( + level: LogLevel, + message: string, + context?: LogContext, + ): Promise { + try { + const consoleFormatted = formatConsoleMessage(level, message, context); + + switch (level) { + case 'DEBUG': + case 'INFO': + console.log(consoleFormatted); + break; + case 'WARN': + console.warn(consoleFormatted); + break; + case 'ERROR': + case 'CRITICAL': + console.error(consoleFormatted); + break; + } + } catch (err) { + console.error(`Failed to write to console: ${err}`); + } + } + + /** + * Write log entry to file + */ + private async writeToFile( + level: LogLevel, + message: string, + context?: LogContext, + ): Promise { + try { + const fileLogger = await getGlobalFileLogger(); + if (fileLogger) { + await fileLogger.rotateIfNeeded(); + + const fileFormatted = formatStructuredMessage( + level, + message, + context, + this.config.serviceContext, + ); + await fileLogger.writeToFile(fileFormatted); + } + } catch (err) { + console.error(`Failed to write to file log: ${err}`); + } + } + + /** + * Write log entry to Sentry + */ + private async writeToSentry( + level: LogLevel, + message: string, + context?: LogContext, + error?: Error, + ): Promise { + try { + if (error) { + // Error reporting + reportErrorToSentry(error, level, context, message); + } else if (level === 'ERROR' || level === 'CRITICAL') { + // Convert high-level log messages to Sentry messages + reportMessageToSentry(message, level, context); + } + // Note: DEBUG/INFO/WARN messages are typically not sent to Sentry + // to avoid noise, but this can be configured per use case + } catch (err) { + console.error(`Failed to write to Sentry: ${err}`); + } + } + + /** + * Merge base context with provided context and metadata + */ + protected mergeContexts( + context?: LogContext, + meta?: Record, + ): LogContext { + const metaContext: LogContext = meta ? { metadata: meta } : {}; + return mergeLogContext(this.baseContext, context, metaContext); + } + + /** + * Get the current base context + */ + getBaseContext(): LogContext { + return { ...this.baseContext }; + } + + /** + * Get the current logger configuration + */ + getConfig(): LoggerConfig { + return { ...this.config }; + } + + /** + * Update the logger configuration + */ + updateConfig(config: Partial): void { + this.config = { ...this.config, ...config }; + } +} + +/** + * Enhanced structured logger that supports error objects in error/critical methods + */ +export class EnhancedStructuredLogger extends StructuredLoggerImpl { + /** + * Log error messages with optional Error object + */ + override async error( + message: string, + errorOrContext?: Error | LogContext, + context?: LogContext, + ): Promise { + if (!shouldLog('ERROR')) return; + + let error: Error | undefined; + let finalContext: LogContext | undefined; + + if (errorOrContext instanceof Error) { + error = errorOrContext; + finalContext = context; + } else { + finalContext = errorOrContext; + } + + const mergedContext = this.mergeContexts(finalContext); + await this.writeToAllChannels('ERROR', message, mergedContext, error); + } + + /** + * Log critical error messages with optional Error object + */ + override async critical( + message: string, + errorOrContext?: Error | LogContext, + context?: LogContext, + ): Promise { + let error: Error | undefined; + let finalContext: LogContext | undefined; + + if (errorOrContext instanceof Error) { + error = errorOrContext; + finalContext = context; + } else { + finalContext = errorOrContext; + } + + const mergedContext = this.mergeContexts(finalContext); + await this.writeToAllChannels('CRITICAL', message, mergedContext, error); + } +} + +/** + * Create a new structured logger instance + * @param config - Logger configuration + * @param baseContext - Base context for all log entries + * @returns New StructuredLogger instance + */ +export function createLogger( + config?: LoggerConfig, + baseContext?: LogContext, +): StructuredLogger { + return new StructuredLoggerImpl(baseContext, config); +} + +/** + * Create an enhanced structured logger that supports Error objects + * @param config - Logger configuration + * @param baseContext - Base context for all log entries + * @returns New EnhancedStructuredLogger instance + */ +export function createEnhancedLogger( + config?: LoggerConfig, + baseContext?: LogContext, +): EnhancedStructuredLogger { + return new EnhancedStructuredLogger(baseContext, config); +} diff --git a/flow-core/src/utils/logger/types.ts b/flow-core/src/utils/logger/types.ts new file mode 100644 index 0000000..e28d319 --- /dev/null +++ b/flow-core/src/utils/logger/types.ts @@ -0,0 +1,232 @@ +/** + * @fileoverview Core logging types and interfaces for the Semantic Flow platform. + * These types provide structured logging context and interfaces for consistent + * logging across all platform modules. + */ + +/** + * Structured logging context interface that provides comprehensive context + * information for logging operations across the platform. + */ +export interface LogContext { + /** Operation being performed (e.g., 'startup', 'config-resolve', 'weave', 'scan', 'api-request') */ + operation?: string; + + /** Unique identifier for tracking related log entries */ + operationId?: string; + + /** Component or module generating the log entry */ + component?: string; + + /** Specific function or method name */ + functionName?: string; + + /** Current mesh identifier when applicable */ + meshId?: string; + + /** Current node identifier when applicable */ + nodeId?: string; + + /** Current mesh name for human-readable context */ + meshName?: string; + + /** Current node name for human-readable context */ + nodeName?: string; + + /** Performance metrics for operation timing */ + performanceMetrics?: { + startTime?: number; + duration?: number; + memoryUsage?: number; + }; + + /** Configuration context when dealing with config operations */ + configContext?: { + configPath?: string; + configType?: string; + validationStage?: string; + }; + + /** Error context when logging errors */ + errorContext?: { + errorType?: string; + errorCode?: string; + stackTrace?: string; + originalError?: Error; + }; + + /** API request context for HTTP operations */ + apiContext?: { + requestId?: string; + method?: string; + path?: string; + statusCode?: number; + userAgent?: string; + ip?: string; + }; + + /** Service-specific context */ + serviceContext?: { + serviceName?: string; + serviceVersion?: string; + environment?: string; + instanceId?: string; + }; + + /** Additional arbitrary metadata */ + metadata?: Record; +} + +/** + * Core structured logger interface that all logger implementations must follow. + * Provides both simple logging methods and context-aware logging factories. + */ +export interface StructuredLogger { + /** + * Log debug information + * @param message - The log message + * @param context - Optional logging context + * @param meta - Additional metadata + */ + debug(message: string, context?: LogContext, meta?: Record): void; + + /** + * Log informational messages + * @param message - The log message + * @param context - Optional logging context + * @param meta - Additional metadata + */ + info(message: string, context?: LogContext, meta?: Record): void; + + /** + * Log warning messages + * @param message - The log message + * @param context - Optional logging context + * @param meta - Additional metadata + */ + warn(message: string, context?: LogContext, meta?: Record): void; + + /** + * Log error messages + * @param message - The log message + * @param context - Optional logging context + * @param meta - Additional metadata + */ + error(message: string, context?: LogContext, meta?: Record): void; + + /** + * Log critical error messages + * @param message - The log message + * @param context - Optional logging context + * @param meta - Additional metadata + */ + critical(message: string, context?: LogContext, meta?: Record): void; + + /** + * Create a contextual logger with pre-filled context + * @param baseContext - The base context to use for all log entries + * @returns A new logger instance with the base context applied + */ + withContext(baseContext: LogContext): StructuredLogger; + + /** + * Create an operation-scoped logger with operation tracking + * @param operationName - Name of the operation + * @param operationId - Optional unique identifier for the operation + * @returns A new logger instance scoped to the operation + */ + forOperation(operationName: string, operationId?: string): StructuredLogger; + + /** + * Create a component-scoped logger + * @param componentName - Name of the component + * @returns A new logger instance scoped to the component + */ + forComponent(componentName: string): StructuredLogger; +} + +/** + * Log level enumeration for controlling log output + */ +export enum LogLevel { + DEBUG = 0, + INFO = 1, + WARN = 2, + ERROR = 3, + CRITICAL = 4, + OFF = 5, +} + +/** + * Logger configuration interface + */ +export interface LoggerConfig { + /** Minimum log level to output */ + level?: LogLevel; + + /** Whether to enable console output */ + enableConsole?: boolean; + + /** Whether to enable file output */ + enableFile?: boolean; + + /** Whether to enable Sentry integration */ + enableSentry?: boolean; + + /** File logging configuration */ + fileConfig?: { + logDir?: string; + maxFileSize?: number; + maxFiles?: number; + rotateDaily?: boolean; + }; + + /** Sentry configuration */ + sentryConfig?: { + dsn?: string; + environment?: string; + release?: string; + sampleRate?: number; + }; + + /** Service context applied to all logs */ + serviceContext?: { + serviceName: string; + serviceVersion: string; + environment: string; + instanceId?: string; + }; +} + +/** + * Error severity levels for error handling + */ +export enum ErrorSeverity { + LOW = 'low', + MEDIUM = 'medium', + HIGH = 'high', + CRITICAL = 'critical', +} + +/** + * Error handling options + */ +export interface ErrorHandlingOptions { + /** Whether to log the error */ + logError?: boolean; + + /** Whether to report to Sentry */ + reportToSentry?: boolean; + + /** Error severity level */ + severity?: ErrorSeverity; + + /** Whether to include stack trace in logs */ + includeStackTrace?: boolean; + + /** Additional context for error reporting */ + context?: LogContext; + + /** Whether to throw the error after handling */ + rethrow?: boolean; +} 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..b83c8f0 --- /dev/null +++ b/flow-core/src/utils/quadstore/quadstore-factory.ts @@ -0,0 +1,26 @@ +// quadstoreFactory.ts +import { Quadstore, MemoryLevel, DataFactory, Engine } from '../../deps.ts'; +import { QuadstoreBundle } from '../../types.ts'; +import { handleCaughtError } from '../logger/error-handlers.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); + + + try { + await store.open(); + } catch (err) { + // Use preferred error handling approach + + await handleCaughtError(err, 'Failed to open quadstore'); + throw new Error(`Failed to open quadstore: ${err instanceof Error ? err.message : String(err)}`); + } + + return { store, df, backend, engine }; +} diff --git a/flow-core/src/utils/quadstore/quadstore-utils.ts b/flow-core/src/utils/quadstore/quadstore-utils.ts new file mode 100644 index 0000000..9415559 --- /dev/null +++ b/flow-core/src/utils/quadstore/quadstore-utils.ts @@ -0,0 +1,140 @@ +import { NodeObject, Quadstore, DataFactory } from '../../deps.ts'; +import { RDF } 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. + +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); + //console.log(`Number of quads before delStream in graph ${graph.value}: ${count}`); + const matchStream2 = store.match(undefined, undefined, undefined, graph); + await store.delStream(matchStream2); + /*const matchStream3 = store.match(undefined, undefined, undefined, graph); + const count2 = await countQuadsInStream(matchStream3); + console.log(`Number of quads in graph ${graph.value}: ${count2}`); + */ + 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) { + //console.log(`Copying quad: ${q.subject.value} ${q.predicate.value} ${q.object.value} to graph ${targetGraph.value}`); + 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); + for (const quad of quads) { + if (quad.subject.value.startsWith("_:") || quad.object.value.startsWith("_:")) { + console.warn(`Quad with blank node: ${quad.subject.value} ${quad.predicate.value} ${quad.object.value} in graph ${graph.value}`); + } + // Uncomment the next line to log each quad being processed + //console.log(`Quad: ${quad.subject.value} ${quad.predicate.value} ${quad.object.value} in graph ${graph.value}`); + } + + + 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/rdfjs-utils.ts b/flow-core/src/utils/rdfjs-utils.ts new file mode 100644 index 0000000..674d3fd --- /dev/null +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -0,0 +1,54 @@ +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 +// Uses jsonld.toRDF from https://github.com/digitalbazaar/jsonld.js +export async function jsonldToQuads( + inputJsonLd: NodeObject, + graph: RDF.NamedNode | RDF.DefaultGraph = df.defaultGraph(), +): Promise { + + + 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) + ); +} + +export function expandRelativeIds(inputJsonLd: NodeObject, baseIRI: string): any { + // Validate baseIRI format + if (!baseIRI.startsWith("http://") && !baseIRI.startsWith("https://")) { + throw new Error(`Invalid baseIRI: must start with http:// or https://, got: ${baseIRI}`); + } + if (!baseIRI.endsWith("/")) { + throw new Error(`Invalid baseIRI: must end with "/", got: ${baseIRI}`); + } + + const expanded = structuredClone(inputJsonLd); + + function rewrite(obj: any) { + if (obj && typeof obj === "object") { + if (typeof obj["@id"] === "string" && !obj["@id"].startsWith("http")) { + obj["@id"] = baseIRI + obj["@id"]; + } + for (const key of Object.keys(obj)) { + if (Array.isArray(obj[key])) obj[key].forEach(rewrite); + else if (typeof obj[key] === "object") rewrite(obj[key]); + } + } + } + + rewrite(expanded); + return expanded; +} diff --git a/flow-core/tests/integration/logger/error-handlers.test.ts b/flow-core/tests/integration/logger/error-handlers.test.ts new file mode 100644 index 0000000..631fb7c --- /dev/null +++ b/flow-core/tests/integration/logger/error-handlers.test.ts @@ -0,0 +1,186 @@ +#!/usr/bin/env -S deno run --allow-read --allow-write --allow-env + +/** + * Test script for the handleCaughtError function with LogContext integration + * This demonstrates the comprehensive error handling capabilities + */ + +import { + handleCaughtError, + handleError, + createDefaultEnhancedLogger +} from '../../../src/utils/logger/index.ts'; +import type { LogContext } from '../../../src/utils/logger/types.ts'; + +console.log('🧪 Testing handleCaughtError and handleError functions with LogContext...\n'); + +// Create logger for testing +const logger = createDefaultEnhancedLogger('flow-core-test', '1.0.0'); + +// Test 1: handleCaughtError with LogContext +console.log('=== Test 1: handleCaughtError with LogContext ==='); +try { + throw new Error('Service initialization failed'); +} catch (e) { + const context: LogContext = { + operation: 'startup', + component: 'mesh-scanner', + serviceContext: { + serviceName: 'flow-core-test', + serviceVersion: '1.0.0' + }, + metadata: { + retryCount: 3 + } + }; + await handleCaughtError(e, 'During service startup', context); +} + +console.log('\n=== Test 2: handleCaughtError with complex LogContext ==='); +try { + throw new TypeError('Cannot read property of undefined'); +} catch (e) { + const context: LogContext = { + operation: 'api-request', + component: 'api-handler', + operationId: 'req-123456', + serviceContext: { + serviceName: 'flow-core-test', + serviceVersion: '1.0.0' + }, + apiContext: { + path: '/api/test', + method: 'POST', + statusCode: 500 + }, + performanceMetrics: { + duration: 1250 + }, + errorContext: { + errorCode: 'PROPERTY_ACCESS_ERROR' + }, + metadata: { + userId: 'test-user-123', + requestBody: { test: 'data' } + } + }; + await handleCaughtError(e, 'Property access error', context); +} + +console.log('\n=== Test 3: handleError with LogContext ==='); +const error = new Error('Configuration validation failed'); +const context: LogContext = { + operation: 'config-resolve', + component: 'config-resolver', + serviceContext: { + serviceName: 'flow-core-test', + serviceVersion: '1.0.0' + }, + configContext: { + configPath: 'file:///test/config.json' + }, + errorContext: { + errorCode: 'CONFIG_VALIDATION_ERROR', + errorType: 'ValidationError' + }, + metadata: { + violations: ['Missing required field: port', 'Invalid log level'] + } +}; +await handleError(error, 'Config validation failed', context); + +console.log('\n=== Test 4: handleCaughtError with minimal LogContext ==='); +try { + throw 'Something went wrong!'; +} catch (e) { + const context: LogContext = { + operation: 'scan', + component: 'mesh-scanner' + }; + await handleCaughtError(e, 'String error thrown', context); +} + +console.log('\n=== Test 5: Error with nested LogContext merge ==='); +try { + throw new Error('Database connection failed'); +} catch (e) { + const baseContext: LogContext = { + operation: 'startup', + component: 'database', + serviceContext: { + serviceName: 'flow-core-test', + serviceVersion: '1.0.0' + } + }; + + const additionalContext: LogContext = { + performanceMetrics: { + duration: 5000 + }, + errorContext: { + errorCode: 'DB_CONNECTION_TIMEOUT' + }, + metadata: { + connectionString: 'postgresql://localhost:5432/test', + retryAttempts: 3 + } + }; + + // Test context merging + await handleCaughtError(e, 'Database connection error', { + ...baseContext, + ...additionalContext, + serviceContext: { + ...baseContext.serviceContext, + ...additionalContext.serviceContext + } + }); +} + +console.log('\n=== Test 6: LogContext with all optional fields ==='); +try { + throw new Error('Complex operation failed'); +} catch (e) { + const fullContext: LogContext = { + operation: 'weave', + component: 'weave-processor', + operationId: 'weave-op-789', + serviceContext: { + serviceName: 'flow-core-test', + serviceVersion: '1.0.0', + environment: 'test' + }, + configContext: { + configPath: 'memory://config', + configType: 'memory' + }, + apiContext: { + path: '/api/weave', + method: 'POST', + statusCode: 500, + userAgent: 'test-client/1.0' + }, + performanceMetrics: { + duration: 2500, + memoryUsage: 128000000 + }, + errorContext: { + errorCode: 'WEAVE_OPERATION_FAILED', + errorType: 'OperationError' + }, + metadata: { + meshPath: '/test/mesh', + nodeCount: 42, + fileCount: 128, + timestamp: new Date().toISOString(), + severity: 'critical', + retryable: false, + violations: ['Invalid mesh structure', 'Missing required nodes'] + } + }; + + await handleCaughtError(e, 'Complex weave operation failed', fullContext); +} + +console.log('\nāœ… All LogContext-enhanced error handling tests completed!'); +console.log('šŸ“‹ Check the logs above to verify proper error handling and LogContext integration.'); diff --git a/flow-core/tests/integration/logger/formatters.test.ts b/flow-core/tests/integration/logger/formatters.test.ts new file mode 100644 index 0000000..df382ff --- /dev/null +++ b/flow-core/tests/integration/logger/formatters.test.ts @@ -0,0 +1,147 @@ +#!/usr/bin/env -S deno run --allow-read --allow-write --allow-env + +/** + * Direct test of the formatting functions from flow-core + */ + +import { + formatConsoleMessage, + formatStructuredMessage, + createContextSummary, + mergeLogContext +} from '../../../src/utils/logger/index.ts'; +import type { LogContext } from '../../../src/utils/logger/types.ts'; + +console.log('🧪 Testing flow-core formatting functions...\n'); + +// Test basic console formatting +console.log('=== Test 1: Console Message Formatting ==='); +const basicContext: LogContext = { + operation: 'startup', + component: 'config-resolver', + serviceContext: { + serviceName: 'flow-core-test', + serviceVersion: '1.0.0' + } +}; + +const consoleMsg = formatConsoleMessage('INFO', 'File logging test started', basicContext); +console.log('Formatted console message:', consoleMsg); + +// Test structured message formatting +console.log('\n=== Test 2: Structured Message Formatting ==='); +const complexContext: LogContext = { + operation: 'scan', + component: 'mesh-scanner', + operationId: 'scan-123', + serviceContext: { + serviceName: 'flow-core-test', + serviceVersion: '1.0.0', + environment: 'test' + }, + performanceMetrics: { + duration: 1250, + memoryUsage: 128000000 + }, + metadata: { + meshPath: '/test/mesh', + nodeCount: 42 + } +}; + +const structuredMsg = formatStructuredMessage('DEBUG', 'Processing mesh nodes', complexContext); +console.log('Formatted structured message:', JSON.stringify(structuredMsg, null, 2)); + +// Test context summary creation +console.log('\n=== Test 3: Context Summary Creation ==='); +const summaryContext: LogContext = { + operation: 'api-request', + component: 'api-handler', + operationId: 'req-456', + apiContext: { + path: '/api/test', + method: 'POST', + statusCode: 200 + }, + performanceMetrics: { + duration: 250 + }, + errorContext: { + errorCode: 'VALIDATION_ERROR', + errorType: 'ValidationError' + }, + metadata: { + userId: 'test-user', + requestId: 'req-456' + } +}; + +const contextSummary = createContextSummary(summaryContext); +console.log('Context summary:', contextSummary); + +// Test context merging +console.log('\n=== Test 4: LogContext Merging ==='); +const baseContext: LogContext = { + operation: 'config-resolve', + component: 'config-loader', + serviceContext: { + serviceName: 'flow-core-test', + serviceVersion: '1.0.0' + } +}; + +const additionalContext: LogContext = { + operationId: 'config-op-789', + configContext: { + configPath: '/test/config.json', + configType: 'file' + }, + performanceMetrics: { + duration: 500 + }, + metadata: { + configSize: 1024 + } +}; + +const mergedContext = mergeLogContext(baseContext, additionalContext); +console.log('Merged context:', JSON.stringify(mergedContext, null, 2)); + +// Test multiple context merges +console.log('\n=== Test 5: Multiple Context Merging ==='); +const context1: LogContext = { + operation: 'weave', + component: 'weave-processor', + serviceContext: { + serviceName: 'flow-core-test', + serviceVersion: '1.0.0' + } +}; + +const context2: LogContext = { + operationId: 'weave-op-101', + performanceMetrics: { + startTime: Date.now(), + duration: 2000 + } +}; + +const context3: LogContext = { + metadata: { + meshCount: 5, + nodeCount: 25, + processingMode: 'parallel' + }, + apiContext: { + requestId: 'weave-req-101' + } +}; + +const multiMergedContext = mergeLogContext( + mergeLogContext(context1, context2), + context3 +); +console.log('Multi-merged context:', JSON.stringify(multiMergedContext, null, 2)); + +console.log('\nāœ… All formatting function tests completed!'); +console.log('šŸ“‹ Check the output above to verify proper formatting functionality.'); diff --git a/flow-core/tests/integration/logger/logger.test.ts b/flow-core/tests/integration/logger/logger.test.ts new file mode 100644 index 0000000..d2c851d --- /dev/null +++ b/flow-core/tests/integration/logger/logger.test.ts @@ -0,0 +1,94 @@ +#!/usr/bin/env -S deno run --allow-all + +/** + * Simple test script to verify file logging functionality + */ + +import { createDefaultEnhancedLogger } from '../../../src/utils/logger/index.ts'; + +// Create logger instance for testing +const logger = createDefaultEnhancedLogger('flow-core-test', '1.0.0'); + +async function testFileLogging() { + console.log('🧪 Testing file logging functionality...\n'); + + // Test basic logging with context + logger.info('File logging test started', { + operation: 'startup', + component: 'config-resolver', + metadata: { + testId: 'file-logging-test-001', + }, + }); + + logger.debug('Debug message with context', { + operation: 'scan', + component: 'mesh-scanner', + performanceMetrics: { + duration: 125, + }, + metadata: { + meshPath: '/test/path', + nodeCount: 5, + }, + }); + + logger.warn('Warning message with context', { + operation: 'config-resolve', + component: 'config-resolver', + configContext: { + configPath: 'file', + }, + metadata: { + schemaVersion: '1.0.0', + }, + }); + + logger.error('Error message with context', new Error('Test error'), { + operation: 'api-request', + component: 'api-handler', + apiContext: { + path: '/api/test', + statusCode: 500, + }, + errorContext: { + errorCode: 'TEST_ERROR', + }, + }); + + // Test contextual logger + const meshLogger = logger.withContext({ + component: 'mesh-scanner', + operation: 'scan', + metadata: { + meshPath: '/test/mesh', + }, + }); + + meshLogger.info('Processing nodes', { + performanceMetrics: { + duration: 250, + }, + metadata: { + nodeCount: 10, + }, + }); + + meshLogger.info('Scan completed', { + performanceMetrics: { + duration: 500, + }, + metadata: { + nodeCount: 10, + fileCount: 25, + }, + }); + + console.log('\nāœ… File logging test completed!'); + console.log('šŸ“ Check the logs directory for the generated log file'); + console.log('šŸ“„ Log file should contain structured JSON entries'); +} + +if (import.meta.main) { + await testFileLogging(); +} 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..d7d9c0a --- /dev/null +++ b/flow-core/tests/unit/quadstore-utils.test.ts @@ -0,0 +1,95 @@ +import { assertEquals } from '../../src/deps.ts'; +import { clearGraph, copyGraph, isGraphEmpty, countQuadsInStream, putJsonLdToGraph, createNewGraphFromJsonLd } from '../../src/utils/quadstore/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', async () => { + const testQuadstoreBundle = await createQuadstoreBundle(); + const df = testQuadstoreBundle.df; + const store = testQuadstoreBundle.store; + + const graph = df.namedNode('urn:testGraph3'); + + const emptyBeforeClear = await isGraphEmpty(graph, testQuadstoreBundle); + await clearGraph(graph); + assertEquals(emptyBeforeClear, true); + + const quad = df.quad(df.namedNode('s'), df.namedNode('p'), df.literal('o'), graph); + await store.put(quad); + + const emptyAfterPut = await isGraphEmpty(graph, testQuadstoreBundle); + assertEquals(emptyAfterPut, false); + + await clearGraph(graph); + + const matchStream = store.match(undefined, undefined, undefined, graph); + const count = await countQuadsInStream(matchStream); + console.log(`quads in graph ${graph.value}: ${count}`); + + const emptyAfterClear = await isGraphEmpty(graph, testQuadstoreBundle); + assertEquals(emptyAfterClear, 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(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..ff2e7c0 100644 --- a/flow-service/deno.json +++ b/flow-service/deno.json @@ -5,19 +5,20 @@ "hono/testing": "jsr:@hono/hono@^4.8.4/testing", "@scalar/hono-api-reference": "npm:@scalar/hono-api-reference@^0.9.10", "@scalar/openapi-to-markdown": "npm:@scalar/openapi-to-markdown", - "@sentry/deno": "npm:@sentry/deno@^8.0.0" + "@sentry/deno": "npm:@sentry/deno@^8.0.0", + "npm:quadstore": "npm:quadstore" }, "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..4ea1718 100644 --- a/flow-service/deno.lock +++ b/flow-service/deno.lock @@ -4,15 +4,25 @@ "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: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.3.0_asynciterator@3.9.0_quadstore@15.4.0", + "npm:quadstore@*": "15.4.0", + "npm:rdf-data-factory@*": "2.0.2", "npm:zod@^3.22.0": "3.25.76" }, "jsr": { @@ -51,6 +61,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 +195,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 +286,7 @@ "integrity": "sha512-ho9ee29Ik+Jv17bSaQEXvGn1e2ydNEyc7Sv8A8hqPq084aIOiTBtVFISPPt+k/O6z0SIRayfL8FEZqr93GyZPg==", "dependencies": [ "@phosphor-icons/core", - "@types/node", + "@types/node@22.16.3", "chalk", "vue" ] @@ -431,6 +471,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 +483,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 +606,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 +660,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 +686,9 @@ "tslib" ] }, + "canonicalize@1.0.8": { + "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==" + }, "ccount@2.0.1": { "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" }, @@ -652,6 +740,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 +786,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 +798,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 +1042,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 +1063,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 +1090,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 +1131,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 +1146,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 +1275,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 +1527,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 +1551,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 +1617,25 @@ "property-information@7.1.0": { "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" }, + "quadstore-comunica@6.3.0_asynciterator@3.9.0_quadstore@15.4.0": { + "integrity": "sha512-2Yy5eIVqWYgbvWOI5/T1GA5zxGhISvrmAMavY/zBghVLyJeOCSw0fPbU3+wPYCGKgSMg65/Kg/nh2QnaMs3uvg==", + "dependencies": [ + "@rdfjs/types@2.0.1", + "asynciterator", + "quadstore", + "sparqlalgebrajs", + "sparqljs" + ] + }, + "quadstore@15.4.0": { + "integrity": "sha512-YlIksV9r7GTMfuawA1vqCEqtd88rX9SnYz+HAqDSdbGDSogyVTnOvJKe2qisy2FWuXrc3PpbKLDEOjJ/wEpgtA==", + "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 +1653,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 +1805,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 +1824,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 +1894,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 +2001,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 +2026,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 +2068,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 +2362,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", @@ -2223,7 +2534,8 @@ "npm:@hono/zod-openapi@0.18", "npm:@scalar/hono-api-reference@~0.9.10", "npm:@scalar/openapi-to-markdown@*", - "npm:@sentry/deno@8" + "npm:@sentry/deno@8", + "npm:quadstore@*" ] } } diff --git a/flow-service/flow-service-config.jsonld b/flow-service/flow-service-config.jsonld index 3b60019..383e635 100644 --- a/flow-service/flow-service-config.jsonld +++ b/flow-service/flow-service-config.jsonld @@ -1,33 +1,27 @@ { "@context": { "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/", "meta": "https://semantic-flow.github.io/ontology/meta-flow/", - "prov": "http://www.w3.org/ns/prov#", - "dcterms": "http://purl.org/dc/terms/" + "prov": "http://www.w3.org/ns/prov#" }, + "@id": "_service-config/", "@type": "fsvc:ServiceConfig", - "@id": "fsvc:defaultConfig", + "fsvc:scheme": "http", + "fsvc:host": "localhost", "fsvc:port": 8080, "fsvc:meshPaths": [ "../meshes/test-ns/" ], "fsvc:defaultLicense": "https://creativecommons.org/licenses/by-sa/4.0/", - "fsvc:defaultAttributedTo": { - "@id": "https://orcid.org/0000-0002-1825-0097" - }, - "fsvc:defaultRightsHolder": { - "@id": "http://djradon.github.io/ns/djradon", - "@type": "prov:Person" - }, + "fsvc:defaultAttributedTo": "http://djradon.github.io/ns/djradon", + "fsvc:defaultRightsHolder": "http://djradon.github.io/ns/djradon", "fsvc:defaultRights": "Copyright 2025 David Richardson. Licensed under CC BY-SA 4.0.", "fsvc:defaultDelegationChain": { + "@id": "_service-config/defaultDelegationChain", "@type": "meta:DelegationChain", "meta:hasStep": [ { + "@id": "_service-config/defaultDelegationChain#1", "@type": "meta:DelegationStep", "meta:stepOrder": 1, "prov:agent": { @@ -35,6 +29,7 @@ } }, { + "@id": "_service-config/defaultDelegationChain#2", "@type": "meta:DelegationStep", "meta:stepOrder": 2, "prov:agent": { diff --git a/flow-service/logs/test-pretty-direct.log b/flow-service/logs/test-pretty-direct.log new file mode 100644 index 0000000..b47f4da --- /dev/null +++ b/flow-service/logs/test-pretty-direct.log @@ -0,0 +1,4 @@ +[2025-08-03T15:27:54.395Z] INFO File logging test started (op=startup, [config-resolver]) +[2025-08-03T15:27:54.396Z] WARN Configuration override detected (op=config-resolve) +[2025-08-03T15:27:54.397Z] ERROR Database connection failed (op=startup) +[2025-08-03T15:27:54.398Z] DEBUG Processing mesh nodes (op=scan) diff --git a/flow-service/main.ts b/flow-service/main.ts index 6f9c89a..ce28b43 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -2,20 +2,25 @@ 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, } from './src/utils/startup-logger.ts'; -import { handleCaughtError } from './src/utils/logger.ts'; +import { handleCaughtError } from '../flow-core/src/utils/logger/error-handlers.ts'; +import type { LogContext } from '../flow-core/src/utils/logger/types.ts'; +import { createServiceLogContext } from './src/utils/service-log-context.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'); + const context: LogContext = createServiceLogContext({ + operation: 'startup', + component: 'service-config-init', + }); + await handleCaughtError(error, 'Failed to initialize service configuration', context); console.error( 'āŒ Service startup failed due to configuration error. Exiting...', ); @@ -24,9 +29,13 @@ try { // Log service startup with configuration info try { - logStartupConfiguration(config); + logStartupConfiguration(); } catch (error) { - await handleCaughtError(error, 'Failed to log startup configuration'); + const context: LogContext = createServiceLogContext({ + operation: 'startup', + component: 'startup-config-logging', + }); + await handleCaughtError(error, 'Failed to log startup configuration', context); console.error('āš ļø Configuration logging failed, but continuing startup...'); } @@ -46,7 +55,7 @@ const content = { }, servers: [ { - url: `http://${config.host}:${config.port}`, + url: `http://${await singletonServiceConfigAccessor.getHost()}:${await singletonServiceConfigAccessor.getPort()}`, description: 'Configured server', }, ], @@ -59,7 +68,7 @@ app.get( MESH.API_PORTAL_ROUTE, apiReference({ spec: { url: '/openapi.json' }, - pageTitle: 'Semantic Flow Service API', + pageTitle: 'Semantic Flow Service API Docs', theme: 'default', layout: 'classic', }), @@ -69,7 +78,11 @@ let markdown; try { markdown = await createMarkdownFromOpenApi(JSON.stringify(content)); } catch (error) { - await handleCaughtError(error, 'Failed to generate markdown documentation'); + const context: LogContext = createServiceLogContext({ + operation: 'startup', + component: 'markdown-generation', + }); + await handleCaughtError(error, 'Failed to generate markdown documentation', context); console.error('āš ļø Markdown generation failed, but continuing startup...'); markdown = '# API Documentation\n\nDocumentation generation failed.'; } @@ -83,27 +96,38 @@ 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'); + const context: LogContext = createServiceLogContext({ + operation: 'startup', + component: 'startup-url-logging', + }); + await handleCaughtError(error, 'Failed to log startup URLs', context); console.error('āš ļø URL logging failed, but continuing startup...'); } 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'); + const context: LogContext = createServiceLogContext({ + operation: 'startup', + component: 'http-server-start', + metadata: { + serverEndpoint: `http://${await singletonServiceConfigAccessor.getHost()}:${await singletonServiceConfigAccessor.getPort()}` + } + }); + await handleCaughtError(error, 'Failed to start HTTP server', context); console.error('āŒ Server startup failed. Exiting...'); Deno.exit(1); } diff --git a/flow-service/src/config/config-types.ts b/flow-service/src/config/config-types.ts new file mode 100644 index 0000000..2bc4855 --- /dev/null +++ b/flow-service/src/config/config-types.ts @@ -0,0 +1,191 @@ +/** + * Configuration Types for Semantic Flow Service + * + * Based on JSON-LD ontologies with side-by-side configuration context pattern. + * 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 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/"; + readonly flow: "https://semantic-flow.github.io/ontology/flow/"; + readonly conf: "https://semantic-flow.github.io/ontology/config-flow/"; +} + +// Logging Configuration Types +export interface LogChannelConfig extends NodeObject { + readonly "@type": "fsvc:LogChannelConfig"; + readonly "fsvc:logChannelEnabled": boolean; + readonly "fsvc:logLevel": "debug" | "info" | "warn" | "error"; + readonly "fsvc:logFormat"?: "json" | "pretty"; + readonly "fsvc:logFilePath"?: string; + readonly "fsvc:sentryDsn"?: string; + readonly "fsvc:sentryLoggingEnabled"?: boolean; + readonly "fsvc:logRetentionDays"?: number; + readonly "fsvc:logMaxFiles"?: number; + readonly "fsvc:logMaxFileSize"?: number; + readonly "fsvc:logRotationInterval"?: + | "daily" + | "weekly" + | "monthly" + | "size-based"; +} + +export interface LoggingConfig extends NodeObject { + readonly "@type": "fsvc:LoggingConfig"; + readonly "fsvc:hasConsoleChannel": LogChannelConfig; + readonly "fsvc:hasFileChannel": LogChannelConfig; + readonly "fsvc:hasSentryChannel": LogChannelConfig; +} + +// Contained Services Configuration +export interface ContainedServicesConfig extends NodeObject { + readonly "@type": "fsvc:ContainedServicesConfig"; + readonly "fsvc:apiEnabled": boolean; + readonly "fsvc:sparqlEnabled": boolean; + readonly "fsvc:queryWidgetEnabled": boolean; + readonly "fsvc:staticServerEnabled": boolean; + readonly "fsvc:apiDocsEnabled": boolean; +} + +// Node Configuration Types (for service defaults) +export interface TemplateMapping extends NodeObject { + readonly "@type": "conf:TemplateMapping"; + readonly "conf:hasResourcePageTemplate": string; +} +// TODO rename to +export interface MeshRootNodeConfig extends NodeObject { + readonly "@type": "conf:MeshRootNodeConfig"; + readonly "conf:versioningEnabled": boolean; + readonly "conf:distributionFormats": string[]; + readonly "conf:templateMappings"?: TemplateMapping; + readonly "conf:configInheritanceEnabled": boolean; + readonly "conf:generateUnifiedDataset": boolean; + readonly "conf:generateAggregatedDataset": boolean; + readonly "conf:generateResourcePages"?: boolean; + readonly "conf:stylesheetPath"?: string; + readonly "conf:defaultAttributedTo"?: string; +} + +// Complete Service Configuration +export interface ServiceConfig extends NodeObject { + readonly "@type": "fsvc:ServiceConfig"; + readonly "fsvc:scheme": "http" | "https"; + readonly "fsvc:port": number; + readonly "fsvc:host": string; + readonly "fsvc:meshPaths"?: string[]; + readonly "fsvc:hasLoggingConfig": LoggingConfig; + readonly "fsvc:hasContainedServices": ContainedServicesConfig; + readonly "fsvc:rootMeshRootNodeConfigTemplate"?: MeshRootNodeConfig; + readonly "fsvc:defaultDelegationChain"?: DelegationChain; + readonly "fsvc:defaultAttributedTo"?: string; +} + +// Delegation Chain Configuration +export interface DelegationStep extends NodeObject { + readonly "@type": "meta:DelegationStep"; + readonly "meta:stepOrder": number; + readonly "prov:agent": { + readonly "@id": string; + }; +} + +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 { + "@type"?: "fsvc:ServiceConfig"; + "fsvc:scheme"?: "http" | "https"; + "fsvc:port"?: number; + "fsvc:host"?: string; + "fsvc:meshPaths"?: string[]; + "fsvc:hasLoggingConfig"?: Partial; + "fsvc:hasContainedServices"?: Partial; + "fsvc:nodeDefaults"?: Partial; + "fsvc:defaultDelegationChain"?: Partial; + "fsvc:defaultAttributedTo"?: string; +} + +export interface MeshRootNodeConfigInput extends Partial { + "@type"?: "conf:MeshRootNodeConfig"; + "conf:versioningEnabled"?: boolean; + "conf:distributionFormats"?: string[]; + "conf:templateMappings"?: Partial; + "conf:configInheritanceEnabled"?: boolean; + "conf:generateUnifiedDataset"?: boolean; + "conf:generateAggregatedDataset"?: boolean; + "conf:generateResourcePages"?: boolean; + "conf:stylesheetPath"?: string; + "conf:defaultAttributedTo"?: string; +} + +export interface MeshRootNodeConfigContext { + readonly inputOptions: MeshRootNodeConfigInput; + readonly defaultOptions: MeshRootNodeConfig; +} + +// CLI Options Type +export interface ServiceOptions { + readonly configPath?: string; + readonly scheme?: "http" | "https"; + readonly port?: number; + readonly host?: string; + readonly meshPaths?: string[]; + readonly logLevel?: LogLevel; + readonly sentryEnabled?: boolean; +} + + +// Environment Variable Mapping +export interface EnvironmentConfig { + readonly FLOW_CONFIG_PATH?: string; + readonly FLOW_SERVICE_SCHEME?: string; + readonly FLOW_SERVICE_PORT?: string; + readonly FLOW_SERVICE_HOST?: string; + readonly FLOW_MESH_PATHS?: string; + readonly FLOW_LOG_LEVEL?: string; + readonly FLOW_SENTRY_ENABLED?: string; + readonly FLOW_SENTRY_DSN?: string; + readonly FLOW_SENTRY_LOGGING_ENABLED?: string; + readonly FLOW_FILE_LOG_ENABLED?: string; + readonly FLOW_FILE_LOG_LEVEL?: string; + readonly FLOW_FILE_LOG_PATH?: string; + readonly FLOW_LOG_RETENTION_DAYS?: string; + readonly FLOW_LOG_MAX_FILES?: string; + readonly FLOW_LOG_MAX_FILE_SIZE?: string; + readonly FLOW_LOG_ROTATION_INTERVAL?: string; + readonly FLOW_DEFAULT_VERSIONING?: string; + readonly FLOW_DEFAULT_FORMATS?: string; + readonly FLOW_API_ENABLED?: string; + readonly FLOW_SPARQL_ENABLED?: string; +} + +// Configuration Error Types +export class ConfigError extends Error { + constructor(message: string, public override readonly cause?: Error) { + super(message); + this.name = "ConfigError"; + } +} + +export class ConfigValidationError extends ConfigError { + constructor( + message: string, + public readonly errors: string[], + cause?: Error, + ) { + super(message, cause); + this.name = "ConfigValidationError"; + } +} + +// Utility Types for Configuration Access +export type LogLevel = "debug" | "info" | "warn" | "error"; + diff --git a/flow-service/src/config/defaults.ts b/flow-service/src/config/defaults.ts index cdc7873..8bdfe30 100644 --- a/flow-service/src/config/defaults.ts +++ b/flow-service/src/config/defaults.ts @@ -5,38 +5,50 @@ * 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"; +import { getCurrentServiceBaseUri } from "../utils/service-uri-builder.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/', +// Function that returns JSON-LD Context with dynamic "local" property +export function getDynamicLocalContext(): FlowServiceContext { + return { + "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": `${getCurrentServiceBaseUri()}/` + }; +} + +// Static fallback context (for backward compatibility) +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/", }; -// 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', +export const PLATFORM_NODE_DEFAULTS: MeshRootNodeConfig = { + "@id": "_service-config/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 +56,129 @@ 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: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": "_service-config/", + "@context": DEFAULT_CONTEXT, + "@type": "fsvc:ServiceConfig", + "fsvc:scheme": "http", + "fsvc:port": 31415, + "fsvc:host": "localhost", + "fsvc:hasLoggingConfig": { + "@id": "_service-config/loggingConfig", + "@type": "fsvc:LoggingConfig", + "fsvc:hasConsoleChannel": { + "@id": "_service-config/loggingConfig/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": "_service-config/loggingConfig/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": "_service-config/loggingConfig/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": "_service-config/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": "_service-config/", + "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 +192,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..108f0fc 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -5,35 +5,39 @@ * and side-by-side configuration context. */ +import { createServiceLogContext } from "../utils/service-log-context.ts"; + +/** + * Named graph terms for Quadstore config graphs + */ +export const CONFIG_GRAPH_NAMES = { + platformServiceDefaults: 'platformServiceDefaults', + platformImplicitMeshRootNodeConfig: 'platformImplicitMeshRootNodeConfig', + inputServiceConfig: 'inputServiceConfig', + inputMeshRootNodeConfig: 'inputMeshRootNodeConfig', + mergedServiceConfig: 'mergedServiceConfig', +} + + // 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 +54,54 @@ 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 { handleCaughtError } from '../utils/logger.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 '../../../flow-core/src/utils/logger/error-handlers.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; - } -} + const context = createServiceLogContext({ + operation: 'config-create', + component: 'service-config-creation', + metadata: { cliOptions } -/** - * 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`, - ); + }); + await handleCaughtError(error, `Failed to create service configuration`, context); throw error; } } + diff --git a/flow-service/src/config/loaders/env-loader.ts b/flow-service/src/config/loaders/env-loader.ts index 7971bb4..1992965 100644 --- a/flow-service/src/config/loaders/env-loader.ts +++ b/flow-service/src/config/loaders/env-loader.ts @@ -7,10 +7,11 @@ import type { EnvironmentConfig, - LogLevel, ServiceConfigInput, -} from '../types.ts'; -import { ConfigError } from '../types.ts'; +} from "../config-types.ts"; +import { DEFAULT_CONTEXT } from "../defaults.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. @@ -19,29 +20,42 @@ import { ConfigError } from '../types.ts'; * * @returns A `ServiceConfigInput` object representing the configuration derived from environment variables. */ + +//TODO: construct a proper service config object export function loadEnvConfig(): ServiceConfigInput { const env = getEnvironmentVariables(); - const configInput: ServiceConfigInput = {}; + const configInput: ServiceConfigInput = { + "@id": "_service-config/", + "@context": DEFAULT_CONTEXT, + "@type": "fsvc:ServiceConfig", + }; // Basic service settings + if (env.FLOW_SERVICE_SCHEME) { + const scheme = env.FLOW_SERVICE_SCHEME.toLowerCase(); + if (scheme === "http" || scheme === "https") { + configInput["fsvc:scheme"] = scheme; + } + } + if (env.FLOW_SERVICE_PORT) { const port = parseInt(env.FLOW_SERVICE_PORT, 10); if (!isNaN(port) && port > 0 && port <= 65535) { - configInput['fsvc:port'] = port; + configInput["fsvc:port"] = port; } } if (env.FLOW_SERVICE_HOST) { - configInput['fsvc:host'] = env.FLOW_SERVICE_HOST; + configInput["fsvc:host"] = env.FLOW_SERVICE_HOST; } // Mesh paths configuration if (env.FLOW_MESH_PATHS) { - const meshPaths = env.FLOW_MESH_PATHS.split(',').map((p) => p.trim()) + const meshPaths = env.FLOW_MESH_PATHS.split(",").map((p) => p.trim()) .filter((p) => p); if (meshPaths.length > 0) { - configInput['fsvc:meshPaths'] = meshPaths; + configInput["fsvc:meshPaths"] = meshPaths; } } @@ -56,10 +70,10 @@ export function loadEnvConfig(): ServiceConfigInput { if (env.FLOW_LOG_LEVEL) { try { const logLevel = validateLogLevel(env.FLOW_LOG_LEVEL); - loggingConfig['fsvc:hasConsoleChannel'] = { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': logLevel, + loggingConfig["fsvc:hasConsoleChannel"] = { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": logLevel, }; hasLoggingConfig = true; } catch (error) { @@ -81,14 +95,14 @@ export function loadEnvConfig(): ServiceConfigInput { const enabled = parseBoolean(env.FLOW_FILE_LOG_ENABLED); if (enabled !== undefined) { const fileChannel: Record = { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': enabled, + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": enabled, }; if (env.FLOW_FILE_LOG_LEVEL) { try { const logLevel = validateLogLevel(env.FLOW_FILE_LOG_LEVEL); - fileChannel['fsvc:logLevel'] = logLevel; + fileChannel["fsvc:logLevel"] = logLevel; } catch (error) { // Re-throw ConfigError, but wrap other errors if (error instanceof ConfigError) { @@ -104,7 +118,7 @@ export function loadEnvConfig(): ServiceConfigInput { } if (env.FLOW_FILE_LOG_PATH) { - fileChannel['fsvc:logFilePath'] = env.FLOW_FILE_LOG_PATH; + fileChannel["fsvc:logFilePath"] = env.FLOW_FILE_LOG_PATH; } // Parse log retention settings @@ -113,36 +127,36 @@ export function loadEnvConfig(): ServiceConfigInput { if ( !isNaN(retentionDays) && retentionDays > 0 && retentionDays <= 365 ) { - fileChannel['fsvc:logRetentionDays'] = retentionDays; + fileChannel["fsvc:logRetentionDays"] = retentionDays; } } if (env.FLOW_LOG_MAX_FILES) { const maxFiles = parseInt(env.FLOW_LOG_MAX_FILES, 10); if (!isNaN(maxFiles) && maxFiles > 0 && maxFiles <= 100) { - fileChannel['fsvc:logMaxFiles'] = maxFiles; + fileChannel["fsvc:logMaxFiles"] = maxFiles; } } if (env.FLOW_LOG_MAX_FILE_SIZE) { const maxFileSize = parseInt(env.FLOW_LOG_MAX_FILE_SIZE, 10); if (!isNaN(maxFileSize) && maxFileSize >= 1048576) { // At least 1MB - fileChannel['fsvc:logMaxFileSize'] = maxFileSize; + fileChannel["fsvc:logMaxFileSize"] = maxFileSize; } } if (env.FLOW_LOG_ROTATION_INTERVAL) { const interval = env.FLOW_LOG_ROTATION_INTERVAL.toLowerCase().trim(); - if (['daily', 'weekly', 'monthly', 'size-based'].includes(interval)) { - fileChannel['fsvc:logRotationInterval'] = interval as - | 'daily' - | 'weekly' - | 'monthly' - | 'size-based'; + if (["daily", "weekly", "monthly", "size-based"].includes(interval)) { + fileChannel["fsvc:logRotationInterval"] = interval as + | "daily" + | "weekly" + | "monthly" + | "size-based"; } } - loggingConfig['fsvc:hasFileChannel'] = fileChannel; + loggingConfig["fsvc:hasFileChannel"] = fileChannel; hasLoggingConfig = true; } } @@ -152,29 +166,31 @@ export function loadEnvConfig(): ServiceConfigInput { const enabled = parseBoolean(env.FLOW_SENTRY_ENABLED); if (enabled !== undefined) { const sentryChannel: Record = { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': enabled, + "@id": "_service-config/loggingConfig/sentryChannel", + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": enabled, }; if (env.FLOW_SENTRY_DSN) { - sentryChannel['fsvc:sentryDsn'] = env.FLOW_SENTRY_DSN; + sentryChannel["fsvc:sentryDsn"] = env.FLOW_SENTRY_DSN; } if (env.FLOW_SENTRY_LOGGING_ENABLED) { const loggingEnabled = parseBoolean(env.FLOW_SENTRY_LOGGING_ENABLED); if (loggingEnabled !== undefined) { - sentryChannel['fsvc:sentryLoggingEnabled'] = loggingEnabled; + sentryChannel["fsvc:sentryLoggingEnabled"] = loggingEnabled; } } - loggingConfig['fsvc:hasSentryChannel'] = sentryChannel; + loggingConfig["fsvc:hasSentryChannel"] = sentryChannel; hasLoggingConfig = true; } } if (hasLoggingConfig) { - loggingConfig['@type'] = 'fsvc:LoggingConfig'; - configInput['fsvc:hasLoggingConfig'] = loggingConfig; + loggingConfig["@id"] = "_service-config/loggingConfig"; + loggingConfig["@type"] = "fsvc:LoggingConfig"; + configInput["fsvc:hasLoggingConfig"] = loggingConfig; } // Contained services @@ -184,7 +200,7 @@ export function loadEnvConfig(): ServiceConfigInput { if (env.FLOW_API_ENABLED) { const enabled = parseBoolean(env.FLOW_API_ENABLED); if (enabled !== undefined) { - containedServices['fsvc:apiEnabled'] = enabled; + containedServices["fsvc:apiEnabled"] = enabled; hasContainedServices = true; } } @@ -192,14 +208,15 @@ export function loadEnvConfig(): ServiceConfigInput { if (env.FLOW_SPARQL_ENABLED) { const enabled = parseBoolean(env.FLOW_SPARQL_ENABLED); if (enabled !== undefined) { - containedServices['fsvc:sparqlEnabled'] = enabled; + containedServices["fsvc:sparqlEnabled"] = enabled; hasContainedServices = true; } } if (hasContainedServices) { - containedServices['@type'] = 'fsvc:ContainedServicesConfig'; - configInput['fsvc:hasContainedServices'] = containedServices; + containedServices["@id"] = "_service-config/containedServices"; + containedServices["@type"] = "fsvc:ContainedServicesConfig"; + configInput["fsvc:hasContainedServices"] = containedServices; } // Node defaults @@ -209,25 +226,26 @@ export function loadEnvConfig(): ServiceConfigInput { if (env.FLOW_DEFAULT_VERSIONING) { const enabled = parseBoolean(env.FLOW_DEFAULT_VERSIONING); if (enabled !== undefined) { - nodeDefaults['conf:versioningEnabled'] = enabled; + nodeDefaults["conf:versioningEnabled"] = enabled; hasNodeDefaults = true; } } if (env.FLOW_DEFAULT_FORMATS) { - const formats = env.FLOW_DEFAULT_FORMATS.split(',').map((f) => f.trim()) + const formats = env.FLOW_DEFAULT_FORMATS.split(",").map((f) => f.trim()) .filter((f) => f); if (formats.length > 0) { - nodeDefaults['conf:distributionFormats'] = formats; + nodeDefaults["conf:distributionFormats"] = formats; hasNodeDefaults = true; } } if (hasNodeDefaults) { - nodeDefaults['@type'] = 'conf:NodeConfig'; - configInput['fsvc:nodeDefaults'] = nodeDefaults; + nodeDefaults["@id"] = "_service-config/meshRootNodeConfig"; + nodeDefaults["@type"] = "conf:MeshRootNodeConfig"; + configInput["fsvc:nodeDefaults"] = nodeDefaults; } - + //console.log(JSON.stringify(configInput)) return configInput; } @@ -238,48 +256,29 @@ export function loadEnvConfig(): ServiceConfigInput { */ function getEnvironmentVariables(): EnvironmentConfig { return { - FLOW_CONFIG_PATH: Deno.env.get('FLOW_CONFIG_PATH'), - FLOW_SERVICE_PORT: Deno.env.get('FLOW_SERVICE_PORT'), - FLOW_SERVICE_HOST: Deno.env.get('FLOW_SERVICE_HOST'), - FLOW_MESH_PATHS: Deno.env.get('FLOW_MESH_PATHS'), - FLOW_LOG_LEVEL: Deno.env.get('FLOW_LOG_LEVEL'), - FLOW_SENTRY_ENABLED: Deno.env.get('FLOW_SENTRY_ENABLED'), - FLOW_SENTRY_DSN: Deno.env.get('FLOW_SENTRY_DSN'), - FLOW_SENTRY_LOGGING_ENABLED: Deno.env.get('FLOW_SENTRY_LOGGING_ENABLED'), - FLOW_FILE_LOG_ENABLED: Deno.env.get('FLOW_FILE_LOG_ENABLED'), - FLOW_FILE_LOG_LEVEL: Deno.env.get('FLOW_FILE_LOG_LEVEL'), - FLOW_FILE_LOG_PATH: Deno.env.get('FLOW_FILE_LOG_PATH'), - FLOW_LOG_RETENTION_DAYS: Deno.env.get('FLOW_LOG_RETENTION_DAYS'), - FLOW_LOG_MAX_FILES: Deno.env.get('FLOW_LOG_MAX_FILES'), - FLOW_LOG_MAX_FILE_SIZE: Deno.env.get('FLOW_LOG_MAX_FILE_SIZE'), - FLOW_LOG_ROTATION_INTERVAL: Deno.env.get('FLOW_LOG_ROTATION_INTERVAL'), - FLOW_DEFAULT_VERSIONING: Deno.env.get('FLOW_DEFAULT_VERSIONING'), - FLOW_DEFAULT_FORMATS: Deno.env.get('FLOW_DEFAULT_FORMATS'), - FLOW_API_ENABLED: Deno.env.get('FLOW_API_ENABLED'), - FLOW_SPARQL_ENABLED: Deno.env.get('FLOW_SPARQL_ENABLED'), + "FLOW_CONFIG_PATH": Deno.env.get("FLOW_CONFIG_PATH"), + "FLOW_SERVICE_SCHEME": Deno.env.get("FLOW_SERVICE_SCHEME"), + "FLOW_SERVICE_PORT": Deno.env.get("FLOW_SERVICE_PORT"), + "FLOW_SERVICE_HOST": Deno.env.get("FLOW_SERVICE_HOST"), + "FLOW_MESH_PATHS": Deno.env.get("FLOW_MESH_PATHS"), + "FLOW_LOG_LEVEL": Deno.env.get("FLOW_LOG_LEVEL"), + "FLOW_SENTRY_ENABLED": Deno.env.get("FLOW_SENTRY_ENABLED"), + "FLOW_SENTRY_DSN": Deno.env.get("FLOW_SENTRY_DSN"), + "FLOW_SENTRY_LOGGING_ENABLED": Deno.env.get("FLOW_SENTRY_LOGGING_ENABLED"), + "FLOW_FILE_LOG_ENABLED": Deno.env.get("FLOW_FILE_LOG_ENABLED"), + "FLOW_FILE_LOG_LEVEL": Deno.env.get("FLOW_FILE_LOG_LEVEL"), + "FLOW_FILE_LOG_PATH": Deno.env.get("FLOW_FILE_LOG_PATH"), + "FLOW_LOG_RETENTION_DAYS": Deno.env.get("FLOW_LOG_RETENTION_DAYS"), + "FLOW_LOG_MAX_FILES": Deno.env.get("FLOW_LOG_MAX_FILES"), + "FLOW_LOG_MAX_FILE_SIZE": Deno.env.get("FLOW_LOG_MAX_FILE_SIZE"), + "FLOW_LOG_ROTATION_INTERVAL": Deno.env.get("FLOW_LOG_ROTATION_INTERVAL"), + "FLOW_DEFAULT_VERSIONING": Deno.env.get("FLOW_DEFAULT_VERSIONING"), + "FLOW_DEFAULT_FORMATS": Deno.env.get("FLOW_DEFAULT_FORMATS"), + "FLOW_API_ENABLED": Deno.env.get("FLOW_API_ENABLED"), + "FLOW_SPARQL_ENABLED": Deno.env.get("FLOW_SPARQL_ENABLED"), }; } -/** - * 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. @@ -293,15 +292,15 @@ function parseBoolean(value: string): boolean | undefined { const normalized = value.toLowerCase().trim(); switch (normalized) { - case 'true': - case '1': - case 'yes': - case 'on': + case "true": + case "1": + case "yes": + case "on": return true; - case 'false': - case '0': - case 'no': - case 'off': + case "false": + case "0": + case "no": + case "off": return false; default: return undefined; @@ -317,5 +316,5 @@ function parseBoolean(value: string): boolean | undefined { export function getServiceConfigPath( cliConfigPath?: string, ): string | undefined { - return cliConfigPath || Deno.env.get('FLOW_CONFIG_PATH'); + return cliConfigPath || Deno.env.get("FLOW_CONFIG_PATH"); } diff --git a/flow-service/src/config/loaders/jsonld-loader.ts b/flow-service/src/config/loaders/jsonld-loader.ts index c10a06b..2c04b89 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 { handleCaughtError } from '../../utils/logger.ts'; +import type { MeshRootNodeConfigInput, ServiceConfigInput } from '../config-types.ts'; +import { ConfigError } from '../config-types.ts'; +import { handleCaughtError } from '../../../../flow-core/src/utils/logger/error-handlers.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..dbe84c1 --- /dev/null +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -0,0 +1,94 @@ +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/quadstore-utils.ts'; +import { handleCaughtError } from '../../../../flow-core/src/utils/logger/error-handlers.ts'; +import { CONFIG_GRAPH_NAMES } from '../index.ts'; +import { expandRelativeIds } from "../../../../flow-core/src/utils/rdfjs-utils.ts"; +import { getCurrentServiceUri } from "../../utils/service-uri-builder.ts"; +/** + * Load platform defaults into Quadstore graphs + */ +export async function loadPlatformServiceDefaults(): Promise { + + try { + const uri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.platformServiceDefaults); + const expandedPlatformServiceDefaults = expandRelativeIds(PLATFORM_SERVICE_DEFAULTS, uri); + // use the Service URI for the graph name + await createNewGraphFromJsonLd(expandedPlatformServiceDefaults, { graphName: uri }); + } catch (error) { + await handleCaughtError( + error, + 'Error loading platform service defaults into Quadstore', + { + operation: 'config-load', + component: 'platform-default-loader', + configContext: { + configType: 'platform-defaults', + }, + } + ); + // Optionally rethrow or handle as needed + } +} + +export async function loadPlatformImplicitMeshRootNodeConfig(): Promise { + const uri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.platformImplicitMeshRootNodeConfig); + const expandedPlatformNodeDefaults = expandRelativeIds(PLATFORM_NODE_DEFAULTS, uri); + await createNewGraphFromJsonLd(expandedPlatformNodeDefaults, { graphName: uri }); +} + +/** + * Load input service config into Quadstore graph + */ +export async function loadInputServiceConfig(inputConfig: ServiceConfigInput): Promise { + //console.log(inputConfig); + const uri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.inputServiceConfig); + const expandedInputServiceConfig = expandRelativeIds(inputConfig, uri); + //console.log(expandedInputServiceConfig) + await createNewGraphFromJsonLd(expandedInputServiceConfig, { graphName: uri }); +} + +/** + * Load input node config overrides into Quadstore graph + */ +export async function loadInputMeshRootNodeConfig(inputMeshRootNodeConfig: MeshRootNodeConfigInput): Promise { + const uri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.inputMeshRootNodeConfig); + const expandedInputMeshRootNodeConfig = expandRelativeIds(inputMeshRootNodeConfig, uri); + await createNewGraphFromJsonLd(expandedInputMeshRootNodeConfig, { graphName: uri }); +} + +/** + * Merge input and platform graphs into merged service config graph + */ +export async function mergeServiceConfigGraphs( + { + store, + df, + } = defaultQuadstoreBundle +): Promise { + + const inputUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.inputServiceConfig); + const mergedUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig); + const defaultUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.platformServiceDefaults); + // Clear merged graph + await clearGraph(df.namedNode(mergedUri)); + + // Copy input service config quads + await copyGraph(df.namedNode(inputUri), df.namedNode(mergedUri)); + + // Copy platform service defaults quads if not overridden + const platformQuads = store.match(undefined, undefined, undefined, df.namedNode(defaultUri)); + for await (const q of platformQuads) { + const exists = await store.countQuads(q.subject, q.predicate, undefined, df.namedNode(mergedUri)); + if (exists === 0) { + console.log(`platform quad copied: ${q.subject.value} ${q.predicate.value} ${q.object.value} in graph ${mergedUri}`); + + await store.put(df.quad(q.subject, q.predicate, q.object, df.namedNode(mergedUri))); + } + } + + +} + + diff --git a/flow-service/src/config/node-config.default.jsonld b/flow-service/src/config/node-config.default.jsonld index 2a2ec9f..f153f77 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": [ @@ -20,6 +20,5 @@ "conf:generateUnifiedDataset": false, "conf:generateAggregatedDataset": false, "conf:generateResourcePages": true, - "conf:stylesheetPath": "/_assets/css/default-resource-page.css", - "conf:defaultAttribution": "https://example.com/some-agent" + "conf:stylesheetPath": "/_assets/css/default-resource-page.css" } 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..790edce --- /dev/null +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -0,0 +1,92 @@ +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 '../../../../flow-core/src/utils/logger/error-handlers.ts'; +import { createServiceLogContext } from '../../utils/service-log-context.ts'; +import { getCurrentServiceUri } from '../../utils/service-uri-builder.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) { + const context = createServiceLogContext({ + operation: 'config-query', + component: 'service-config-accessor', + metadata: { sparql } + }); + await handleCaughtError(error, 'Failed to execute SPARQL query', context); + throw new Error('Failed to execute SPARQL query'); + } + return undefined; + } + + async getPort(): Promise { + const sparql = ` + PREFIX fsvc: + SELECT ?value WHERE { + GRAPH <${getCurrentServiceUri(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 <${getCurrentServiceUri(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 <${getCurrentServiceUri(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-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index add0cdf..4f80fe6 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -1,79 +1,88 @@ -/** - * 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 { PLATFORM_SERVICE_DEFAULTS } from '../defaults.ts'; +import { ConfigError } from '../config-types.ts'; import { mergeConfigs } from '../../utils/merge-configs.ts'; -import { handleCaughtError } from '../../utils/logger.ts'; +import { handleCaughtError } from '../../../../flow-core/src/utils/logger/error-handlers.ts'; +import { LogContext } from '../../../../flow-core/src/utils/logger/types.ts'; +import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; +import { createServiceLogContext } from '../../utils/service-log-context.ts'; +import { serviceUriConfigManager, type ServiceUriConfig } from '../../utils/service-uri-builder.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 { loadPlatformServiceDefaults, loadInputServiceConfig, loadInputMeshRootNodeConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.ts'; + export async function resolveServiceConfig( cliOptions?: ServiceOptions, -): Promise { - try { - // 1. Start with empty input options - let inputOptions: ServiceConfigInput = {}; +): Promise { + // Define serviceConfigPath outside try block for error handling access + const serviceConfigPath = getServiceConfigPath(cliOptions?.configPath); - // 2. Merge environment variables (cascading pattern) + try { + // Load environment config const envConfig = loadEnvConfig(); - inputOptions = mergeConfigs(inputOptions, envConfig); - - // 3. Load and merge service config file (cascading pattern) - const serviceConfigPath = getServiceConfigPath(cliOptions?.configPath); + //console.log(envConfig) + // Load file config if specified + 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); - // 6. Return side-by-side context (no merge) - return { - inputOptions, - defaultOptions, + // Extract service URI configuration from merged config, using platform defaults as fallback + const serviceUriConfig: ServiceUriConfig = { + scheme: mergedInputConfig['fsvc:scheme'] ?? PLATFORM_SERVICE_DEFAULTS['fsvc:scheme'], + host: mergedInputConfig['fsvc:host'] ?? PLATFORM_SERVICE_DEFAULTS['fsvc:host'], + port: mergedInputConfig['fsvc:port'] ?? PLATFORM_SERVICE_DEFAULTS['fsvc:port'], }; + + // Initialize the service URI configuration manager early, before any quadstore operations + serviceUriConfigManager.setConfig(serviceUriConfig); + + // Load platform defaults into Quadstore graphs + await loadPlatformServiceDefaults(); + + // 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(); + } catch (error) { + const context: LogContext = createServiceLogContext({ + operation: 'config-resolve', + component: 'service-config-resolution', + configContext: { + configPath: serviceConfigPath || 'none', + configType: 'service-config' + } + }); + if (error instanceof ConfigError) { - await handleCaughtError(error, `Service configuration resolution failed`); + await handleCaughtError(error, `Service configuration resolution failed`, context); throw error; } - await handleCaughtError(error, `Failed to resolve service configuration`); + await handleCaughtError(error, `Failed to resolve service configuration`, context); const errorMessage = error instanceof Error ? error.message : String(error); const cause = error instanceof Error ? error : undefined; throw new ConfigError( @@ -83,23 +92,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. * @@ -113,6 +105,10 @@ function convertCliOptionsToConfig( ): ServiceConfigInput { const config: ServiceConfigInput = {}; + if (cliOptions.scheme !== undefined) { + config['fsvc:scheme'] = cliOptions.scheme; + } + if (cliOptions.port !== undefined) { config['fsvc:port'] = cliOptions.port; } @@ -154,172 +150,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..cc98c29 --- /dev/null +++ b/flow-service/src/config/resolution/service-config-validator.ts @@ -0,0 +1,43 @@ + +import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; +import { ConfigError } from '../config-types.ts'; +import { CONFIG_GRAPH_NAMES } from '../index.ts'; +import { getCurrentServiceUri } from '../../utils/service-uri-builder.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(getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)); + // Validate port + const portQuads = store.match(undefined, df.namedNode('https://semantic-flow.github.io/ontology/flow-service/port'), undefined, graph); + let count = 0; + + //console.log(`Validating port in graph ${graph.value}`); + for await (const quad of portQuads) { + //console.log(`quad matched: ${quad.subject.value} ${quad.predicate.value} ${quad.object.value} in graph ${graph.value}`); + if (count > 0) { + throw new ConfigError('Multiple port definitions found in service configuration.'); + } + count++; + 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) { + 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/config/types.ts b/flow-service/src/config/types.ts deleted file mode 100644 index 0125aba..0000000 --- a/flow-service/src/config/types.ts +++ /dev/null @@ -1,273 +0,0 @@ -/** - * Configuration Types for Semantic Flow Service - * - * Based on JSON-LD ontologies with side-by-side configuration context pattern. - * Supports sparse input configurations that are validated against complete defaults. - */ - -// JSON-LD Context and Type Definitions -export interface JSONLDContext { - 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/'; - readonly flow: 'https://semantic-flow.github.io/ontology/flow/'; - 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 { - readonly '@type': 'fsvc:LogChannelConfig'; - readonly 'fsvc:logChannelEnabled': boolean; - readonly 'fsvc:logLevel': 'debug' | 'info' | 'warn' | 'error'; - readonly 'fsvc:logFormat'?: 'json' | 'pretty'; - readonly 'fsvc:logFilePath'?: string; - readonly 'fsvc:sentryDsn'?: string; - readonly 'fsvc:sentryLoggingEnabled'?: boolean; - readonly 'fsvc:logRetentionDays'?: number; - readonly 'fsvc:logMaxFiles'?: number; - readonly 'fsvc:logMaxFileSize'?: number; - readonly 'fsvc:logRotationInterval'?: - | 'daily' - | 'weekly' - | 'monthly' - | 'size-based'; -} - -export interface LoggingConfig { - readonly '@type': 'fsvc:LoggingConfig'; - readonly 'fsvc:hasConsoleChannel': LogChannelConfig; - readonly 'fsvc:hasFileChannel': LogChannelConfig; - readonly 'fsvc:hasSentryChannel': LogChannelConfig; -} - -// Contained Services Configuration -export interface ContainedServicesConfig { - readonly '@type': 'fsvc:ContainedServicesConfig'; - readonly 'fsvc:apiEnabled': boolean; - readonly 'fsvc:sparqlEnabled': boolean; - readonly 'fsvc:queryWidgetEnabled': boolean; - readonly 'fsvc:staticServerEnabled': boolean; - readonly 'fsvc:apiDocsEnabled': boolean; -} - -// Node Configuration Types (for service defaults) -export interface TemplateMapping { - readonly '@type': 'conf:TemplateMapping'; - readonly 'conf:hasResourcePageTemplate': string; -} - -export interface NodeConfig extends JSONLDBase { - readonly '@type': 'conf:NodeConfig'; - readonly 'conf:versioningEnabled': boolean; - readonly 'conf:distributionFormats': string[]; - readonly 'conf:templateMappings'?: TemplateMapping; - readonly 'conf:configInheritanceEnabled': boolean; - readonly 'conf:generateUnifiedDataset': boolean; - readonly 'conf:generateAggregatedDataset': boolean; - readonly 'conf:generateResourcePages'?: boolean; - readonly 'conf:stylesheetPath'?: string; - readonly 'conf:defaultAttribution'?: string; -} - -// Complete Service Configuration -export interface ServiceConfig extends JSONLDBase { - 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:defaultDelegationChain'?: DelegationChain; - readonly 'fsvc:defaultAttributedTo'?: AttributedTo; -} - -// Attribution Configuration -export interface AttributedTo { - readonly '@id': string; - [key: string]: unknown; -} - -// Delegation Chain Configuration -export interface DelegationStep { - readonly '@type': 'meta:DelegationStep'; - readonly 'meta:stepOrder': number; - readonly 'prov:agent': { - readonly '@id': string; - }; -} - -export interface DelegationChain { - readonly '@type': 'meta:DelegationChain'; - readonly 'meta:hasStep': DelegationStep[]; -} - -// Partial Types for Sparse Input Configuration (mutable for construction) -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:defaultDelegationChain'?: Partial; - 'fsvc:defaultAttributedTo'?: Partial; -} - -export interface NodeConfigInput extends Partial { - '@type'?: 'conf:NodeConfig'; - 'conf:versioningEnabled'?: boolean; - 'conf:distributionFormats'?: string[]; - 'conf:templateMappings'?: Partial; - 'conf:configInheritanceEnabled'?: boolean; - 'conf:generateUnifiedDataset'?: boolean; - 'conf:generateAggregatedDataset'?: boolean; - 'conf:generateResourcePages'?: boolean; - 'conf:stylesheetPath'?: string; - '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; -} - -// CLI Options Type -export interface ServiceOptions { - readonly configPath?: string; - readonly port?: number; - readonly host?: string; - readonly meshPaths?: string[]; - readonly logLevel?: LogLevel; - readonly sentryEnabled?: boolean; -} - -// Environment Variable Mapping -export interface EnvironmentConfig { - readonly FLOW_CONFIG_PATH?: string; - readonly FLOW_SERVICE_PORT?: string; - readonly FLOW_SERVICE_HOST?: string; - readonly FLOW_MESH_PATHS?: string; - readonly FLOW_LOG_LEVEL?: string; - readonly FLOW_SENTRY_ENABLED?: string; - readonly FLOW_SENTRY_DSN?: string; - readonly FLOW_SENTRY_LOGGING_ENABLED?: string; - readonly FLOW_FILE_LOG_ENABLED?: string; - readonly FLOW_FILE_LOG_LEVEL?: string; - readonly FLOW_FILE_LOG_PATH?: string; - readonly FLOW_LOG_RETENTION_DAYS?: string; - readonly FLOW_LOG_MAX_FILES?: string; - readonly FLOW_LOG_MAX_FILE_SIZE?: string; - readonly FLOW_LOG_ROTATION_INTERVAL?: string; - readonly FLOW_DEFAULT_VERSIONING?: string; - readonly FLOW_DEFAULT_FORMATS?: string; - readonly FLOW_API_ENABLED?: string; - readonly FLOW_SPARQL_ENABLED?: string; -} - -// Configuration Error Types -export class ConfigError extends Error { - constructor(message: string, public override readonly cause?: Error) { - super(message); - this.name = 'ConfigError'; - } -} - -export class ConfigValidationError extends ConfigError { - constructor( - message: string, - public readonly errors: string[], - cause?: Error, - ) { - super(message, cause); - this.name = 'ConfigValidationError'; - } -} - -// 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/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/health.ts b/flow-service/src/routes/health.ts index b35c3bb..9cacf76 100644 --- a/flow-service/src/routes/health.ts +++ b/flow-service/src/routes/health.ts @@ -1,5 +1,5 @@ import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi'; -import { logger } from '../utils/logger.ts'; +import { logger } from '../utils/service-logger.ts'; import { getUptimeInfo } from '../utils/uptime.ts'; const health = new OpenAPIHono(); @@ -36,8 +36,13 @@ const healthRoute = createRoute({ health.openapi(healthRoute, (c) => { logger.debug('Health check requested', { - userAgent: c.req.header('user-agent'), - ip: c.req.header('x-forwarded-for') || 'unknown', + operation: 'api-request', + apiContext: { + method: 'GET', + path: '/health', + userAgent: c.req.header('user-agent'), + ip: c.req.header('x-forwarded-for') || 'unknown', + }, }); const uptimeInfo = getUptimeInfo(); @@ -50,7 +55,10 @@ health.openapi(healthRoute, (c) => { uptime: uptimeInfo, }; - logger.info('Health check completed', { status: response.status }); + logger.info('Health check completed', { + operation: 'api-request', + metadata: { status: response.status }, + }); return c.json(response); }); diff --git a/flow-service/src/routes/meshes.ts b/flow-service/src/routes/meshes.ts index 51c5337..bc2dd9a 100644 --- a/flow-service/src/routes/meshes.ts +++ b/flow-service/src/routes/meshes.ts @@ -1,5 +1,5 @@ import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi'; -import { logger } from '../utils/logger.ts'; +import { logger } from '../utils/service-logger.ts'; import { getAssetsPath, getCurrentMetaDistPath, @@ -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 { initializeMeshRegistry, meshRegistry } 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,13 +131,16 @@ 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); } - logger.info(`Attempting to register mesh '${name}' at path: ${path}`); + logger.info(`Attempting to register mesh '${name}' at path: ${path}`, { + operation: 'api-request', + component: 'mesh-management', + metadata: { meshName: name, meshPath: path }, + }); try { const stats = await Deno.stat(path); @@ -166,6 +164,11 @@ export const createMeshesRoutes = ( if (!existsSync(join(path, '.git'))) { logger.info( `Mesh root folder '${path}' exists but is not under git control (no '.git' folder found).`, + { + operation: 'api-request', + component: 'mesh-management', + metadata: { meshName: name, meshPath: path }, + }, ); } @@ -281,7 +284,17 @@ export const createMeshesRoutes = ( const logMessage = isRootNode ? `Attempting to create node at mesh root (${meshName})` : `Attempting to create node in mesh '${meshName}' at path '${fileSystemNodePath}' (physical: ${meshParentPath})`; - logger.info(logMessage); + logger.info(logMessage, { + operation: 'api-request', + component: 'node-management', + metadata: { + meshName, + apiNodePath, + fileSystemNodePath, + nodeType, + isRootNode, + }, + }); const slug = apiNodePath.split(MESH.API_IDENTIFIER_PATH_SEPARATOR).pop() || meshName; @@ -335,7 +348,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..d526814 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 { logger } from '../utils/service-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,9 +90,16 @@ 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(', ') })`, + { + operation: 'weave', + component: 'weave-processor', + metadata: { + nodeSpecifier, + nodeSegments, + }, + }, ); try { @@ -108,6 +113,17 @@ export const createWeaveRoutes = ( } catch (error) { logger.error( `Weave process failed for node '${nodeSpecifier}': ${String(error)}`, + { + operation: 'weave', + component: 'weave-processor', + errorContext: { + errorType: 'WeaveError', + }, + metadata: { + nodeSpecifier, + error: String(error), + }, + }, ); return c.json({ error: 'Internal Server Error', diff --git a/flow-service/src/service-constants.ts b/flow-service/src/service-constants.ts new file mode 100644 index 0000000..888b57a --- /dev/null +++ b/flow-service/src/service-constants.ts @@ -0,0 +1,3 @@ +export const FLOW_SERVICE_VERSION = '0.1.0'; + +export const FLOW_SERVICE_NAME = 'flow-service'; diff --git a/flow-service/src/services/metadata-composer.ts b/flow-service/src/services/metadata-composer.ts index 431f662..7c22ac9 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,75 +8,73 @@ 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' + const title = typeof initialData.title === "string" ? initialData.title : slug; - const description = typeof initialData.description === 'string' + const description = typeof initialData.description === "string" ? initialData.description : `Node created for ${slug}`; - const attributedTo = config.defaultAttributedTo; - const delegationChain = config.defaultDelegationChain; + const attributedTo = "https://djradon.github.io/ns/djradon/"; // TODO: Use config or a default value + //const attributedTo = config.defaultAttributedTo; + //const delegationChain = config.defaultDelegationChain; const startTime = startedAtTime ?? new Date().toISOString(); const endTime = new Date().toISOString(); return { - '@context': { - 'owl': 'http://www.w3.org/2002/07/owl#', - 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', - 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#', - 'xsd': 'http://www.w3.org/2001/XMLSchema#', - 'dcterms': 'http://purl.org/dc/terms/', - 'prov': 'http://www.w3.org/ns/prov#', - 'dcat': 'http://www.w3.org/ns/dcat#', - 'meta': 'https://semantic-flow.github.io/ontology/meta-flow/', - 'mesh': 'https://semantic-flow.github.io/ontology/mesh/', - 'node': 'https://semantic-flow.github.io/ontology/node/', - 'flow': 'https://semantic-flow.github.io/ontology/flow/', + "@context": { + "owl": "http://www.w3.org/2002/07/owl#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "dcterms": "http://purl.org/dc/terms/", + "prov": "http://www.w3.org/ns/prov#", + "dcat": "http://www.w3.org/ns/dcat#", + "meta": "https://semantic-flow.github.io/ontology/meta-flow/", + "mesh": "https://semantic-flow.github.io/ontology/mesh/", + "node": "https://semantic-flow.github.io/ontology/node/", + "flow": "https://semantic-flow.github.io/ontology/flow/", }, - '@graph': [ + "@graph": [ { - '@id': `../../${slug}/_handle/`, - '@type': 'mesh:Node', - 'node:hasSlug': slug, - 'dcterms:title': title, - 'dcterms:description': description, + "@id": `../../${slug}/_handle/`, + "@type": "mesh:Node", + "node:hasSlug": slug, + "dcterms:title": title, + "dcterms:description": description, + "prov:wasAttributedTo": attributedTo, }, { - '@id': `../../${slug}/_handle/#`, - '@type': 'node:Handle', - 'mesh:relativeIdentifier': `../../${slug}/_handle/`, - 'dcterms:title': `${title} Handle`, - 'dcterms:description': `Handle for the ${slug} ${nodeType} node.`, - 'node:isHandleFor': { - '@id': `../../${slug}/_handle/`, + "@id": `../../${slug}/_handle/#`, + "@type": "node:Handle", + "mesh:relativeIdentifier": `../../${slug}/_handle/`, + "dcterms:title": `${title} Handle`, + "dcterms:description": `Handle for the ${slug} ${nodeType} node.`, + "node:isHandleFor": { + "@id": `../../${slug}/_handle/`, }, }, { - '@id': '#creation-activity', - '@type': 'meta:NodeCreation', - 'dcterms:title': `${title} Node Creation`, - 'dcterms:description': `Creation of the ${slug} ${nodeType} node.`, - 'prov:startedAtTime': startTime, - 'prov:endedAtTime': endTime, - 'prov:wasAssociatedWith': attributedTo, + "@id": "#creation-activity", + "@type": "meta:NodeCreation", + "dcterms:title": `${title} Node Creation`, + "dcterms:description": `Creation of the ${slug} ${nodeType} node.`, + "prov:startedAtTime": startTime, + "prov:endedAtTime": endTime, + "prov:wasAssociatedWith": attributedTo, }, { - '@id': '#creation-context', - '@type': 'meta:ProvenanceContext', - 'meta:forActivity': { '@id': '#creation-activity' }, - 'prov:wasAttributedTo': attributedTo, - 'meta:delegationChain': delegationChain, + "@id": "#creation-context", + "@type": "meta:ProvenanceContext", + "meta:forActivity": { "@id": "#creation-activity" }, }, ], }; diff --git a/flow-service/src/utils/errors.ts b/flow-service/src/utils/errors.ts index 9e7ac8b..fe136dd 100644 --- a/flow-service/src/utils/errors.ts +++ b/flow-service/src/utils/errors.ts @@ -1,4 +1,5 @@ -import { handleError, logger } from './logger.ts'; +import { logger } from './service-logger.ts'; +import { handleError } from '../../../flow-core/src/utils/logger/error-handlers.ts'; // Define custom error types (simplified from your weave implementation) export class FlowServiceError extends Error { @@ -30,22 +31,40 @@ export class ConfigurationError extends FlowServiceError { } } -// Enhanced error handler with automatic Sentry integration +// Enhanced error handler export function handleServiceError( error: unknown, context?: string, meta?: Record, ): void { if (error instanceof FlowServiceError) { - // Log structured error with context - logger.error(`${context ? context + ': ' : ''}${error.message}`, error, { - code: error.code, - context: error.context, - ...meta, - }); + // Log structured error with context using basic logger interface + logger.error( + `${context ? context + ': ' : ''}${error.message}`, + { + operation: context || 'service-error', + component: 'error-handler', + errorContext: { + errorCode: error.code, + errorType: error.name, + stackTrace: error.stack, + originalError: error, + }, + metadata: { + field: error instanceof ValidationError ? error.field : undefined, + originalContext: error.context, + ...meta, + }, + } + ); } else { - // Use the base error handler from logger - handleError(error, context, meta); + // Use the base error handler from logger with proper parameters + const logContext = { + operation: context || 'service-error', + component: 'error-handler', + metadata: meta, + }; + handleError(error, context, logContext); } } diff --git a/flow-service/src/utils/logger.ts b/flow-service/src/utils/logger.ts deleted file mode 100644 index 1cd62a5..0000000 --- a/flow-service/src/utils/logger.ts +++ /dev/null @@ -1,668 +0,0 @@ -import * as Sentry from 'npm:@sentry/deno'; -import { dirname, ensureDir } from '../../../flow-core/src/deps.ts'; - -// Environment-based configuration -const isDevelopment = Deno.env.get('FLOW_ENV') !== 'production'; -const logLevel = Deno.env.get('FLOW_LOG_LEVEL') || - (isDevelopment ? 'DEBUG' : 'INFO'); - -// Log levels (inspired by your weave implementation) -export const LogLevels = { - DEBUG: 10, - INFO: 20, - WARN: 30, - ERROR: 40, - CRITICAL: 50, -} as const; - -type LogLevel = keyof typeof LogLevels; - -// Structured logging interfaces -export interface LogContext { - // Operation tracking - operation?: - | 'scan' - | 'weave' - | 'validate' - | 'config-resolve' - | 'api-request' - | 'startup' - | 'error-handling'; - requestId?: string; - - // Mesh/Node context - meshPath?: string; - nodePath?: string; - nodeType?: 'data' | 'namespace' | 'reference'; - - // Performance metrics - duration?: number; - startTime?: number; - nodeCount?: number; - fileCount?: number; - - // Configuration context - configSource?: 'file' | 'inheritance' | 'defaults' | 'api' | 'environment'; - schemaVersion?: string; - - // Error context - errorCode?: string; - violations?: string[]; - cause?: string; - - // API context - endpoint?: string; - statusCode?: number; - userAgent?: string; - - // Service context - component?: - | 'mesh-scanner' - | 'api-handler' - | 'config-resolver' - | 'weave-processor'; - - // Arbitrary additional context - [key: string]: unknown; -} - -export interface StructuredLogger { - debug(message: string, context?: LogContext): Promise; - info(message: string, context?: LogContext): Promise; - warn(message: string, context?: LogContext): Promise; - error(message: string, error?: Error, context?: LogContext): Promise; - critical(message: string, error?: Error, context?: LogContext): Promise; - - // Contextual logger factories - withContext(baseContext: LogContext): StructuredLogger; -} - -// File logging functionality -class FileLogger { - private logFile: string; - private isWriting = false; - private writeQueue: string[] = []; - private readonly maxQueueSize = 10000; // Configurable limit - - constructor(logFilePath: string) { - this.logFile = logFilePath; - } - - async ensureLogDirectory(): Promise { - try { - const dir = dirname(this.logFile); - await ensureDir(dir); - } catch (error) { - console.error(`Failed to create log directory: ${error}`); - } - } - - async writeToFile(content: string): Promise { - if (this.writeQueue.length >= this.maxQueueSize) { - console.error( - `Log queue full (${this.maxQueueSize} items), dropping oldest entries`, - ); - this.writeQueue = this.writeQueue.slice( - -Math.floor(this.maxQueueSize / 2), - ); - } - this.writeQueue.push(content); - if (!this.isWriting) { - await this.processWriteQueue(); - } - } - - private async processWriteQueue(): Promise { - if (this.writeQueue.length === 0) return; - - this.isWriting = true; - - try { - await this.ensureLogDirectory(); - - const content = this.writeQueue.join('\n') + '\n'; - this.writeQueue = []; - - await Deno.writeTextFile(this.logFile, content, { append: true }); - } catch (error) { - console.error(`Failed to write to log file: ${error}`); - // Graceful degradation - continue without crashing - } finally { - this.isWriting = false; - - // Process any new entries that were added while writing - if (this.writeQueue.length > 0) { - await this.processWriteQueue(); - } - } - } - - async rotateIfNeeded(): Promise { - try { - const stats = await Deno.stat(this.logFile); - const now = new Date(); - const fileDate = new Date(stats.mtime || stats.birthtime || now); - - // Check if file is from a different day - if (fileDate.toDateString() !== now.toDateString()) { - await this.performDailyRotation(); - } - } catch (error) { - // File doesn't exist yet, no rotation needed - if (!(error instanceof Deno.errors.NotFound)) { - console.error(`Failed to check log file for rotation: ${error}`); - } - } - } - - private async performDailyRotation(): Promise { - try { - const now = new Date(); - const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000); - const rotatedName = this.logFile.replace( - '.log', - `-${yesterday.toISOString().split('T')[0]}.log`, - ); - - await Deno.rename(this.logFile, rotatedName); - console.log(`Log rotated to: ${rotatedName}`); - } catch (error) { - console.error(`Failed to rotate log file: ${error}`); - } - } -} - -// Global file logger instance -let fileLogger: FileLogger | null = null; - -async function getFileLogger(): Promise { - if (!fileLogger) { - try { - // Check environment variable first to avoid config dependency during logger initialization - const fileLogPath = Deno.env.get('FLOW_FILE_LOG_PATH'); - const fileLogEnabled = Deno.env.get('FLOW_FILE_LOG_ENABLED'); - - if (fileLogEnabled === 'true' && fileLogPath) { - fileLogger = new FileLogger(fileLogPath); - await fileLogger.rotateIfNeeded(); - } - } catch (error) { - // Use console.error here to avoid circular dependency with handleCaughtError - console.error(`Failed to initialize file logger: ${error}`); - } - } - return fileLogger; -} - -// Initialize Sentry (only in production or when explicitly configured) -const sentryDsn = Deno.env.get('FLOW_SENTRY_DSN'); -let sentryInitialized = false; - -// Formatters for structured logging -function formatStructuredMessage( - level: LogLevel, - message: string, - context?: LogContext, -): string { - const timestamp = new Date().toISOString(); - const baseEntry = { - timestamp, - level: level.toLowerCase(), - message, - service: 'flow-service', - version: Deno.env.get('FLOW_VERSION'), - environment: Deno.env.get('FLOW_ENV') || 'development', - ...context, - }; - - return JSON.stringify(baseEntry); -} - -function formatConsoleMessage( - level: LogLevel, - message: string, - context?: LogContext, -): string { - const timestamp = new Date().toISOString(); - const prefix = `[${timestamp}] ${level.padEnd(5)}`; - - if (isDevelopment) { - const coloredLevel = level === 'ERROR' || level === 'CRITICAL' - ? colorize('red', level) - : level === 'WARN' - ? colorize('yellow', level) - : level === 'DEBUG' - ? colorize('blue', level) - : colorize('green', level); - - const coloredPrefix = colorize('gray', `[${timestamp}]`) + - ` ${coloredLevel.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.component) parts.push(`[${context.component}]`); - - contextStr = parts.length > 0 ? ` (${parts.join(', ')})` : ''; - } - - return `${coloredPrefix} ${message}${contextStr}`; - } - - return context - ? `${prefix} ${message} ${JSON.stringify(context)}` - : `${prefix} ${message}`; -} - -async function writeToAllChannels( - level: LogLevel, - message: string, - context?: LogContext, - error?: Error, -): Promise { - // Write to console - const consoleFormatted = formatConsoleMessage(level, message, context); - - switch (level) { - case 'DEBUG': - case 'INFO': - console.log(consoleFormatted); - break; - case 'WARN': - console.warn(consoleFormatted); - break; - case 'ERROR': - case 'CRITICAL': - console.error(consoleFormatted); - break; - } - - // Write to file if enabled - use fallback to avoid circular dependencies - const fileLoggerInstance = await getFileLogger(); - if (fileLoggerInstance) { - try { - await fileLoggerInstance.rotateIfNeeded(); - - // Use JSON format as fallback to avoid config dependency loops - const fileFormatted = formatStructuredMessage(level, message, context); - await fileLoggerInstance.writeToFile(fileFormatted); - } catch (err) { - console.error(`Failed to write to file log: ${err}`); - } - } - - // Write to Sentry if enabled - use basic check to avoid circular dependencies - if (sentryInitialized) { - try { - if (error) { - // Error reporting - Sentry.captureException(error, { - level: level.toLowerCase() as - | 'debug' - | 'info' - | 'warning' - | 'error' - | 'fatal', - tags: { - source: 'flow-service', - component: context?.component, - operation: context?.operation, - }, - extra: { message, ...context }, - }); - } else { - // Log message sending - Sentry.captureMessage( - message, - level.toLowerCase() as - | 'debug' - | 'info' - | 'warning' - | 'error' - | 'fatal', - ); - if (context) { - Sentry.setContext(`${level.toLowerCase()}_context`, context); - } - } - } catch (err) { - console.error(`Failed to write to Sentry: ${err}`); - } - } -} - -export function initSentry(dsn?: string) { - const targetDsn = dsn || sentryDsn; - const sentryEnabled = Deno.env.get('FLOW_SENTRY_ENABLED') === 'true' && - !!targetDsn; - - console.log(`šŸ”§ Sentry enabled in config: ${sentryEnabled}`); - - if (sentryEnabled && !sentryInitialized) { - console.log(`šŸ”§ Initializing Sentry...`); - try { - Sentry.init({ - dsn: targetDsn, - environment: isDevelopment ? 'development' : 'production', - debug: isDevelopment, - tracesSampleRate: isDevelopment ? 1.0 : 0.1, - // Enable logs to be sent to Sentry (experimental feature) - _experiments: { enableLogs: true }, - }); - sentryInitialized = true; - console.log(`šŸ”§ DEBUG: Sentry initialized successfully`); - } catch (error) { - // Use console.error here to avoid circular dependency with handleCaughtError - console.error(`šŸ”§ ERROR: Failed to initialize Sentry: ${error}`); - console.error(`šŸ”§ ERROR: Sentry will remain disabled`); - } - } else if (Deno.env.get('FLOW_SENTRY_ENABLED') !== 'true') { - console.log(`šŸ”§ Sentry disabled - FLOW_SENTRY_ENABLED is not 'true'`); - } else if (!targetDsn) { - console.log(`šŸ”§ Sentry disabled - no DSN provided`); - } else { - console.log(`šŸ”§ Sentry already initialized, skipping`); - } -} - -// Console colors (simplified from your weave implementation) -const colors = { - reset: '\x1b[0m', - red: '\x1b[31m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - green: '\x1b[32m', - gray: '\x1b[90m', - bold: '\x1b[1m', -}; - -function colorize(color: keyof typeof colors, text: string): string { - if (!isDevelopment) return text; - return `${colors[color]}${text}${colors.reset}`; -} - -function shouldLog(level: LogLevel): boolean { - const currentLevel = LogLevels[logLevel as LogLevel] || LogLevels.INFO; - return LogLevels[level] >= currentLevel; -} - -function safeSpread(obj?: unknown): Record { - if (!obj || typeof obj !== 'object') return {}; - return obj as Record; -} - -// Structured logger implementation -class StructuredLoggerImpl implements StructuredLogger { - constructor(private baseContext: LogContext = {}) {} - - async debug(message: string, context?: LogContext): Promise { - if (!shouldLog('DEBUG')) return; - const mergedContext = { ...this.baseContext, ...context }; - await writeToAllChannels('DEBUG', message, mergedContext); - } - - async info(message: string, context?: LogContext): Promise { - if (!shouldLog('INFO')) return; - const mergedContext = { ...this.baseContext, ...context }; - await writeToAllChannels('INFO', message, mergedContext); - } - - async warn(message: string, context?: LogContext): Promise { - if (!shouldLog('WARN')) return; - const mergedContext = { ...this.baseContext, ...context }; - await writeToAllChannels('WARN', message, mergedContext); - } - - async error( - message: string, - error?: Error, - context?: LogContext, - ): Promise { - if (!shouldLog('ERROR')) return; - const mergedContext = { ...this.baseContext, ...context }; - await writeToAllChannels('ERROR', message, mergedContext, error); - } - - async critical( - message: string, - error?: Error, - context?: LogContext, - ): Promise { - const mergedContext = { ...this.baseContext, ...context }; - await writeToAllChannels('CRITICAL', message, mergedContext, error); - } - - withContext(baseContext: LogContext): StructuredLogger { - return new StructuredLoggerImpl({ ...this.baseContext, ...baseContext }); - } -} - -// Create main logger instance -export const logger = new StructuredLoggerImpl(); - -// Enhanced error handler (inspired by your weave handleCaughtError) -export async function handleError( - error: unknown, - context?: string, - meta?: Record, -): Promise { - const contextMessage = context ? `${context}: ` : ''; - - if (error instanceof Error) { - await logger.error(`${contextMessage}${error.message}`, error, { - stack: error.stack, - cause: error.cause ? String(error.cause) : undefined, - name: error.name, - ...safeSpread(meta), - }); - } else { - await logger.error(`${contextMessage}Unknown error occurred`, undefined, { - error: String(error), - ...safeSpread(meta), - }); - } -} -/** - * Comprehensive error handler following weave application pattern. - * Handles different error types with detailed logging including stack traces, causes, and context. - * - * @param e - The caught error of unknown type - * @param customMessage - Optional custom message to prefix error details - */ -export async function handleCaughtError( - e: unknown, - customMessage?: string, -): Promise { - // Format error message with context if provided - const formatErrorMsg = (errorType: string, msg: string) => { - if (customMessage) { - return `${customMessage} ${errorType}: ${msg}`; - } - return `${errorType}: ${msg}`; - }; - - // Log detailed error information - const logDetailedError = async (error: Error, type = 'Error') => { - await logger.error(formatErrorMsg(type, error.message), error, { - errorType: type, - errorName: error.name, - customMessage, - }); - - // Log stack trace if available - if (error.stack) { - await logger.debug('Stack trace:', { - stack: error.stack, - operation: 'error-handling', - }); - } - - // Log cause if available - if (error.cause) { - await logger.debug('Caused by:', { - cause: Deno.inspect(error.cause, { colors: false }), - operation: 'error-handling', - }); - } - - // Log additional error details - await logger.debug('Error details:', { - errorDetails: Deno.inspect(error, { colors: false }), - operation: 'error-handling', - }); - }; - - // Handle specific error types - try { - if (e instanceof Error) { - // Check for custom Flow Service error types first - if (e.name === 'FlowServiceError') { - const fsError = e as Error & { code?: string; context?: unknown }; - await logger.error(formatErrorMsg('FlowServiceError', e.message), e, { - errorType: 'FlowServiceError', - errorCode: fsError.code, - errorContext: fsError.context, - customMessage, - }); - - if (e.stack) { - await logger.debug('FlowServiceError stack trace:', { - stack: e.stack, - operation: 'error-handling', - }); - } - - if (fsError.context) { - await logger.debug('FlowServiceError context:', { - context: Deno.inspect(fsError.context, { colors: false }), - operation: 'error-handling', - }); - } - } else if (e.name === 'ValidationError') { - const valError = e as Error & { - code?: string; - field?: string; - context?: unknown; - }; - await logger.error(formatErrorMsg('ValidationError', e.message), e, { - errorType: 'ValidationError', - errorCode: valError.code, - field: valError.field, - errorContext: valError.context, - customMessage, - }); - - if (e.stack) { - await logger.debug('ValidationError stack trace:', { - stack: e.stack, - operation: 'error-handling', - }); - } - } else if ( - e.name === 'ConfigurationError' || e.name === 'ConfigError' || - e.name === 'ConfigValidationError' - ) { - const configError = e as Error & { - code?: string; - errors?: string[]; - context?: unknown; - }; - await logger.error(formatErrorMsg('ConfigurationError', e.message), e, { - errorType: e.name, - errorCode: configError.code, - errors: configError.errors, // For ConfigValidationError - errorContext: configError.context, - customMessage, - }); - - if (e.stack) { - await logger.debug('ConfigurationError stack trace:', { - stack: e.stack, - operation: 'error-handling', - }); - } - - if (configError.errors) { - await logger.debug('Configuration validation errors:', { - validationErrors: configError.errors, - operation: 'error-handling', - }); - } - } else if (e.name === 'TypeError') { - await logDetailedError(e, 'TypeError'); - } else if (e.name === 'ReferenceError') { - await logDetailedError(e, 'ReferenceError'); - } else if (e.name === 'SyntaxError') { - await logDetailedError(e, 'SyntaxError'); - } else if (e.name === 'RangeError') { - await logDetailedError(e, 'RangeError'); - } else { - // Generic Error handling - await logDetailedError(e, 'Error'); - } - } else if (typeof e === 'string') { - // Handle string errors - await logger.error(formatErrorMsg('StringError', e), undefined, { - errorType: 'StringError', - errorValue: e, - customMessage, - }); - } else if (typeof e === 'number') { - // Handle numeric errors - await logger.error(formatErrorMsg('NumericError', String(e)), undefined, { - errorType: 'NumericError', - errorValue: e, - customMessage, - }); - } else if (e === null) { - // Handle null errors - await logger.error( - formatErrorMsg('NullError', 'null value thrown'), - undefined, - { - errorType: 'NullError', - customMessage, - }, - ); - } else if (e === undefined) { - // Handle undefined errors - await logger.error( - formatErrorMsg('UndefinedError', 'undefined value thrown'), - undefined, - { - errorType: 'UndefinedError', - customMessage, - }, - ); - } else { - // Handle any other unknown error types - await logger.error( - formatErrorMsg('UnknownError', 'Unknown error type'), - undefined, - { - errorType: 'UnknownError', - errorValue: Deno.inspect(e, { colors: false }), - customMessage, - }, - ); - - // Log detailed inspection of unknown error - await logger.debug('Unknown error details:', { - unknownError: Deno.inspect(e, { colors: false, depth: 3 }), - operation: 'error-handling', - }); - } - } catch (loggingError) { - // Fallback error handling if logging itself fails - console.error('Failed to log error properly:', loggingError); - console.error('Original error was:', e); - } -} - -// Convenience functions for common patterns -export const log = logger; // Alias for shorter usage -export { Sentry }; // Re-export for direct Sentry usage when needed diff --git a/flow-service/src/utils/mesh-utils.ts b/flow-service/src/utils/mesh-utils.ts index 39da494..b7028be 100644 --- a/flow-service/src/utils/mesh-utils.ts +++ b/flow-service/src/utils/mesh-utils.ts @@ -1,12 +1,12 @@ -import { ServiceConfigAccessor } from '../config/index.ts'; -import { logger } from './logger.ts'; +import { logger } from './service-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; +// Global mesh registry +export const meshRegistry: Record = {}; + +export const initializeMeshRegistry = async () => { + const meshPaths = await config.getMeshPaths(); if (meshPaths && Array.isArray(meshPaths)) { for (const meshPath of meshPaths) { const meshName = basename(meshPath); @@ -15,6 +15,11 @@ export const initializeMeshRegistry = ( if (!existsSync(meshPath)) { logger.info( `Mesh path '${meshPath}' does not exist. Skipping registration.`, + { + operation: 'mesh-scan', + component: 'mesh-scanner', + metadata: { meshPath }, + }, ); continue; } @@ -22,14 +27,27 @@ export const initializeMeshRegistry = ( if (!existsSync(`${meshPath}/.git`)) { logger.info( `Mesh root folder '${meshPath}' exists but no '.git' folder found.`, + { + operation: 'mesh-scan', + component: 'mesh-scanner', + metadata: { meshPath }, + }, ); } 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}'.`, + { + operation: 'mesh-scan', + component: 'mesh-scanner', + metadata: { + meshName, + existingPath: meshRegistry[meshName], + skippedPath: meshPath, + }, + }, ); continue; } @@ -37,6 +55,11 @@ export const initializeMeshRegistry = ( meshRegistry[meshName] = parentDir; logger.info( `Discovered mesh '${meshName}' from configuration at path: ${parentDir}`, + { + operation: 'mesh-scan', + component: 'mesh-scanner', + metadata: { meshName, parentDir }, + }, ); } } diff --git a/flow-service/src/utils/service-log-context.ts b/flow-service/src/utils/service-log-context.ts new file mode 100644 index 0000000..da79a42 --- /dev/null +++ b/flow-service/src/utils/service-log-context.ts @@ -0,0 +1,24 @@ +import type { LogContext } from '../../../flow-core/src/utils/logger/types.ts'; +import { FLOW_SERVICE_VERSION, FLOW_SERVICE_NAME } from '../service-constants.ts'; + +/** + * Extended LogContext for flow-service with default serviceVersion. + */ +export interface ServiceLogContext extends LogContext { + serviceVersion?: string; + serviceName?: string; +} + +/** + * Creates a ServiceLogContext with default serviceVersion set. + * Merges any provided partial context. + */ +export function createServiceLogContext( + partialContext: Partial = {}, +): ServiceLogContext { + return { + serviceVersion: FLOW_SERVICE_VERSION, + serviceName: FLOW_SERVICE_NAME, + ...partialContext, + }; +} diff --git a/flow-service/src/utils/service-logger.ts b/flow-service/src/utils/service-logger.ts new file mode 100644 index 0000000..bbfec4e --- /dev/null +++ b/flow-service/src/utils/service-logger.ts @@ -0,0 +1,192 @@ +/** + * @fileoverview Service-specific logger configuration for flow-service. + * Configures the flow-core logger with service-specific settings including + * service name, version, environment, and other service context. + */ + +import { + createEnhancedLogger, + type LoggerConfig, + type LogContext, + type StructuredLogger, + type EnhancedStructuredLogger, +} from '../../../flow-core/src/utils/logger/index.ts'; + +// Re-export formatters and error handlers for test access +export { formatConsoleMessage } from '../../../flow-core/src/utils/logger/formatters.ts'; +export { handleCaughtError } from '../../../flow-core/src/utils/logger/error-handlers.ts'; + +/** + * Service-specific context that gets applied to all log entries + */ +const SERVICE_CONTEXT = { + serviceName: 'flow-service', + serviceVersion: Deno.env.get('FLOW_VERSION') || '1.0.0', + environment: Deno.env.get('FLOW_ENV') || 'development', + instanceId: Deno.env.get('FLOW_INSTANCE_ID') || crypto.randomUUID(), +} as const; + +/** + * Service-specific logger configuration + */ +const SERVICE_LOGGER_CONFIG: LoggerConfig = { + enableConsole: true, + enableFile: Deno.env.get('FLOW_LOG_FILE_ENABLED') === 'true', + enableSentry: Deno.env.get('FLOW_SENTRY_ENABLED') === 'true', + + fileConfig: { + logDir: Deno.env.get('FLOW_LOG_DIR') || './logs', + maxFileSize: parseInt(Deno.env.get('FLOW_LOG_MAX_FILE_SIZE') || '10485760'), // 10MB + maxFiles: parseInt(Deno.env.get('FLOW_LOG_MAX_FILES') || '5'), + rotateDaily: Deno.env.get('FLOW_LOG_ROTATE_DAILY') === 'true', + }, + + sentryConfig: { + dsn: Deno.env.get('FLOW_SENTRY_DSN'), + environment: Deno.env.get('FLOW_ENV') || 'development', + release: Deno.env.get('FLOW_VERSION'), + sampleRate: parseFloat(Deno.env.get('FLOW_SENTRY_SAMPLE_RATE') || '1.0'), + }, + + serviceContext: SERVICE_CONTEXT, +}; + +/** + * Configured logger instance for flow-service with service-specific context + */ +export const logger: EnhancedStructuredLogger = createEnhancedLogger(SERVICE_LOGGER_CONFIG); + +/** + * Create a logger with additional service operation context + * @param operation - The operation being performed + * @param operationId - Optional unique identifier for the operation + * @returns Logger instance scoped to the operation + */ +export function createOperationLogger(operation: string, operationId?: string): StructuredLogger { + return logger.forOperation(operation, operationId || crypto.randomUUID()); +} + +/** + * Create a logger with component-specific context + * @param component - The component name + * @returns Logger instance scoped to the component + */ +export function createComponentLogger(component: string): StructuredLogger { + return logger.forComponent(component); +} + +/** + * Create a logger with API request context + * @param requestId - Unique request identifier + * @param method - HTTP method + * @param path - Request path + * @param userAgent - User agent string + * @param ip - Client IP address + * @returns Logger instance with API context + */ +export function createApiLogger( + requestId: string, + method?: string, + path?: string, + userAgent?: string, + ip?: string, +): StructuredLogger { + const apiContext: LogContext = { + operation: 'api-request', + operationId: requestId, + apiContext: { + requestId, + method, + path, + userAgent, + ip, + }, + }; + + return logger.withContext(apiContext); +} + +/** + * Create a logger with mesh processing context + * @param meshId - Mesh identifier + * @param meshName - Human-readable mesh name + * @param nodeId - Optional node identifier + * @param nodeName - Optional human-readable node name + * @returns Logger instance with mesh context + */ +export function createMeshLogger( + meshId: string, + meshName?: string, + nodeId?: string, + nodeName?: string, +): StructuredLogger { + const meshContext: LogContext = { + operation: 'mesh-processing', + meshId, + meshName, + nodeId, + nodeName, + }; + + return logger.withContext(meshContext); +} + +/** + * Create a logger with configuration operation context + * @param configPath - Path to configuration file + * @param configType - Type of configuration (e.g., 'service', 'mesh', 'env') + * @param validationStage - Current validation stage + * @returns Logger instance with config context + */ +export function createConfigLogger( + configPath?: string, + configType?: string, + validationStage?: string, +): StructuredLogger { + const configContext: LogContext = { + operation: 'config-resolve', + configContext: { + configPath, + configType, + validationStage, + }, + }; + + return logger.withContext(configContext); +} + +/** + * Create a logger with startup operation context + * @param stage - Startup stage (e.g., 'initialization', 'config-loading', 'server-start') + * @returns Logger instance with startup context + */ +export function createStartupLogger(stage?: string): StructuredLogger { + const startupContext: LogContext = { + operation: 'startup', + component: 'flow-service', + metadata: { + stage, + }, + }; + + return logger.withContext(startupContext); +} + +/** + * Get service information for logging context + * @returns Service context information + */ +export function getServiceContext() { + return SERVICE_CONTEXT; +} + +/** + * Get the raw configured logger instance (without additional context) + * @returns Base logger instance + */ +export function getRawLogger(): StructuredLogger { + return logger; +} + +// Export the configured logger as default +export default logger; diff --git a/flow-service/src/utils/service-uri-builder.ts b/flow-service/src/utils/service-uri-builder.ts new file mode 100644 index 0000000..7463af8 --- /dev/null +++ b/flow-service/src/utils/service-uri-builder.ts @@ -0,0 +1,86 @@ +/** + * Service URI Builder Utility + * + * Provides dynamic URI construction for service-related resources based on + * configured host, port, and scheme. This ensures URIs are properly qualified + * for quadstore operations while remaining configurable. + */ + +export interface ServiceUriConfig { + readonly scheme: string; + readonly host: string; + readonly port: number; +} + + +/** + * Builds a base service URI from the given configuration + */ +export function buildServiceBaseUri(config: ServiceUriConfig): string { + const port = (config.scheme === 'https' && config.port === 443) || + (config.scheme === 'http' && config.port === 80) + ? '' + : `:${config.port}`; + + return `${config.scheme}://${config.host}${port}/`; +} + +/** + * Builds a service-relative URI for the given path + */ +export function buildServiceUri(config: ServiceUriConfig, path: string): string { + const baseUri = buildServiceBaseUri(config); + const cleanPath = path.replace(/^\/+/, ''); + return `${baseUri}${cleanPath}/`; +} + +/** + * Singleton holder for current service URI configuration + * This is updated during service configuration resolution + */ +class ServiceUriConfigManager { + private _config: ServiceUriConfig | null = null; + private _cachedBaseUri: string | null = null; + + setConfig(config: ServiceUriConfig): void { + this._config = config; + // Update cached values when config changes + this._cachedBaseUri = buildServiceBaseUri(config); + } + + getConfig(): ServiceUriConfig { + if (!this._config) { + throw new Error('Service URI configuration not initialized. Call setConfig() first.'); + } + return this._config; + } + + getServiceBaseUri(): string { + if (!this._cachedBaseUri) { + throw new Error('Service URI configuration not initialized. Call setConfig() first.'); + } + return this._cachedBaseUri; + } + + isInitialized(): boolean { + return this._config !== null; + } + + reset(): void { + this._config = null; + this._cachedBaseUri = null; + } +} + +export const serviceUriConfigManager = new ServiceUriConfigManager(); + +/** + * Convenience functions that use the current service configuration + */ +export function getCurrentServiceBaseUri(): string { + return serviceUriConfigManager.getServiceBaseUri(); +} + +export function getCurrentServiceUri(path: string): string { + return buildServiceUri(serviceUriConfigManager.getConfig(), path); +} diff --git a/flow-service/src/utils/startup-logger.ts b/flow-service/src/utils/startup-logger.ts index 73c43fe..e55d5ab 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' - }`, - ); - const enabledServices = []; - if (config.apiEnabled) enabledServices.push('API'); - if (config.sparqlEnabled) enabledServices.push('SPARQL Endpoint'); - if (config.queryWidgetEnabled) enabledServices.push('SPARQL GUI'); + // 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: string[] = []; + // 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..e1e5479 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 formatters module +import { formatConsoleMessage } from '../../../src/utils/service-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..d678b11 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/service-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/logging/logger.test.ts b/flow-service/tests/integration/logging/logger.test.ts new file mode 100644 index 0000000..2ac4461 --- /dev/null +++ b/flow-service/tests/integration/logging/logger.test.ts @@ -0,0 +1,91 @@ +#!/usr/bin/env -S deno run --allow-all + +/** + * Simple test script to verify file logging functionality + */ + +import { logger } from '../../../src/utils/service-logger.ts'; + +async function testFileLogging() { + console.log('🧪 Testing file logging functionality...\n'); + + // Test basic logging with context + logger.info('File logging test started', { + operation: 'startup', + component: 'config-resolver', + metadata: { + testId: 'file-logging-test-001', + }, + }); + + logger.debug('Debug message with context', { + operation: 'scan', + component: 'mesh-scanner', + performanceMetrics: { + duration: 125, + }, + metadata: { + meshPath: '/test/path', + nodeCount: 5, + }, + }); + + logger.warn('Warning message with context', { + operation: 'config-resolve', + component: 'config-resolver', + configContext: { + configPath: 'file', + }, + metadata: { + schemaVersion: '1.0.0', + }, + }); + + logger.error('Error message with context', new Error('Test error'), { + operation: 'api-request', + component: 'api-handler', + apiContext: { + path: '/api/test', + statusCode: 500, + }, + errorContext: { + errorCode: 'TEST_ERROR', + }, + }); + + // Test contextual logger + const meshLogger = logger.withContext({ + component: 'mesh-scanner', + operation: 'scan', + metadata: { + meshPath: '/test/mesh', + }, + }); + + meshLogger.info('Processing nodes', { + performanceMetrics: { + duration: 250, + }, + metadata: { + nodeCount: 10, + }, + }); + + meshLogger.info('Scan completed', { + performanceMetrics: { + duration: 500, + }, + metadata: { + nodeCount: 10, + fileCount: 25, + }, + }); + + console.log('\nāœ… File logging test completed!'); + console.log('šŸ“ Check the logs directory for the generated log file'); + console.log('šŸ“„ Log file should contain structured JSON entries'); +} + +if (import.meta.main) { + await testFileLogging(); +} 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 82% 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..4140083 100644 --- a/flow-service/tests/integration/test-pretty-format-simple.ts +++ b/flow-service/tests/integration/logging/pretty-format-simple.test.ts @@ -3,12 +3,12 @@ /** * Simple test to verify pretty formatting works without Sentry configuration issues */ - +// TODO: flow-service-dev.log // Disable Sentry for this test 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/service-logger.ts'; console.log('🧪 Testing pretty file logging format (Sentry disabled)...\n'); @@ -21,19 +21,23 @@ try { } // Test different log levels with various contexts -await logger.info('File logging test started', { +logger.info('File logging test started', { operation: 'startup', component: 'config-resolver', }); -await logger.warn('Configuration override detected', { +logger.warn('Configuration override detected', { operation: 'config-resolve', - configSource: 'environment', + configContext: { + configPath: 'environment', + }, }); -await logger.error('Database connection failed', undefined, { +logger.error('Database connection failed', { operation: 'startup', - duration: 5000, + performanceMetrics: { + duration: 5000, + }, }); console.log('\nāœ… Test logging complete!'); 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..81d3674 --- /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 { logger } from '../../../src/utils/service-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..8ae9dfe --- /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/service-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..a0a349f --- /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/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..68e3680 --- /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/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-logger.ts b/flow-service/tests/integration/test-logger.ts deleted file mode 100644 index 09c6476..0000000 --- a/flow-service/tests/integration/test-logger.ts +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env -S deno run --allow-all - -/** - * Simple test script to verify file logging functionality - */ - -import { logger } from '../../src/utils/logger.ts'; - -async function testFileLogging() { - console.log('🧪 Testing file logging functionality...\n'); - - // Test basic logging with context - await logger.info('File logging test started', { - operation: 'startup', - component: 'config-resolver', - testId: 'file-logging-test-001', - }); - - await logger.debug('Debug message with context', { - operation: 'scan', - component: 'mesh-scanner', - meshPath: '/test/path', - nodeCount: 5, - duration: 125, - }); - - await logger.warn('Warning message with context', { - operation: 'config-resolve', - component: 'config-resolver', - configSource: 'file', - schemaVersion: '1.0.0', - }); - - await logger.error('Error message with context', new Error('Test error'), { - operation: 'api-request', - component: 'api-handler', - endpoint: '/api/test', - statusCode: 500, - errorCode: 'TEST_ERROR', - }); - - // Test contextual logger - const meshLogger = logger.withContext({ - component: 'mesh-scanner', - operation: 'scan', - meshPath: '/test/mesh', - }); - - await meshLogger.info('Processing nodes', { - nodeCount: 10, - duration: 250, - }); - - await meshLogger.info('Scan completed', { - nodeCount: 10, - fileCount: 25, - duration: 500, - }); - - console.log('\nāœ… File logging test completed!'); - console.log('šŸ“ Check the logs directory for the generated log file'); - console.log('šŸ“„ Log file should contain structured JSON entries'); -} - -if (import.meta.main) { - await testFileLogging(); -} 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..18fb136 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1712 @@ +{ + "name": "sflow-platform", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "quadstore": "^15.4.0-beta.0" + }, + "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/@rdfjs/types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-2.0.1.tgz", + "integrity": "sha512-uyAzpugX7KekAXAHq26m3JlUIZJOC0uSBhpnefGV5i15bevDyyejoB7I+9MKeUrzXD8OOUI3+4FeV1wwQr5ihA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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/@types/node": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "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/abstract-level": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-3.1.0.tgz", + "integrity": "sha512-j2e+TsAxy7Ri+0h7dJqwasymgt0zHBWX4+nMk3XatyuqgHfdstBJ9wsMfbiGwE1O+QovRyPcVAqcViMYdyPaaw==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "is-buffer": "^2.0.5", + "level-supports": "^6.2.0", + "level-transcoder": "^1.0.1", + "maybe-combine-errors": "^1.0.0", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "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/asynciterator": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/asynciterator/-/asynciterator-3.9.0.tgz", + "integrity": "sha512-bwLLTAnoE6Ap6XdjK/j8vDk2Vi9p3ojk0PFwM0SwktAG1k8pfRJF9ng+mmkaRFKdZCQQlOxcWnvOmX2NQ1HV0g==", + "license": "MIT" + }, + "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/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/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/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "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/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "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-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "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", + "engines": { + "node": ">=4" + } + }, + "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-sorted-set": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/js-sorted-set/-/js-sorted-set-0.7.0.tgz", + "integrity": "sha512-NGTSMeoLNYR2BoTLhQ6w+u7Ox4PO34omb/0OBCy4gyedWeXolMGv948Ato0/Of6tfxsAjeySDymCkAj93/xkeA==", + "license": "Public Domain" + }, + "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/level-supports": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-6.2.0.tgz", + "integrity": "sha512-QNxVXP0IRnBmMsJIh+sb2kwNCYcKciQZJEt+L1hPCHrKNELllXhvrlClVHXBYZVT+a7aTSM6StgNXdAldoab3w==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/level-transcoder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "module-error": "^1.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "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/maybe-combine-errors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/maybe-combine-errors/-/maybe-combine-errors-1.0.0.tgz", + "integrity": "sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/module-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/quadstore": { + "version": "15.4.0-beta.0", + "resolved": "https://registry.npmjs.org/quadstore/-/quadstore-15.4.0-beta.0.tgz", + "integrity": "sha512-RFDcXdLxlXtqFoSGFbgc+1xmJQu9g3E/fyMFeCGaVTxftDqfkNj6BuatWCF74moAUBG/EtbhqvytJKm1IGny5w==", + "license": "MIT", + "dependencies": { + "@rdfjs/types": "^2.0.1", + "abstract-level": "^3.1.0", + "asynciterator": "^3.9.0", + "js-sorted-set": "^0.7.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT" + }, + "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..1fada90 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "devDependencies": { + "@typescript-eslint/parser": "^8.38.0", + "eslint": "^9.32.0" + }, + "dependencies": { + "quadstore": "^15.4.0-beta.0" + } +}