From e63ce1c43160e8c414374e922489d7635db64096 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Sun, 27 Jul 2025 08:43:06 -0700 Subject: [PATCH 01/20] groundwork for quadstore --- deno.lock | 194 ++++++++++++++++++++++++++- flow-core/src/deps.ts | 4 + flow-service/deno.lock | 97 +++++++++++++- flow-service/src/config/defaults.ts | 8 +- flow-service/src/config/quadstore.ts | 9 ++ 5 files changed, 303 insertions(+), 9 deletions(-) create mode 100644 flow-service/src/config/quadstore.ts diff --git a/deno.lock b/deno.lock index d96f83f..4a5b815 100644 --- a/deno.lock +++ b/deno.lock @@ -6,9 +6,14 @@ "jsr:@std/path@*": "1.1.0", "npm:@hono/zod-openapi@*": "0.19.10_hono@4.8.5_zod@3.24.1", "npm:@hono/zod-openapi@^1.0.2": "1.0.2_hono@4.8.5_zod@4.0.5", + "npm:@rdfjs/data-model@*": "2.1.1", "npm:@scalar/hono-api-reference@*": "0.9.12_hono@4.8.5", "npm:@scalar/openapi-to-markdown@*": "0.2.23", - "npm:@sentry/deno@*": "9.40.0" + "npm:@sentry/deno@*": "9.40.0", + "npm:@types/node@*": "22.15.15", + "npm:memory-level@*": "3.1.0", + "npm:quadstore@*": "15.3.0", + "npm:rdf-data-factory@*": "2.0.2" }, "jsr": { "@hono/hono@4.8.5": { @@ -191,6 +196,16 @@ "@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@2.0.1": { + "integrity": "sha512-uyAzpugX7KekAXAHq26m3JlUIZJOC0uSBhpnefGV5i15bevDyyejoB7I+9MKeUrzXD8OOUI3+4FeV1wwQr5ihA==", + "dependencies": [ + "@types/node@22.15.15" + ] + }, "@scalar/code-highlight@0.1.8": { "integrity": "sha512-SH7gqBVGotf9Hw2srAKd/s854Wjf5lpR4FgAf/l3AlCYC5QXJ4hbj+fk1/3tvzeRFm+wvS6/ah4FuiIzvDPOug==", "dependencies": [ @@ -253,7 +268,7 @@ "integrity": "sha512-ho9ee29Ik+Jv17bSaQEXvGn1e2ydNEyc7Sv8A8hqPq084aIOiTBtVFISPPt+k/O6z0SIRayfL8FEZqr93GyZPg==", "dependencies": [ "@phosphor-icons/core", - "@types/node", + "@types/node@22.16.5", "chalk", "vue" ] @@ -436,6 +451,12 @@ "@types/ms@2.1.0": { "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" }, + "@types/node@22.15.15": { + "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==", + "dependencies": [ + "undici-types" + ] + }, "@types/node@22.16.5": { "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==", "dependencies": [ @@ -540,6 +561,17 @@ "vue-demi" ] }, + "abstract-level@3.1.0": { + "integrity": "sha512-j2e+TsAxy7Ri+0h7dJqwasymgt0zHBWX4+nMk3XatyuqgHfdstBJ9wsMfbiGwE1O+QovRyPcVAqcViMYdyPaaw==", + "dependencies": [ + "buffer", + "is-buffer", + "level-supports", + "level-transcoder", + "maybe-combine-errors", + "module-error" + ] + }, "acorn@8.15.0": { "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "bin": true @@ -577,12 +609,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": [ @@ -696,6 +741,9 @@ "flatted@3.3.3": { "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" }, + "functional-red-black-tree@1.0.1": { + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==" + }, "get-own-enumerable-keys@1.0.0": { "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==" }, @@ -918,9 +966,15 @@ "html-whitespace-sensitive-tag-names@3.0.1": { "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==" }, + "ieee754@1.2.1": { + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "is-absolute-url@4.0.1": { "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==" }, + "is-buffer@2.0.5": { + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + }, "is-obj@3.0.0": { "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==" }, @@ -930,6 +984,9 @@ "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==" }, @@ -945,6 +1002,16 @@ "just-curry-it@5.3.0": { "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==" }, + "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==" }, @@ -974,6 +1041,9 @@ "markdown-table@3.0.4": { "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==" }, + "maybe-combine-errors@1.0.0": { + "integrity": "sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A==" + }, "mdast-util-find-and-replace@3.0.2": { "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "dependencies": [ @@ -1100,6 +1170,14 @@ "@types/mdast" ] }, + "memory-level@3.1.0": { + "integrity": "sha512-mTqFVi5iReKcjue/pag0OY4VNU7dlagCyjjPwWGierpk1Bpl9WjOxgXIswymPW3Q9bj3Foay+Z16mPGnKzvTkQ==", + "dependencies": [ + "abstract-level", + "functional-red-black-tree", + "module-error" + ] + }, "microdiff@1.5.0": { "integrity": "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==" }, @@ -1344,6 +1422,9 @@ "micromark-util-types" ] }, + "module-error@1.0.2": { + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==" + }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, @@ -1416,6 +1497,15 @@ "property-information@7.1.0": { "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" }, + "quadstore@15.3.0": { + "integrity": "sha512-KeFNurvPg61nO/WgdFYxbO/Sj80ne1gmzMGnWI0ukIXabdLggZ7i9TERRzPoyLHiUJP+Kkc1J2lZ+wl7n8hNBA==", + "dependencies": [ + "@rdfjs/types", + "abstract-level", + "asynciterator", + "js-sorted-set" + ] + }, "radix-vue@1.9.17_vue@3.5.18": { "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==", "dependencies": [ @@ -1433,6 +1523,12 @@ "vue" ] }, + "rdf-data-factory@2.0.2": { + "integrity": "sha512-WzPoYHwQYWvIP9k+7IBLY1b4nIDitzAK4mA37WumAF/Cjvu/KOtYJH9IPZnUTWNSd5K2+pq4vrcE9WZC4sRHhg==", + "dependencies": [ + "@rdfjs/types" + ] + }, "rehype-external-links@3.0.0": { "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==", "dependencies": [ @@ -1899,9 +1995,103 @@ "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", + "https://deno.land/std@0.224.0/fs/_create_walk_entry.ts": "5d9d2aaec05bcf09a06748b1684224d33eba7a4de24cf4cf5599991ca6b5b412", + "https://deno.land/std@0.224.0/fs/_get_file_info_type.ts": "da7bec18a7661dba360a1db475b826b18977582ce6fc9b25f3d4ee0403fe8cbd", + "https://deno.land/std@0.224.0/fs/_is_same_path.ts": "709c95868345fea051c58b9e96af95cff94e6ae98dfcff2b66dee0c212c4221f", + "https://deno.land/std@0.224.0/fs/_is_subdir.ts": "c68b309d46cc8568ed83c000f608a61bbdba0943b7524e7a30f9e450cf67eecd", + "https://deno.land/std@0.224.0/fs/_to_path_string.ts": "29bfc9c6c112254961d75cbf6ba814d6de5349767818eb93090cecfa9665591e", + "https://deno.land/std@0.224.0/fs/copy.ts": "7ab12a16adb65d155d4943c88081ca16ce3b0b5acada64c1ce93800653678039", + "https://deno.land/std@0.224.0/fs/empty_dir.ts": "e400e96e1d2c8c558a5a1712063bd43939e00619c1d1cc29959babc6f1639418", + "https://deno.land/std@0.224.0/fs/ensure_dir.ts": "51a6279016c65d2985f8803c848e2888e206d1b510686a509fa7cc34ce59d29f", + "https://deno.land/std@0.224.0/fs/ensure_file.ts": "67608cf550529f3d4aa1f8b6b36bf817bdc40b14487bf8f60e61cbf68f507cf3", + "https://deno.land/std@0.224.0/fs/ensure_link.ts": "5c98503ebfa9cc05e2f2efaa30e91e60b4dd5b43ebbda82f435c0a5c6e3ffa01", + "https://deno.land/std@0.224.0/fs/ensure_symlink.ts": "cafe904cebacb9a761977d6dbf5e3af938be946a723bb394080b9a52714fafe4", + "https://deno.land/std@0.224.0/fs/eol.ts": "18c4ac009d0318504c285879eb7f47942643f13619e0ff070a0edc59353306bd", + "https://deno.land/std@0.224.0/fs/exists.ts": "3d38cb7dcbca3cf313be343a7b8af18a87bddb4b5ca1bd2314be12d06533b50f", + "https://deno.land/std@0.224.0/fs/expand_glob.ts": "2e428d90acc6676b2aa7b5c78ef48f30641b13f1fe658e7976c9064fb4b05309", + "https://deno.land/std@0.224.0/fs/mod.ts": "c25e6802cbf27f3050f60b26b00c2d8dba1cb7fcdafe34c66006a7473b7b34d4", + "https://deno.land/std@0.224.0/fs/move.ts": "ca205d848908d7f217353bc5c623627b1333490b8b5d3ef4cab600a700c9bd8f", + "https://deno.land/std@0.224.0/fs/walk.ts": "cddf87d2705c0163bff5d7767291f05b0f46ba10b8b28f227c3849cace08d303", "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", + "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", + "https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", + "https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c", + "https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", + "https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b", + "https://deno.land/std@0.224.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf", + "https://deno.land/std@0.224.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d", + "https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3", + "https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607", + "https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a", + "https://deno.land/std@0.224.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883", + "https://deno.land/std@0.224.0/path/_interface.ts": "8dfeb930ca4a772c458a8c7bbe1e33216fe91c253411338ad80c5b6fa93ddba0", + "https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15", + "https://deno.land/std@0.224.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e", + "https://deno.land/std@0.224.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643", + "https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36", + "https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c", + "https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441", + "https://deno.land/std@0.224.0/path/format.ts": "6ce1779b0980296cf2bc20d66436b12792102b831fd281ab9eb08fa8a3e6f6ac", + "https://deno.land/std@0.224.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069", + "https://deno.land/std@0.224.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972", + "https://deno.land/std@0.224.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7", + "https://deno.land/std@0.224.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141", + "https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a", + "https://deno.land/std@0.224.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0", + "https://deno.land/std@0.224.0/path/mod.ts": "f6bd79cb08be0e604201bc9de41ac9248582699d1b2ee0ab6bc9190d472cf9cd", + "https://deno.land/std@0.224.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352", + "https://deno.land/std@0.224.0/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f", + "https://deno.land/std@0.224.0/path/parse.ts": "77ad91dcb235a66c6f504df83087ce2a5471e67d79c402014f6e847389108d5a", + "https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d", + "https://deno.land/std@0.224.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0", + "https://deno.land/std@0.224.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.224.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1", + "https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00", + "https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2", + "https://deno.land/std@0.224.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1", + "https://deno.land/std@0.224.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40", + "https://deno.land/std@0.224.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f", + "https://deno.land/std@0.224.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede", + "https://deno.land/std@0.224.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63", + "https://deno.land/std@0.224.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", + "https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91", + "https://deno.land/std@0.224.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/posix/parse.ts": "09dfad0cae530f93627202f28c1befa78ea6e751f92f478ca2cc3b56be2cbb6a", + "https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c", + "https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf", + "https://deno.land/std@0.224.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf", + "https://deno.land/std@0.224.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0", + "https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add", + "https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d", + "https://deno.land/std@0.224.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b", + "https://deno.land/std@0.224.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40", + "https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808", + "https://deno.land/std@0.224.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660", + "https://deno.land/std@0.224.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.224.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5", + "https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", + "https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef", + "https://deno.land/std@0.224.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6", + "https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01", + "https://deno.land/std@0.224.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8", + "https://deno.land/std@0.224.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a", + "https://deno.land/std@0.224.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf", + "https://deno.land/std@0.224.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", + "https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780", + "https://deno.land/std@0.224.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/windows/parse.ts": "08804327b0484d18ab4d6781742bf374976de662f8642e62a67e93346e759707", + "https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7", + "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", + "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", + "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", "https://deno.land/std@0.224.0/testing/mock.ts": "a963181c2860b6ba3eb60e08b62c164d33cf5da7cd445893499b2efda20074db", "https://deno.land/x/free_port@v1.2.0/mod.ts": "512646732aaea41fbfd1f210f3ae82660f38251777d189d290da331d0235a58e", "https://deno.land/x/opine@2.1.2/src/methods.ts": "0481daecc6068d24e9e5391818baddf555ab803d39a465dcd259161f8bd8ee49", diff --git a/flow-core/src/deps.ts b/flow-core/src/deps.ts index 89051b2..cf449f6 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -12,3 +12,7 @@ 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 { MemoryLevel } from 'npm:memory-level'; +export { DataFactory } from 'npm:rdf-data-factory'; diff --git a/flow-service/deno.lock b/flow-service/deno.lock index 49e8a9f..3ca95a3 100644 --- a/flow-service/deno.lock +++ b/flow-service/deno.lock @@ -6,6 +6,7 @@ "jsr:@std/path@*": "1.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:@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", @@ -13,6 +14,9 @@ "npm:@sentry/deno@*": "8.55.0", "npm:@sentry/deno@8": "8.55.0", "npm:hono@*": "4.8.4", + "npm:memory-level@*": "3.1.0", + "npm:quadstore@*": "15.3.0", + "npm:rdf-data-factory@*": "2.0.2", "npm:zod@^3.22.0": "3.25.76" }, "jsr": { @@ -171,6 +175,16 @@ "@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@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 +260,7 @@ "integrity": "sha512-ho9ee29Ik+Jv17bSaQEXvGn1e2ydNEyc7Sv8A8hqPq084aIOiTBtVFISPPt+k/O6z0SIRayfL8FEZqr93GyZPg==", "dependencies": [ "@phosphor-icons/core", - "@types/node", + "@types/node@22.16.3", "chalk", "vue" ] @@ -440,6 +454,12 @@ "@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": [ @@ -551,6 +571,17 @@ "vue-demi" ] }, + "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 +619,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": [ @@ -707,6 +751,9 @@ "flatted@3.3.3": { "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" }, + "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 +979,15 @@ "html-whitespace-sensitive-tag-names@3.0.1": { "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==" }, + "ieee754@1.2.1": { + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "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,6 +997,9 @@ "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==" }, @@ -959,6 +1015,16 @@ "just-curry-it@5.3.0": { "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==" }, + "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==" }, @@ -988,6 +1054,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 +1183,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 +1435,9 @@ "micromark-util-types" ] }, + "module-error@1.0.2": { + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==" + }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, @@ -1430,6 +1510,15 @@ "property-information@7.1.0": { "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" }, + "quadstore@15.3.0": { + "integrity": "sha512-KeFNurvPg61nO/WgdFYxbO/Sj80ne1gmzMGnWI0ukIXabdLggZ7i9TERRzPoyLHiUJP+Kkc1J2lZ+wl7n8hNBA==", + "dependencies": [ + "@rdfjs/types", + "abstract-level", + "asynciterator", + "js-sorted-set" + ] + }, "radix-vue@1.9.17_vue@3.5.17": { "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==", "dependencies": [ @@ -1447,6 +1536,12 @@ "vue" ] }, + "rdf-data-factory@2.0.2": { + "integrity": "sha512-WzPoYHwQYWvIP9k+7IBLY1b4nIDitzAK4mA37WumAF/Cjvu/KOtYJH9IPZnUTWNSd5K2+pq4vrcE9WZC4sRHhg==", + "dependencies": [ + "@rdfjs/types" + ] + }, "rehype-external-links@3.0.0": { "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==", "dependencies": [ diff --git a/flow-service/src/config/defaults.ts b/flow-service/src/config/defaults.ts index cdc7873..8e618f7 100644 --- a/flow-service/src/config/defaults.ts +++ b/flow-service/src/config/defaults.ts @@ -31,7 +31,7 @@ export const PLATFORM_NODE_DEFAULTS: NodeConfig = { 'conf:templateMappings': { '@type': 'conf:TemplateMapping', 'conf:hasResourcePageTemplate': - '/_assets/templates/default-resource-page.html', + '/_assets/_templates/default-resource-page.html', }, 'conf:generateUnifiedDataset': false, 'conf:generateAggregatedDataset': false, @@ -44,7 +44,6 @@ 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 */ @@ -62,7 +61,7 @@ export const SERVICE_NODE_DEFAULTS: Omit< ], 'conf:templateMappings': { '@type': 'conf:TemplateMapping', - 'conf:hasResourcePageTemplate': 'templates/default-resource.html', // Service-relative path + 'conf:hasResourcePageTemplate': '/_assets/_templates/default-resource-page.html', // Service-relative path }, 'conf:generateUnifiedDataset': false, 'conf:generateAggregatedDataset': false, @@ -110,9 +109,6 @@ export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { }, // Reference the service-specific node defaults to avoid duplication 'fsvc:nodeDefaults': SERVICE_NODE_DEFAULTS, - 'fsvc:defaultAttributedTo': { - '@id': 'https://orcid.org/0000-0002-1825-0097', - }, }; // Development Environment Overrides diff --git a/flow-service/src/config/quadstore.ts b/flow-service/src/config/quadstore.ts new file mode 100644 index 0000000..5e8f0c8 --- /dev/null +++ b/flow-service/src/config/quadstore.ts @@ -0,0 +1,9 @@ +import { Quadstore, MemoryLevel, DataFactory } from '../../../flow-core/src/deps.ts'; + +const db = new MemoryLevel(); +const dataFactory = new DataFactory(); +const quadstore = new Quadstore({ backend: db, dataFactory }); + +await quadstore.open(); + +export { quadstore }; From 8e9eb841a7ede580bfdbca7348cc527a66fcca34 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Sun, 27 Jul 2025 09:32:36 -0700 Subject: [PATCH 02/20] rootNodeConfigTemplate refinement (ontology, types, docs) --- flow-service/src/config/defaults.ts | 2 -- flow-service/src/config/types.ts | 22 +++++++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/flow-service/src/config/defaults.ts b/flow-service/src/config/defaults.ts index 8e618f7..0044c05 100644 --- a/flow-service/src/config/defaults.ts +++ b/flow-service/src/config/defaults.ts @@ -107,8 +107,6 @@ export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { 'fsvc:staticServerEnabled': true, 'fsvc:apiDocsEnabled': true, }, - // Reference the service-specific node defaults to avoid duplication - 'fsvc:nodeDefaults': SERVICE_NODE_DEFAULTS, }; // Development Environment Overrides diff --git a/flow-service/src/config/types.ts b/flow-service/src/config/types.ts index 0125aba..ec0e6d7 100644 --- a/flow-service/src/config/types.ts +++ b/flow-service/src/config/types.ts @@ -33,10 +33,10 @@ export interface LogChannelConfig { readonly 'fsvc:logMaxFiles'?: number; readonly 'fsvc:logMaxFileSize'?: number; readonly 'fsvc:logRotationInterval'?: - | 'daily' - | 'weekly' - | 'monthly' - | 'size-based'; + | 'daily' + | 'weekly' + | 'monthly' + | 'size-based'; } export interface LoggingConfig { @@ -83,7 +83,7 @@ export interface ServiceConfig extends JSONLDBase { readonly 'fsvc:meshPaths'?: string[]; readonly 'fsvc:hasLoggingConfig': LoggingConfig; readonly 'fsvc:hasContainedServices': ContainedServicesConfig; - readonly 'fsvc:nodeDefaults': NodeConfig; + readonly 'fsvc:rootNodeConfigTemplate'?: NodeConfig; readonly 'fsvc:defaultDelegationChain'?: DelegationChain; readonly 'fsvc:defaultAttributedTo'?: AttributedTo; } @@ -230,8 +230,8 @@ export function getConsoleLogLevel(context: ServiceConfigContext): LogLevel { ?.['fsvc:hasConsoleChannel']?.['fsvc:logLevel'] ?? context .defaultOptions['fsvc:hasLoggingConfig']['fsvc:hasConsoleChannel'][ - 'fsvc:logLevel' - ]; + 'fsvc:logLevel' + ]; } /** @@ -244,8 +244,8 @@ export function getFileLogEnabled(context: ServiceConfigContext): boolean { ?.['fsvc:logChannelEnabled'] ?? context .defaultOptions['fsvc:hasLoggingConfig']['fsvc:hasFileChannel'][ - 'fsvc:logChannelEnabled' - ]; + 'fsvc:logChannelEnabled' + ]; } /** @@ -258,8 +258,8 @@ export function getSentryEnabled(context: ServiceConfigContext): boolean { ?.['fsvc:hasSentryChannel']?.['fsvc:logChannelEnabled'] ?? context .defaultOptions['fsvc:hasLoggingConfig']['fsvc:hasSentryChannel'][ - 'fsvc:logChannelEnabled' - ]; + 'fsvc:logChannelEnabled' + ]; } /** From cf1cf0df50ad515424a73fd66a99437491079988 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Sun, 27 Jul 2025 22:46:28 -0700 Subject: [PATCH 03/20] service configuration system is now modular, better organized --- flow-core/src/platform-constants.ts | 19 ++ flow-service/src/config/index.ts | 23 +- flow-service/src/config/loaders/env-loader.ts | 22 +- .../resolution/service-config-accessor.ts | 100 +++++++++ .../resolution/service-config-loader.ts | 92 ++++++++ .../resolution/service-config-resolver.ts | 205 +----------------- .../config/resolution/service-config-utils.ts | 38 ++++ .../resolution/service-config-validator.ts | 39 ++++ 8 files changed, 305 insertions(+), 233 deletions(-) create mode 100644 flow-core/src/platform-constants.ts create mode 100644 flow-service/src/config/resolution/service-config-accessor.ts create mode 100644 flow-service/src/config/resolution/service-config-loader.ts create mode 100644 flow-service/src/config/resolution/service-config-utils.ts create mode 100644 flow-service/src/config/resolution/service-config-validator.ts 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-service/src/config/index.ts b/flow-service/src/config/index.ts index 22ce010..2da2904 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -63,24 +63,27 @@ export { // Service Configuration Resolution (Cascading Pattern) export { - getConfigValue, - mergeConfigContext, resolveServiceConfig, - ServiceConfigAccessor, - validateServiceConfig, } from './resolution/service-config-resolver.ts'; +export { + getConfigValue, + mergeConfigContext, +} from './resolution/service-config-utils.ts'; + +export { validateServiceConfig } from './resolution/service-config-validator.ts'; + +export { ServiceConfigAccessor } 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 { resolveServiceConfig } from './resolution/service-config-resolver.ts'; +import { mergeConfigContext } from './resolution/service-config-utils.ts'; +import { validateServiceConfig } from './resolution/service-config-validator.ts'; +import { ServiceConfigAccessor } from './resolution/service-config-accessor.ts'; import { handleCaughtError } from '../utils/logger.ts'; /** diff --git a/flow-service/src/config/loaders/env-loader.ts b/flow-service/src/config/loaders/env-loader.ts index 7971bb4..6e66021 100644 --- a/flow-service/src/config/loaders/env-loader.ts +++ b/flow-service/src/config/loaders/env-loader.ts @@ -7,10 +7,10 @@ import type { EnvironmentConfig, - LogLevel, ServiceConfigInput, } from '../types.ts'; import { ConfigError } from '../types.ts'; +import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; /** * Loads environment variables prefixed with `FLOW_` and assembles them into a structured `ServiceConfigInput` object. @@ -260,26 +260,6 @@ function getEnvironmentVariables(): EnvironmentConfig { }; } -/** - * Validates and normalizes a log level string from an environment variable. - * - * @param level - The log level string to validate - * @returns The normalized log level if valid - * @throws ConfigError if the log level is not one of "debug", "info", "warn", or "error" - */ -function validateLogLevel(level: string): LogLevel { - const validLevels: LogLevel[] = ['debug', 'info', 'warn', 'error']; - const normalized = level.toLowerCase().trim() as LogLevel; - - if (!validLevels.includes(normalized)) { - throw new ConfigError( - `Invalid log level in environment variable: ${level}. Must be one of: ${ - validLevels.join(', ') - }`, - ); - } - return normalized; -} /** * Parses a string into a boolean value, supporting common true/false representations. diff --git a/flow-service/src/config/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts new file mode 100644 index 0000000..29df178 --- /dev/null +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -0,0 +1,100 @@ +import type { ServiceConfigContext, DelegationChain, AttributedTo } from '../types.ts'; +import { getConfigValue } from './service-config-utils.ts'; + +/** + * 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-loader.ts b/flow-service/src/config/resolution/service-config-loader.ts new file mode 100644 index 0000000..d71a0b3 --- /dev/null +++ b/flow-service/src/config/resolution/service-config-loader.ts @@ -0,0 +1,92 @@ +import type { ServiceConfigInput, ServiceOptions } from '../types.ts'; +import { getServiceConfigPath, loadEnvConfig } from '../loaders/env-loader.ts'; +import { loadServiceConfig } from '../loaders/jsonld-loader.ts'; +import { mergeConfigs } from '../../utils/merge-configs.ts'; +import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; + +/** + * Loads and merges service configuration input options from CLI, environment variables, and config file. + * + * @param cliOptions - Optional CLI-provided service options + * @returns Merged service configuration input options + */ +export async function loadServiceConfigInput( + cliOptions?: ServiceOptions, +): Promise { + // 1. Start with empty input options + let inputOptions: ServiceConfigInput = {}; + + // 2. Merge environment variables (cascading pattern) + const envConfig = loadEnvConfig(); + inputOptions = mergeConfigs(inputOptions, envConfig); + + // 3. Load and merge service config file (cascading pattern) + const serviceConfigPath = getServiceConfigPath(cliOptions?.configPath); + if (serviceConfigPath) { + const fileConfig = await loadServiceConfig(serviceConfigPath); + if (fileConfig) { + inputOptions = mergeConfigs(inputOptions, fileConfig); + } + } + + // 4. Apply CLI overrides (cascading pattern) + if (cliOptions) { + const cliConfig = convertCliOptionsToConfig(cliOptions); + inputOptions = mergeConfigs(inputOptions, cliConfig); + } + + return inputOptions; +} + +/** + * Converts CLI-provided service options into the internal ServiceConfigInput format. + * + * @param cliOptions - The service options provided via the command line interface + * @returns A partial service configuration object representing the CLI options in config format + */ +function convertCliOptionsToConfig( + cliOptions: ServiceOptions, +): ServiceConfigInput { + const config: ServiceConfigInput = {}; + + if (cliOptions.port !== undefined) { + config['fsvc:port'] = cliOptions.port; + } + + if (cliOptions.host !== undefined) { + config['fsvc:host'] = cliOptions.host; + } + + if (cliOptions.meshPaths !== undefined && cliOptions.meshPaths.length > 0) { + config['fsvc:meshPaths'] = cliOptions.meshPaths; + } + + if (cliOptions.logLevel !== undefined) { + config['fsvc:hasLoggingConfig'] = { + '@type': 'fsvc:LoggingConfig', + 'fsvc:hasConsoleChannel': { + '@type': 'fsvc:LogChannelConfig', + 'fsvc:logChannelEnabled': true, + 'fsvc:logLevel': validateLogLevel(cliOptions.logLevel), + }, + }; + } + + if (cliOptions.sentryEnabled !== undefined) { + if (!config['fsvc:hasLoggingConfig']) { + config['fsvc:hasLoggingConfig'] = { + '@type': 'fsvc:LoggingConfig', + }; + } + const loggingConfig = config['fsvc:hasLoggingConfig'] as Record< + string, + unknown + >; + loggingConfig['fsvc:hasSentryChannel'] = { + '@type': 'fsvc:LogChannelConfig', + 'fsvc:logChannelEnabled': cliOptions.sentryEnabled, + }; + } + + return config; +} diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index add0cdf..247f83d 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -1,14 +1,4 @@ -/** - * 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, @@ -17,11 +7,12 @@ 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 { mergeConfigs } from '../../utils/merge-configs.ts'; import { handleCaughtError } from '../../utils/logger.ts'; +import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; +import { getConfigValue, mergeConfigContext } from './service-config-utils.ts'; /** * Asynchronously resolves the service configuration by merging CLI options, environment variables, configuration files, and environment-specific defaults in a defined precedence order. @@ -36,14 +27,11 @@ export async function resolveServiceConfig( cliOptions?: ServiceOptions, ): Promise { try { - // 1. Start with empty input options let inputOptions: ServiceConfigInput = {}; - // 2. Merge environment variables (cascading pattern) const envConfig = loadEnvConfig(); inputOptions = mergeConfigs(inputOptions, envConfig); - // 3. Load and merge service config file (cascading pattern) const serviceConfigPath = getServiceConfigPath(cliOptions?.configPath); if (serviceConfigPath) { const fileConfig = await loadServiceConfig(serviceConfigPath); @@ -52,17 +40,14 @@ export async function resolveServiceConfig( } } - // 4. Apply CLI overrides (cascading pattern) if (cliOptions) { const cliConfig = convertCliOptionsToConfig(cliOptions); inputOptions = mergeConfigs(inputOptions, cliConfig); } - // 5. Get environment-specific defaults const environment = Deno.env.get('FLOW_ENV') || 'development'; const defaultOptions = getEnvironmentDefaults(environment); - // 6. Return side-by-side context (no merge) return { inputOptions, defaultOptions, @@ -83,23 +68,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. * @@ -155,171 +123,4 @@ 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', - ); - } -} +export { getConfigValue, mergeConfigContext }; diff --git a/flow-service/src/config/resolution/service-config-utils.ts b/flow-service/src/config/resolution/service-config-utils.ts new file mode 100644 index 0000000..a6958ab --- /dev/null +++ b/flow-service/src/config/resolution/service-config-utils.ts @@ -0,0 +1,38 @@ +import type { ServiceConfigContext, ServiceConfigInput } from '../types.ts'; +import { mergeConfigs } from '../../utils/merge-configs.ts'; +import { PLATFORM_SERVICE_DEFAULTS } from '../defaults.ts'; + +/** + * 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, + ); +} 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..2fbe622 --- /dev/null +++ b/flow-service/src/config/resolution/service-config-validator.ts @@ -0,0 +1,39 @@ +import type { ServiceConfigContext } from '../types.ts'; +import { ConfigError } from '../types.ts'; +import { getConfigValue } from './service-config-utils.ts'; + +/** + * 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.', + ); + } + } +} From 02f006b3cf7c16a45e6fd4dd7813812930f0bdab Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Mon, 28 Jul 2025 02:32:48 -0700 Subject: [PATCH 04/20] runs, figured out the relationship between rdf-data-factor and rdfjs/types --- flow-core/src/deps.ts | 1 + .../src/utils/quadstore/quadstore-utils.ts | 54 +++++++++++++ .../src/config/loaders/quadstore-loader.ts | 81 +++++++++++++++++++ flow-service/src/config/quadstore.ts | 9 --- flow-service/src/quadstore.ts | 9 +++ flow-service/src/test.ts | 37 +++++++++ 6 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 flow-core/src/utils/quadstore/quadstore-utils.ts create mode 100644 flow-service/src/config/loaders/quadstore-loader.ts delete mode 100644 flow-service/src/config/quadstore.ts create mode 100644 flow-service/src/quadstore.ts create mode 100644 flow-service/src/test.ts diff --git a/flow-core/src/deps.ts b/flow-core/src/deps.ts index cf449f6..26bf1d9 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -16,3 +16,4 @@ export { ensureDir } from 'https://deno.land/std@0.224.0/fs/ensure_dir.ts'; export { Quadstore } from 'npm:quadstore'; export { MemoryLevel } from 'npm:memory-level'; export { DataFactory } from 'npm:rdf-data-factory'; +export type { Quad, NamedNode, Literal, Term, Stream } from 'npm:@rdfjs/types'; 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..d7bd146 --- /dev/null +++ b/flow-core/src/utils/quadstore/quadstore-utils.ts @@ -0,0 +1,54 @@ +import type { NamedNode, Stream } from '../../deps.ts'; +import { quadstore, df } from '../../../../flow-service/src/quadstore.ts'; + +/** + * Clears all quads in the specified named graph. + * + * @param store - The Quadstore instance + * @param graph - The named graph to clear + * @param countOnly - If true, only count quads without deleting + * @returns The number of quads counted or deleted + */ +/** + * Delete all quads in the given graph and return the count of deleted quads. + * If `countOnly` is true, it does not delete but still counts. + */ +export async function clearGraph( + graph: NamedNode, +): Promise { + const matchStream = quadstore.match(undefined, undefined, undefined, graph); + const count = await countQuads(matchStream); + + if (count > 0) { + const delStream = quadstore.match(undefined, undefined, undefined, graph); + await quadstore.delStream(delStream as any); + } + + return count; +} +async function countQuads(stream: 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)); + }); +} + +/** + * Copies all quads from source graph to target graph. + * + * @param store - The Quadstore instance + * @param sourceGraph - The source named graph + * @param targetGraph - The target named graph + */ +export async function copyGraph( + sourceGraph: NamedNode, + targetGraph: NamedNode +): Promise { + const stream = quadstore.match(undefined, undefined, undefined, sourceGraph); + for await (const q of stream as any) { + const newQuad = df.quad(q.subject, q.predicate, q.object, targetGraph); + await quadstore.put(newQuad); + } +} 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..02b9f50 --- /dev/null +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -0,0 +1,81 @@ +import { namedNode, literal, quad } from '../../../../flow-core/src/deps.ts'; +import { quadstore } from '../../quadstore.ts'; +import type { ServiceConfigInput, NodeConfigInput } from '../types.ts'; +import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; + +/** + * Named graph terms for Quadstore config graphs + */ +export const GRAPH_NAMES = { + platformServiceDefaults: 'graph/platformServiceDefaults', + platformImplicitNodeConfig: 'graph/platformImplicitNodeConfig', + inputServiceConfig: 'graph/inputServiceConfig', + inputNodeConfig: 'graph/inputNodeConfig', + mergedServiceConfig: 'graph/mergedServiceConfig', + // Per-node effective configs could be stored with dynamic graph names +}; + +/** + * Load platform defaults into Quadstore graphs + */ +export async function loadPlatformDefaults(): Promise { + await loadConfigObjectToGraph(PLATFORM_SERVICE_DEFAULTS, GRAPH_NAMES.platformServiceDefaults); + await loadConfigObjectToGraph(PLATFORM_NODE_DEFAULTS, GRAPH_NAMES.platformImplicitNodeConfig); +} + +/** + * Load input service config into Quadstore graph + */ +export async function loadInputServiceConfig(inputConfig: ServiceConfigInput): Promise { + await loadConfigObjectToGraph(inputConfig, GRAPH_NAMES.inputServiceConfig); +} + +/** + * Load input node config overrides into Quadstore graph + */ +export async function loadInputNodeConfig(inputNodeConfig: NodeConfigInput): Promise { + await loadConfigObjectToGraph(inputNodeConfig, GRAPH_NAMES.inputNodeConfig); +} + +import { clearGraph, copyGraph } from '../../../../flow-core/src/utils/quadstore/quadstore-utils.ts'; + +/** + * Merge input and platform graphs into merged service config graph + */ +export async function mergeServiceConfigGraphs(): Promise { + // Clear merged graph + const count = await clearGraph(namedNode(GRAPH_NAMES.mergedServiceConfig)); + if (count > 0) { + console.info(`Deleted ${count} quads from mergedServiceConfig graph`); + } + + // Copy input service config quads + await copyGraph(namedNode(GRAPH_NAMES.inputServiceConfig), namedNode(GRAPH_NAMES.mergedServiceConfig)); + + // Copy platform service defaults quads if not overridden + const platformQuads = quadstore.match(undefined, undefined, undefined, namedNode(GRAPH_NAMES.platformServiceDefaults)); + for await (const q of platformQuads as any) { + const exists = await quadstore.countQuads(q.subject, q.predicate, undefined, namedNode(GRAPH_NAMES.mergedServiceConfig)); + if (exists === 0) { + await quadstore.put(quad(q.subject, q.predicate, q.object, namedNode(GRAPH_NAMES.mergedServiceConfig))); + } + } +} + +/** + * Helper to convert a config object to RDF quads and load into a named graph + * This is a simplified placeholder; a real implementation would convert JSON-LD fully. + */ +async function loadConfigObjectToGraph(configObj: object, graphName: string): Promise { + // Clear existing quads in the graph + clearGraph(namedNode(graphName)); + + // Convert configObj to quads and add to store + const subject = namedNode((configObj as Record)['@id'] || 'urn:config'); + for (const [key, value] of Object.entries(configObj)) { + if (key === '@context' || key === '@type' || key === '@id') continue; + const predicate = namedNode(key); + const object = typeof value === 'string' ? literal(value) : literal(JSON.stringify(value)); + await quadstore.put(quad(subject, predicate, object, namedNode(graphName))); + } +} diff --git a/flow-service/src/config/quadstore.ts b/flow-service/src/config/quadstore.ts deleted file mode 100644 index 5e8f0c8..0000000 --- a/flow-service/src/config/quadstore.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Quadstore, MemoryLevel, DataFactory } from '../../../flow-core/src/deps.ts'; - -const db = new MemoryLevel(); -const dataFactory = new DataFactory(); -const quadstore = new Quadstore({ backend: db, dataFactory }); - -await quadstore.open(); - -export { quadstore }; diff --git a/flow-service/src/quadstore.ts b/flow-service/src/quadstore.ts new file mode 100644 index 0000000..5578d94 --- /dev/null +++ b/flow-service/src/quadstore.ts @@ -0,0 +1,9 @@ +import { Quadstore, MemoryLevel, DataFactory } from '../../flow-core/src/deps.ts'; + +const db = new MemoryLevel(); +const df = new DataFactory(); +const quadstore = new Quadstore({ backend: db, dataFactory: df }); + +await quadstore.open(); + +export { quadstore, df }; diff --git a/flow-service/src/test.ts b/flow-service/src/test.ts new file mode 100644 index 0000000..baba7ee --- /dev/null +++ b/flow-service/src/test.ts @@ -0,0 +1,37 @@ +import { Quadstore, MemoryLevel, DataFactory } from '../../flow-core/src/deps.ts'; + +// Any implementation of AbstractLevel can be used. +const backend = new MemoryLevel(); + +// Implementation of the RDF/JS DataFactory interface +const df = new DataFactory(); + +// Store and query engine are separate modules +const store = new Quadstore({ backend, dataFactory: df }); + +// Open the store +await store.open(); + +// Put a single quad into the store using Quadstore's API +await store.put(df.quad( + df.namedNode('http://example.com/subject'), + df.namedNode('http://example.com/predicate'), + df.namedNode('http://example.com/object'), + df.defaultGraph(), +)); + +// Retrieves all quads using Quadstore's API +const { items } = await store.get({}); + +// Retrieves all quads using RDF/JS Stream interfaces +const quadsStream = store.match(undefined, undefined, undefined, undefined); +quadsStream.on('data', quad => console.log(quad)); + + +const quadsStream2 = store.match(undefined, undefined, undefined, undefined); +await store.delStream(quadsStream2 as any); + +console.log('All quads deleted'); + +const quadsStream3 = store.match(undefined, undefined, undefined, undefined); +quadsStream3.on('data', quad => console.log(quad)); From 1cb06c719a0db63f1425b28948919901657227be Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Mon, 28 Jul 2025 10:04:59 -0700 Subject: [PATCH 05/20] renamed tests --- .../src/utils/quadstore/quadstore-utils.ts | 4 +- flow-service/deno.json | 8 +- flow-service/deno.lock | 1 + flow-service/logs/test-pretty-direct.log | 4 + .../src/config/loaders/quadstore-loader.ts | 30 ++++--- flow-service/src/test.ts | 37 -------- .../entry-init.test.ts} | 4 +- .../formatting-direct.test.ts} | 26 +++--- .../handle-caught-error.test.ts} | 6 +- .../log-retention-config.test.ts} | 4 +- .../logger.test.ts} | 2 +- .../pretty-format-simple.test.ts} | 2 +- .../integration/logging/sentry-init.test.ts | 88 +++++++++++++++++++ .../sentry-logging.test.ts} | 4 +- .../tests/integration/quadstore.test.ts | 38 ++++++++ 15 files changed, 177 insertions(+), 81 deletions(-) create mode 100644 flow-service/logs/test-pretty-direct.log delete mode 100644 flow-service/src/test.ts rename flow-service/tests/integration/{test-sentry-init.ts => logging/entry-init.test.ts} (95%) rename flow-service/tests/integration/{test-formatting-direct.ts => logging/formatting-direct.test.ts} (93%) rename flow-service/tests/integration/{test-handle-caught-error.ts => logging/handle-caught-error.test.ts} (94%) rename flow-service/tests/integration/{test-log-retention-config.ts => logging/log-retention-config.test.ts} (97%) rename flow-service/tests/integration/{test-logger.ts => logging/logger.test.ts} (96%) rename flow-service/tests/integration/{test-pretty-format-simple.ts => logging/pretty-format-simple.test.ts} (96%) create mode 100644 flow-service/tests/integration/logging/sentry-init.test.ts rename flow-service/tests/integration/{test-sentry-logging.ts => logging/sentry-logging.test.ts} (96%) create mode 100644 flow-service/tests/integration/quadstore.test.ts diff --git a/flow-core/src/utils/quadstore/quadstore-utils.ts b/flow-core/src/utils/quadstore/quadstore-utils.ts index d7bd146..0b40ab6 100644 --- a/flow-core/src/utils/quadstore/quadstore-utils.ts +++ b/flow-core/src/utils/quadstore/quadstore-utils.ts @@ -20,8 +20,8 @@ export async function clearGraph( const count = await countQuads(matchStream); if (count > 0) { - const delStream = quadstore.match(undefined, undefined, undefined, graph); - await quadstore.delStream(delStream as any); + //TODO maybe delStream would be better + quadstore.removeMatches(undefined, undefined, undefined, graph); } return count; diff --git a/flow-service/deno.json b/flow-service/deno.json index d884e92..661d49d 100644 --- a/flow-service/deno.json +++ b/flow-service/deno.json @@ -8,10 +8,10 @@ "@sentry/deno": "npm:@sentry/deno@^8.0.0" }, "tasks": { - "start": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes --env-file main.ts", - "dev": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes --env-file --watch main.ts", - "debug": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes --env-file --inspect-brk=0.0.0.0:9229 main.ts", - "test": "deno test --allow-net --allow-env --allow-read --allow-write=../meshes" + "start": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes,./logs --env-file main.ts", + "dev": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes,./logs --env-file --watch main.ts", + "debug": "deno run --allow-net --allow-env --allow-read --allow-write=../meshes,./logs --env-file --inspect-brk=0.0.0.0:9229 main.ts", + "test": "deno test --allow-net --allow-env --allow-read --allow-write=../meshes,./logs" }, "compilerOptions": { "jsx": "precompile", diff --git a/flow-service/deno.lock b/flow-service/deno.lock index 3ca95a3..53ca352 100644 --- a/flow-service/deno.lock +++ b/flow-service/deno.lock @@ -7,6 +7,7 @@ "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", diff --git a/flow-service/logs/test-pretty-direct.log b/flow-service/logs/test-pretty-direct.log new file mode 100644 index 0000000..4da0abc --- /dev/null +++ b/flow-service/logs/test-pretty-direct.log @@ -0,0 +1,4 @@ +[2025-07-28T17:03:58.926Z] INFO File logging test started (op=startup, [config-resolver]) +[2025-07-28T17:03:58.931Z] WARN Configuration override detected (op=config-resolve, source=environment) +[2025-07-28T17:03:58.931Z] ERROR Database connection failed (op=startup, 5000ms) +[2025-07-28T17:03:58.932Z] DEBUG Processing mesh nodes (op=scan, mesh=/data/mesh, 1250ms, 42 nodes) diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index 02b9f50..c6589ef 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -1,7 +1,7 @@ -import { namedNode, literal, quad } from '../../../../flow-core/src/deps.ts'; -import { quadstore } from '../../quadstore.ts'; +import { quadstore, df } from '../../quadstore.ts'; import type { ServiceConfigInput, NodeConfigInput } from '../types.ts'; import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; +import { clearGraph, copyGraph } from '../../../../flow-core/src/utils/quadstore/quadstore-utils.ts'; /** * Named graph terms for Quadstore config graphs @@ -37,27 +37,25 @@ export async function loadInputNodeConfig(inputNodeConfig: NodeConfigInput): Pro await loadConfigObjectToGraph(inputNodeConfig, GRAPH_NAMES.inputNodeConfig); } -import { clearGraph, copyGraph } from '../../../../flow-core/src/utils/quadstore/quadstore-utils.ts'; - /** * Merge input and platform graphs into merged service config graph */ export async function mergeServiceConfigGraphs(): Promise { // Clear merged graph - const count = await clearGraph(namedNode(GRAPH_NAMES.mergedServiceConfig)); + const count = await clearGraph(df.namedNode(GRAPH_NAMES.mergedServiceConfig)); if (count > 0) { console.info(`Deleted ${count} quads from mergedServiceConfig graph`); } // Copy input service config quads - await copyGraph(namedNode(GRAPH_NAMES.inputServiceConfig), namedNode(GRAPH_NAMES.mergedServiceConfig)); + await copyGraph(df.namedNode(GRAPH_NAMES.inputServiceConfig), df.namedNode(GRAPH_NAMES.mergedServiceConfig)); // Copy platform service defaults quads if not overridden - const platformQuads = quadstore.match(undefined, undefined, undefined, namedNode(GRAPH_NAMES.platformServiceDefaults)); + const platformQuads = quadstore.match(undefined, undefined, undefined, df.namedNode(GRAPH_NAMES.platformServiceDefaults)); for await (const q of platformQuads as any) { - const exists = await quadstore.countQuads(q.subject, q.predicate, undefined, namedNode(GRAPH_NAMES.mergedServiceConfig)); + const exists = await quadstore.countQuads(q.subject, q.predicate, undefined, df.namedNode(GRAPH_NAMES.mergedServiceConfig)); if (exists === 0) { - await quadstore.put(quad(q.subject, q.predicate, q.object, namedNode(GRAPH_NAMES.mergedServiceConfig))); + await quadstore.put(df.quad(q.subject, q.predicate, q.object, df.namedNode(GRAPH_NAMES.mergedServiceConfig))); } } } @@ -68,14 +66,18 @@ export async function mergeServiceConfigGraphs(): Promise { */ async function loadConfigObjectToGraph(configObj: object, graphName: string): Promise { // Clear existing quads in the graph - clearGraph(namedNode(graphName)); + await clearGraph(df.namedNode(graphName)); // Convert configObj to quads and add to store - const subject = namedNode((configObj as Record)['@id'] || 'urn:config'); + const id = (configObj as Record)['@id']; + if (typeof id !== 'string') { + throw new Error('Config object must have a valid @id string'); + } + const subject = df.namedNode(id); for (const [key, value] of Object.entries(configObj)) { if (key === '@context' || key === '@type' || key === '@id') continue; - const predicate = namedNode(key); - const object = typeof value === 'string' ? literal(value) : literal(JSON.stringify(value)); - await quadstore.put(quad(subject, predicate, object, namedNode(graphName))); + const predicate = df.namedNode(key); + const object = typeof value === 'string' ? df.literal(value) : df.literal(JSON.stringify(value)); + await quadstore.put(df.quad(subject, predicate, object, df.namedNode(graphName))); } } diff --git a/flow-service/src/test.ts b/flow-service/src/test.ts deleted file mode 100644 index baba7ee..0000000 --- a/flow-service/src/test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Quadstore, MemoryLevel, DataFactory } from '../../flow-core/src/deps.ts'; - -// Any implementation of AbstractLevel can be used. -const backend = new MemoryLevel(); - -// Implementation of the RDF/JS DataFactory interface -const df = new DataFactory(); - -// Store and query engine are separate modules -const store = new Quadstore({ backend, dataFactory: df }); - -// Open the store -await store.open(); - -// Put a single quad into the store using Quadstore's API -await store.put(df.quad( - df.namedNode('http://example.com/subject'), - df.namedNode('http://example.com/predicate'), - df.namedNode('http://example.com/object'), - df.defaultGraph(), -)); - -// Retrieves all quads using Quadstore's API -const { items } = await store.get({}); - -// Retrieves all quads using RDF/JS Stream interfaces -const quadsStream = store.match(undefined, undefined, undefined, undefined); -quadsStream.on('data', quad => console.log(quad)); - - -const quadsStream2 = store.match(undefined, undefined, undefined, undefined); -await store.delStream(quadsStream2 as any); - -console.log('All quads deleted'); - -const quadsStream3 = store.match(undefined, undefined, undefined, undefined); -quadsStream3.on('data', quad => console.log(quad)); diff --git a/flow-service/tests/integration/test-sentry-init.ts b/flow-service/tests/integration/logging/entry-init.test.ts similarity index 95% rename from flow-service/tests/integration/test-sentry-init.ts rename to flow-service/tests/integration/logging/entry-init.test.ts index 685fec7..809e8e6 100644 --- a/flow-service/tests/integration/test-sentry-init.ts +++ b/flow-service/tests/integration/logging/entry-init.test.ts @@ -4,8 +4,8 @@ * Test script to verify Sentry initialization and logging */ -import { initSentry, logger } from '../../src/utils/logger.ts'; -import { getCompleteServiceConfig } from '../../src/config/index.ts'; +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'); diff --git a/flow-service/tests/integration/test-formatting-direct.ts b/flow-service/tests/integration/logging/formatting-direct.test.ts similarity index 93% rename from flow-service/tests/integration/test-formatting-direct.ts rename to flow-service/tests/integration/logging/formatting-direct.test.ts index 28ff04d..a7f4b08 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,10 +37,10 @@ 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; } @@ -71,7 +71,7 @@ function formatPrettyMessage( // Simple file logger class SimpleFileLogger { - constructor(private logFile: string) {} + constructor(private logFile: string) { } async ensureLogDirectory(): Promise { try { 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 94% 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..16713a2 100644 --- a/flow-service/tests/integration/test-handle-caught-error.ts +++ b/flow-service/tests/integration/logging/handle-caught-error.test.ts @@ -5,13 +5,13 @@ * This demonstrates the comprehensive error handling capabilities */ -import { handleCaughtError } from '../../src/utils/logger.ts'; +import { handleCaughtError } from '../../../src/utils/logger.ts'; import { ConfigurationError, FlowServiceError, ValidationError, -} from '../../src/utils/errors.ts'; -import { ConfigValidationError } from '../../src/config/types.ts'; +} from '../../../src/utils/errors.ts'; +import { ConfigValidationError } from '../../../src/config/types.ts'; console.log('🧪 Testing handleCaughtError function...\n'); diff --git a/flow-service/tests/integration/test-log-retention-config.ts b/flow-service/tests/integration/logging/log-retention-config.test.ts similarity index 97% rename from flow-service/tests/integration/test-log-retention-config.ts rename to flow-service/tests/integration/logging/log-retention-config.test.ts index bf51eaa..986540c 100644 --- a/flow-service/tests/integration/test-log-retention-config.ts +++ b/flow-service/tests/integration/logging/log-retention-config.test.ts @@ -4,8 +4,8 @@ * Test script to verify log retention configuration integration */ -import { getCompleteServiceConfig } from '../../src/config/index.ts'; -import { loadEnvConfig } from '../../src/config/loaders/env-loader.ts'; +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'); diff --git a/flow-service/tests/integration/test-logger.ts b/flow-service/tests/integration/logging/logger.test.ts similarity index 96% rename from flow-service/tests/integration/test-logger.ts rename to flow-service/tests/integration/logging/logger.test.ts index 09c6476..99ac743 100644 --- a/flow-service/tests/integration/test-logger.ts +++ b/flow-service/tests/integration/logging/logger.test.ts @@ -4,7 +4,7 @@ * Simple test script to verify file logging functionality */ -import { logger } from '../../src/utils/logger.ts'; +import { logger } from '../../../src/utils/logger.ts'; async function testFileLogging() { console.log('🧪 Testing file logging functionality...\n'); diff --git a/flow-service/tests/integration/test-pretty-format-simple.ts b/flow-service/tests/integration/logging/pretty-format-simple.test.ts similarity index 96% rename from flow-service/tests/integration/test-pretty-format-simple.ts rename to flow-service/tests/integration/logging/pretty-format-simple.test.ts index f9824fa..5834260 100644 --- a/flow-service/tests/integration/test-pretty-format-simple.ts +++ b/flow-service/tests/integration/logging/pretty-format-simple.test.ts @@ -8,7 +8,7 @@ Deno.env.set('FLOW_SENTRY_ENABLED', 'false'); Deno.env.set('FLOW_ENV', 'development'); -import { logger } from '../../src/utils/logger.ts'; +import { logger } from '../../../src/utils/logger.ts'; console.log('🧪 Testing pretty file logging format (Sentry disabled)...\n'); diff --git a/flow-service/tests/integration/logging/sentry-init.test.ts b/flow-service/tests/integration/logging/sentry-init.test.ts new file mode 100644 index 0000000..809e8e6 --- /dev/null +++ b/flow-service/tests/integration/logging/sentry-init.test.ts @@ -0,0 +1,88 @@ +#!/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/logging/sentry-logging.test.ts similarity index 96% rename from flow-service/tests/integration/test-sentry-logging.ts rename to flow-service/tests/integration/logging/sentry-logging.test.ts index b86e11a..264403c 100644 --- a/flow-service/tests/integration/test-sentry-logging.ts +++ b/flow-service/tests/integration/logging/sentry-logging.test.ts @@ -4,8 +4,8 @@ * 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'; +import { logger } from '../../../src/utils/logger.ts'; +import { getCompleteServiceConfig } from '../../../src/config/index.ts'; async function testSentryLogging() { console.log('🧪 Testing Sentry logging integration...\n'); diff --git a/flow-service/tests/integration/quadstore.test.ts b/flow-service/tests/integration/quadstore.test.ts new file mode 100644 index 0000000..90d3214 --- /dev/null +++ b/flow-service/tests/integration/quadstore.test.ts @@ -0,0 +1,38 @@ +import { Quadstore, MemoryLevel, DataFactory } from '../../../flow-core/src/deps.ts'; +import { quadstore, df } from '../../src/quadstore.ts'; +import { Quad } from '../../../flow-core/src/deps.ts'; + + +// Open the store +await quadstore.open(); + +// Put a single quad into the store using Quadstore's API +await quadstore.put(df.quad( + df.namedNode('http://example.com/subject'), + df.namedNode('http://example.com/predicate'), + df.namedNode('http://example.com/object'), + df.defaultGraph(), +)); + + +// Retrieves all quads using RDF/JS Stream interfaces +const quadsStream = quadstore.match(undefined, undefined, undefined, df.defaultGraph()); +quadsStream.on('data', (quad: Quad) => console.log(quad)); + + +const quadsStream2 = quadstore.match(undefined, undefined, undefined, df.defaultGraph()); +await quadstore.delStream(quadsStream2 as any); + +console.log('All quads deleted'); + +const quadsStream3 = quadstore.match(undefined, undefined, undefined, df.defaultGraph()); +quadsStream3.on('data', quad => console.log(quad)); + + + +// Retrieves all quads using Quadstore's API +const { items } = await quadstore.get({}); + +for (const item of items) { + console.log(item); +} From 6e8171e8715c887943c003cd2a4d4e41b5e3e321 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Mon, 28 Jul 2025 16:31:55 -0700 Subject: [PATCH 06/20] groundwork for jsonLD->graph utils --- deno.lock | 100 ++++++++++++++ flow-core/src/deps.ts | 7 +- flow-core/src/utils/quadstore-utils.ts | 124 ++++++++++++++++++ .../src/utils/quadstore/quadstore-utils.ts | 54 -------- flow-core/src/utils/rdfjs-utils.ts | 8 ++ .../src/config/loaders/quadstore-loader.ts | 36 ++--- .../resolution/service-config-resolver.ts | 4 + flow-service/src/config/types.ts | 13 ++ flow-service/src/quadstore.ts | 9 -- flow-service/src/quadstoreDefaultBundle.ts | 9 ++ .../tests/integration/quadstore.test.ts | 64 +++++---- 11 files changed, 315 insertions(+), 113 deletions(-) create mode 100644 flow-core/src/utils/quadstore-utils.ts delete mode 100644 flow-core/src/utils/quadstore/quadstore-utils.ts create mode 100644 flow-core/src/utils/rdfjs-utils.ts delete mode 100644 flow-service/src/quadstore.ts create mode 100644 flow-service/src/quadstoreDefaultBundle.ts diff --git a/deno.lock b/deno.lock index 4a5b815..6b63dff 100644 --- a/deno.lock +++ b/deno.lock @@ -7,10 +7,12 @@ "npm:@hono/zod-openapi@*": "0.19.10_hono@4.8.5_zod@3.24.1", "npm:@hono/zod-openapi@^1.0.2": "1.0.2_hono@4.8.5_zod@4.0.5", "npm:@rdfjs/data-model@*": "2.1.1", + "npm:@rdfjs/types@*": "2.0.1", "npm:@scalar/hono-api-reference@*": "0.9.12_hono@4.8.5", "npm:@scalar/openapi-to-markdown@*": "0.2.23", "npm:@sentry/deno@*": "9.40.0", "npm:@types/node@*": "22.15.15", + "npm:jsonld@*": "8.3.3", "npm:memory-level@*": "3.1.0", "npm:quadstore@*": "15.3.0", "npm:rdf-data-factory@*": "2.0.2" @@ -58,6 +60,17 @@ "@babel/helper-validator-identifier" ] }, + "@digitalbazaar/http-client@3.4.1_ky@0.33.3": { + "integrity": "sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==", + "dependencies": [ + "ky", + "ky-universal", + "undici" + ] + }, + "@fastify/busboy@2.1.1": { + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==" + }, "@floating-ui/core@1.7.2": { "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", "dependencies": [ @@ -561,6 +574,12 @@ "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": [ @@ -635,6 +654,9 @@ "tslib" ] }, + "canonicalize@1.0.8": { + "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==" + }, "ccount@2.0.1": { "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" }, @@ -686,6 +708,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": [ @@ -729,6 +754,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==" }, @@ -738,9 +766,22 @@ "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==" }, @@ -993,6 +1034,15 @@ "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==" }, @@ -1002,6 +1052,17 @@ "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==" }, @@ -1032,6 +1093,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": [ @@ -1443,6 +1510,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": [ @@ -1523,6 +1602,12 @@ "vue" ] }, + "rdf-canonize@3.4.0": { + "integrity": "sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==", + "dependencies": [ + "setimmediate" + ] + }, "rdf-data-factory@2.0.2": { "integrity": "sha512-WzPoYHwQYWvIP9k+7IBLY1b4nIDitzAK4mA37WumAF/Cjvu/KOtYJH9IPZnUTWNSd5K2+pq4vrcE9WZC4sRHhg==", "dependencies": [ @@ -1642,6 +1727,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==" }, @@ -1707,6 +1795,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": [ @@ -1808,6 +1902,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 diff --git a/flow-core/src/deps.ts b/flow-core/src/deps.ts index 26bf1d9..c44b563 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -14,6 +14,11 @@ 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 { MemoryLevel } from 'npm:memory-level'; export { DataFactory } from 'npm:rdf-data-factory'; +export { MemoryLevel } from 'npm:memory-level'; + +export { jsonld } from "npm:jsonld"; + +// exported types export type { Quad, NamedNode, Literal, Term, Stream } from 'npm:@rdfjs/types'; + diff --git a/flow-core/src/utils/quadstore-utils.ts b/flow-core/src/utils/quadstore-utils.ts new file mode 100644 index 0000000..1b63ebc --- /dev/null +++ b/flow-core/src/utils/quadstore-utils.ts @@ -0,0 +1,124 @@ +import { NamedNode, Stream } from '../deps.ts'; +import { jsonld } from '../deps.ts'; +import { defaultQuadstoreBundle } from '../../../flow-service/src/quadstoreDefaultBundle.ts'; +import { jsonldToQuads } from './rdfjs-utils.ts' + +export function countQuadsInStream(stream: 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: NamedNode, + { + store + } = defaultQuadstoreBundle +): Promise { + const matchStream = store.match(undefined, undefined, undefined, graph); + const count = await countQuadsInStream(matchStream); + + return count === 0; +} + + +// Clears all quads in the specified named graph. Returns the number of quads deleted. + +export async function clearGraph( + graph: NamedNode, + { + store + } = defaultQuadstoreBundle +): Promise { + const matchStream = store.match(undefined, undefined, undefined, graph); + const count = await countQuadsInStream(matchStream); + + if (count > 0) { + const delStream = store.match(undefined, undefined, undefined, graph); + await store.delStream(delStream as any); + } + + return count; +} + +// Copies all quads from source graph to target graph. + +export async function copyGraph( + sourceGraph: NamedNode, + targetGraph: NamedNode, + { + store, + df, + } = defaultQuadstoreBundle +): Promise { + const stream = store.match(undefined, undefined, undefined, sourceGraph); + // TODO: multiPut + for await (const q of stream as any) { + const newQuad = df.quad(q.subject, q.predicate, q.object, targetGraph); + await store.put(newQuad); + } +} + + +// Converts a JSON-LD object to quads and puts them into the specified graph with store.multiPut + +export async function putJsonLDToGraph( + obj: Record, + graphName: string, + { + store, + df, + } = defaultQuadstoreBundle +): Promise { + +} + +// For each (subject, predicate) in the jsonld, remove matching, then insert. + + +export async function patchJsonLDToGraph( + obj: Record, + graphName: string, + { + store, + df, + } = defaultQuadstoreBundle +): Promise { + +} + +// Creates a new graph from a JSON-LD object, +// By default, fails if target graph isn't empty, but can overwrite ALL existing quads if specified. +// + +export async function createNewGraphFromJsonLD( + obj: Record, + graphName: string, + overwrite: boolean = false, + { + store, + df, + } = defaultQuadstoreBundle +): Promise { + // Clear existing quads in the graph + await clearGraph(df.namedNode(graphName)); + + // Convert obj to quads and add to store + const id = (obj as Record)['@id']; + if (typeof id !== 'string') { + throw new Error('Config object must have a valid @id string'); + } + const subject = df.namedNode(id); + for (const [key, value] of Object.entries(configObj)) { + if (key === '@context' || key === '@type' || key === '@id') continue; + const predicate = df.namedNode(key); + const object = typeof value === 'string' ? df.literal(value) : df.literal(JSON.stringify(value)); + await store.put(df.quad(subject, predicate, object, df.namedNode(graphName))); + } +} diff --git a/flow-core/src/utils/quadstore/quadstore-utils.ts b/flow-core/src/utils/quadstore/quadstore-utils.ts deleted file mode 100644 index 0b40ab6..0000000 --- a/flow-core/src/utils/quadstore/quadstore-utils.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { NamedNode, Stream } from '../../deps.ts'; -import { quadstore, df } from '../../../../flow-service/src/quadstore.ts'; - -/** - * Clears all quads in the specified named graph. - * - * @param store - The Quadstore instance - * @param graph - The named graph to clear - * @param countOnly - If true, only count quads without deleting - * @returns The number of quads counted or deleted - */ -/** - * Delete all quads in the given graph and return the count of deleted quads. - * If `countOnly` is true, it does not delete but still counts. - */ -export async function clearGraph( - graph: NamedNode, -): Promise { - const matchStream = quadstore.match(undefined, undefined, undefined, graph); - const count = await countQuads(matchStream); - - if (count > 0) { - //TODO maybe delStream would be better - quadstore.removeMatches(undefined, undefined, undefined, graph); - } - - return count; -} -async function countQuads(stream: 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)); - }); -} - -/** - * Copies all quads from source graph to target graph. - * - * @param store - The Quadstore instance - * @param sourceGraph - The source named graph - * @param targetGraph - The target named graph - */ -export async function copyGraph( - sourceGraph: NamedNode, - targetGraph: NamedNode -): Promise { - const stream = quadstore.match(undefined, undefined, undefined, sourceGraph); - for await (const q of stream as any) { - const newQuad = df.quad(q.subject, q.predicate, q.object, targetGraph); - await quadstore.put(newQuad); - } -} diff --git a/flow-core/src/utils/rdfjs-utils.ts b/flow-core/src/utils/rdfjs-utils.ts new file mode 100644 index 0000000..20dbcb6 --- /dev/null +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -0,0 +1,8 @@ +import { Quad } from '../deps.ts'; + +// TODO implement +export async function jsonldToQuads(obj: Record): Promise { + let quads: Quad[] = []; + + return quads +} diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index c6589ef..5fc94a3 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -1,7 +1,8 @@ -import { quadstore, df } from '../../quadstore.ts'; +import { defaultQuadstoreBundle } from '../../quadstoreDefaultBundle.ts'; import type { ServiceConfigInput, NodeConfigInput } from '../types.ts'; import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; import { clearGraph, copyGraph } from '../../../../flow-core/src/utils/quadstore/quadstore-utils.ts'; +import { ServiceConfig } from '../index.ts'; /** * Named graph terms for Quadstore config graphs @@ -40,7 +41,12 @@ export async function loadInputNodeConfig(inputNodeConfig: NodeConfigInput): Pro /** * Merge input and platform graphs into merged service config graph */ -export async function mergeServiceConfigGraphs(): Promise { +export async function mergeServiceConfigGraphs( + { + store, + df, + } = defaultQuadstoreBundle +): Promise { // Clear merged graph const count = await clearGraph(df.namedNode(GRAPH_NAMES.mergedServiceConfig)); if (count > 0) { @@ -51,33 +57,13 @@ export async function mergeServiceConfigGraphs(): Promise { await copyGraph(df.namedNode(GRAPH_NAMES.inputServiceConfig), df.namedNode(GRAPH_NAMES.mergedServiceConfig)); // Copy platform service defaults quads if not overridden - const platformQuads = quadstore.match(undefined, undefined, undefined, df.namedNode(GRAPH_NAMES.platformServiceDefaults)); + const platformQuads = store.match(undefined, undefined, undefined, df.namedNode(GRAPH_NAMES.platformServiceDefaults)); for await (const q of platformQuads as any) { - const exists = await quadstore.countQuads(q.subject, q.predicate, undefined, df.namedNode(GRAPH_NAMES.mergedServiceConfig)); + const exists = await store.countQuads(q.subject, q.predicate, undefined, df.namedNode(GRAPH_NAMES.mergedServiceConfig)); if (exists === 0) { - await quadstore.put(df.quad(q.subject, q.predicate, q.object, df.namedNode(GRAPH_NAMES.mergedServiceConfig))); + await store.put(df.quad(q.subject, q.predicate, q.object, df.namedNode(GRAPH_NAMES.mergedServiceConfig))); } } } -/** - * Helper to convert a config object to RDF quads and load into a named graph - * This is a simplified placeholder; a real implementation would convert JSON-LD fully. - */ -async function loadConfigObjectToGraph(configObj: object, graphName: string): Promise { - // Clear existing quads in the graph - await clearGraph(df.namedNode(graphName)); - // Convert configObj to quads and add to store - const id = (configObj as Record)['@id']; - if (typeof id !== 'string') { - throw new Error('Config object must have a valid @id string'); - } - const subject = df.namedNode(id); - for (const [key, value] of Object.entries(configObj)) { - if (key === '@context' || key === '@type' || key === '@id') continue; - const predicate = df.namedNode(key); - const object = typeof value === 'string' ? df.literal(value) : df.literal(JSON.stringify(value)); - await quadstore.put(df.quad(subject, predicate, object, df.namedNode(graphName))); - } -} diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index 247f83d..fed68ec 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -1,4 +1,7 @@ import type { + AttributedTo, + DelegationChain, + LogLevel, ServiceConfigContext, ServiceConfigInput, ServiceOptions, @@ -7,6 +10,7 @@ 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 { mergeConfigs } from '../../utils/merge-configs.ts'; diff --git a/flow-service/src/config/types.ts b/flow-service/src/config/types.ts index ec0e6d7..181d152 100644 --- a/flow-service/src/config/types.ts +++ b/flow-service/src/config/types.ts @@ -155,6 +155,19 @@ export interface ServiceOptions { readonly sentryEnabled?: boolean; } +// Imports for QuadstoreBundle types +// usage: const bundle: QuadstoreBundle> = { ... }; +import type { Quadstore, DataFactory } from '../../../flow-core/src/deps.ts'; +import type { AbstractLevel } from 'npm:abstract-level'; + +// Quadstore Bundle Type +export interface QuadstoreBundle = AbstractLevel> { + store: Quadstore; + df: DataFactory; + backend: B; +} + + // Environment Variable Mapping export interface EnvironmentConfig { readonly FLOW_CONFIG_PATH?: string; diff --git a/flow-service/src/quadstore.ts b/flow-service/src/quadstore.ts deleted file mode 100644 index 5578d94..0000000 --- a/flow-service/src/quadstore.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Quadstore, MemoryLevel, DataFactory } from '../../flow-core/src/deps.ts'; - -const db = new MemoryLevel(); -const df = new DataFactory(); -const quadstore = new Quadstore({ backend: db, dataFactory: df }); - -await quadstore.open(); - -export { quadstore, df }; diff --git a/flow-service/src/quadstoreDefaultBundle.ts b/flow-service/src/quadstoreDefaultBundle.ts new file mode 100644 index 0000000..67708b8 --- /dev/null +++ b/flow-service/src/quadstoreDefaultBundle.ts @@ -0,0 +1,9 @@ +import { Quadstore, MemoryLevel, DataFactory } from '../../flow-core/src/deps.ts'; + +const backend = new MemoryLevel(); +const df = new DataFactory(); +const store = new Quadstore({ backend, dataFactory: df }); + +await store.open(); + +export const defaultQuadstoreBundle = { store, df, backend }; diff --git a/flow-service/tests/integration/quadstore.test.ts b/flow-service/tests/integration/quadstore.test.ts index 90d3214..b2286ed 100644 --- a/flow-service/tests/integration/quadstore.test.ts +++ b/flow-service/tests/integration/quadstore.test.ts @@ -1,38 +1,54 @@ -import { Quadstore, MemoryLevel, DataFactory } from '../../../flow-core/src/deps.ts'; -import { quadstore, df } from '../../src/quadstore.ts'; -import { Quad } from '../../../flow-core/src/deps.ts'; +import { assertEquals } from '../../../flow-core/src/deps.ts'; +import { MemoryLevel, DataFactory, Quadstore, NamedNode } 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(); -// Open the store -await quadstore.open(); + const testQuadstoreBundle = { store, df, backend }; -// Put a single quad into the store using Quadstore's API -await quadstore.put(df.quad( - df.namedNode('http://example.com/subject'), - df.namedNode('http://example.com/predicate'), - df.namedNode('http://example.com/object'), - df.defaultGraph(), -)); + const graphA: NamedNode = df.namedNode('urn:graphA'); + const graphB: NamedNode = df.namedNode('urn:graphB'); + // Clear both graphs before test + await clearGraph(graphA, testQuadstoreBundle); + await clearGraph(graphB, testQuadstoreBundle); -// Retrieves all quads using RDF/JS Stream interfaces -const quadsStream = quadstore.match(undefined, undefined, undefined, df.defaultGraph()); -quadsStream.on('data', (quad: Quad) => console.log(quad)); + // 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)); -const quadsStream2 = quadstore.match(undefined, undefined, undefined, df.defaultGraph()); -await quadstore.delStream(quadsStream2 as any); + // Verify graphA has 1 quad + let count = await countQuadsInGraph(graphA, store); + assertEquals(count, 1); -console.log('All quads deleted'); + // Copy graphA to graphB + await copyGraph(graphA, graphB, testQuadstoreBundle); -const quadsStream3 = quadstore.match(undefined, undefined, undefined, df.defaultGraph()); -quadsStream3.on('data', quad => console.log(quad)); + // 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); -// Retrieves all quads using Quadstore's API -const { items } = await quadstore.get({}); + // Verify graphB still has 1 quad + count = await countQuadsInGraph(graphB, store); + assertEquals(count, 1); +}); -for (const item of items) { - console.log(item); +function countQuadsInGraph(graph: NamedNode, store: Quadstore): Promise { + const stream = store.match(undefined, undefined, undefined, graph); + return countQuadsInStream(stream); } From b8b944994227ca33323b97423de1e590303c9aee Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Mon, 28 Jul 2025 18:36:03 -0700 Subject: [PATCH 07/20] using @types/jsonld in my types.ts; starting a new task --- deno.lock | 4 + flow-core/src/deps.ts | 10 +- flow-core/src/utils/quadstore-utils.ts | 53 +++++---- flow-core/src/utils/rdfjs-utils.ts | 48 +++++++- flow-core/tests/unit/quadstore-utils.test.ts | 76 +++++++++++++ flow-service/deno.lock | 103 ++++++++++++++++++ flow-service/src/config/defaults.ts | 12 +- flow-service/src/config/index.ts | 12 +- flow-service/src/config/loaders/env-loader.ts | 2 +- .../src/config/loaders/jsonld-loader.ts | 24 ++-- .../src/config/loaders/quadstore-loader.ts | 18 +-- .../src/config/node-config.default.jsonld | 2 +- .../resolution/service-config-resolver.ts | 5 +- flow-service/src/config/types.ts | 50 ++++----- .../tests/integration/quadstore.test.ts | 2 +- 15 files changed, 326 insertions(+), 95 deletions(-) create mode 100644 flow-core/tests/unit/quadstore-utils.test.ts diff --git a/deno.lock b/deno.lock index 6b63dff..d98d005 100644 --- a/deno.lock +++ b/deno.lock @@ -11,6 +11,7 @@ "npm:@scalar/hono-api-reference@*": "0.9.12_hono@4.8.5", "npm:@scalar/openapi-to-markdown@*": "0.2.23", "npm:@sentry/deno@*": "9.40.0", + "npm:@types/jsonld@*": "1.5.15", "npm:@types/node@*": "22.15.15", "npm:jsonld@*": "8.3.3", "npm:memory-level@*": "3.1.0", @@ -455,6 +456,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": [ diff --git a/flow-core/src/deps.ts b/flow-core/src/deps.ts index c44b563..6776097 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -17,7 +17,15 @@ export { Quadstore } from 'npm:quadstore'; export { DataFactory } from 'npm:rdf-data-factory'; export { MemoryLevel } from 'npm:memory-level'; -export { jsonld } from "npm:jsonld"; +import jsonldDefault from "npm:jsonld"; +export const jsonld = jsonldDefault; + +export type { + JsonLdDocument, + NodeObject, + ValueObject, + ContextDefinition, +} from "npm:@types/jsonld"; // exported types export type { Quad, NamedNode, Literal, Term, Stream } from 'npm:@rdfjs/types'; diff --git a/flow-core/src/utils/quadstore-utils.ts b/flow-core/src/utils/quadstore-utils.ts index 1b63ebc..86f5b3e 100644 --- a/flow-core/src/utils/quadstore-utils.ts +++ b/flow-core/src/utils/quadstore-utils.ts @@ -68,21 +68,22 @@ export async function copyGraph( // Converts a JSON-LD object to quads and puts them into the specified graph with store.multiPut -export async function putJsonLDToGraph( +export async function putJsonLdToGraph( obj: Record, graphName: string, { - store, - df, + store } = defaultQuadstoreBundle ): Promise { + // Convert JSON-LD to RDF quads + const quads = await jsonldToQuads(obj, graphName); + // Put quads into the store + await store.multiPut(quads); } // For each (subject, predicate) in the jsonld, remove matching, then insert. - - -export async function patchJsonLDToGraph( +export async function patchJsonLdToGraph( obj: Record, graphName: string, { @@ -90,14 +91,21 @@ export async function patchJsonLDToGraph( df, } = defaultQuadstoreBundle ): Promise { + // Convert JSON-LD to RDF quads + const quads = await jsonldToQuads(obj, graphName); + + // Remove matching quads for each subject-predicate pair + for (const q of quads) { + store.removeMatches(q.subject, q.predicate, undefined, df.namedNode(graphName)); + } + // Insert new quads + await store.multiPut(quads); } // Creates a new graph from a JSON-LD object, // By default, fails if target graph isn't empty, but can overwrite ALL existing quads if specified. -// - -export async function createNewGraphFromJsonLD( +export async function createNewGraphFromJsonLd( obj: Record, graphName: string, overwrite: boolean = false, @@ -106,19 +114,18 @@ export async function createNewGraphFromJsonLD( df, } = defaultQuadstoreBundle ): Promise { - // Clear existing quads in the graph - await clearGraph(df.namedNode(graphName)); - - // Convert obj to quads and add to store - const id = (obj as Record)['@id']; - if (typeof id !== 'string') { - throw new Error('Config object must have a valid @id string'); - } - const subject = df.namedNode(id); - for (const [key, value] of Object.entries(configObj)) { - if (key === '@context' || key === '@type' || key === '@id') continue; - const predicate = df.namedNode(key); - const object = typeof value === 'string' ? df.literal(value) : df.literal(JSON.stringify(value)); - await store.put(df.quad(subject, predicate, object, df.namedNode(graphName))); + if (!overwrite) { + const isEmpty = await isGraphEmpty(df.namedNode(graphName)); + if (!isEmpty) { + throw new Error(`Graph ${graphName} is not empty. Use overwrite option to clear it.`); + } + } else { + await clearGraph(df.namedNode(graphName)); } + + // Convert JSON-LD to RDF quads + const quads = await jsonldToQuads(obj, graphName); + + // Put quads into the store + await store.multiPut(quads); } diff --git a/flow-core/src/utils/rdfjs-utils.ts b/flow-core/src/utils/rdfjs-utils.ts index 20dbcb6..ac7a772 100644 --- a/flow-core/src/utils/rdfjs-utils.ts +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -1,8 +1,46 @@ -import { Quad } from '../deps.ts'; +import { jsonld } from '../deps.ts'; +import type { Quad } from 'npm:@rdfjs/types'; +import { DataFactory } from 'npm:rdf-data-factory'; -// TODO implement -export async function jsonldToQuads(obj: Record): Promise { - let quads: Quad[] = []; +const df = new DataFactory(); - return quads +// Converts a JSON-LD object to an array of RDFJS quads using a local DataFactory instance +export async function jsonldToQuads( + obj: Record, + graphName: string, +): Promise { + // Expand the JSON-LD object to full form + const expanded = await jsonld.expand(obj); + + // Convert expanded JSON-LD to RDFJS quads + const quads: Quad[] = []; + + for (const node of expanded) { + const subject = df.namedNode(node['@id'] as string); + + for (const [key, values] of Object.entries(node)) { + if (key === '@id' || key === '@type' || key === '@context') continue; + + for (const value of values as any[]) { + let object; + + if (value['@id']) { + object = df.namedNode(value['@id']); + } else if (value['@value'] !== undefined) { + const datatype = value['@type'] ? df.namedNode(value['@type']) : undefined; + const language = value['@language']; + object = df.literal(value['@value'], language || datatype); + } else { + // For complex nested objects, serialize as JSON string literal + object = df.literal(JSON.stringify(value)); + } + + const predicate = df.namedNode(key); + const quadObj = df.quad(subject, predicate, object, df.namedNode(graphName)); + quads.push(quadObj); + } + } + } + + return quads; } 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..4a02b40 --- /dev/null +++ b/flow-core/tests/unit/quadstore-utils.test.ts @@ -0,0 +1,76 @@ +import { assertEquals } from '../../src/deps.ts'; +import { clearGraph, copyGraph, isGraphEmpty, countQuadsInStream, putJsonLdToGraph, patchJsonLdToGraph, createNewGraphFromJsonLd } from '../../src/utils/quadstore-utils.ts'; +import { defaultQuadstoreBundle } from '../../../flow-service/src/quadstoreDefaultBundle.ts'; + +Deno.test('countQuadsInStream counts quads correctly', async () => { + const { store, df } = defaultQuadstoreBundle; + 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 { store, df } = defaultQuadstoreBundle; + const graph = df.namedNode('urn:testGraph2'); + await clearGraph(graph); + + let empty = await isGraphEmpty(graph); + 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); + assertEquals(empty, false); + + await clearGraph(graph); +}); + +Deno.test('clearGraph deletes all quads and returns count', async () => { + const { store, df } = defaultQuadstoreBundle; + const graph = df.namedNode('urn:testGraph3'); + await clearGraph(graph); + + const quad = df.quad(df.namedNode('s'), df.namedNode('p'), df.literal('o'), graph); + await store.put(quad); + + const count = await clearGraph(graph); + assertEquals(count, 1); + + const empty = await isGraphEmpty(graph); + assertEquals(empty, true); +}); + +Deno.test('copyGraph copies quads from source to target graph', async () => { + const { store, df } = defaultQuadstoreBundle; + const sourceGraph = df.namedNode('urn:sourceGraph'); + const targetGraph = df.namedNode('urn:targetGraph'); + + await clearGraph(sourceGraph); + await clearGraph(targetGraph); + + const quad = df.quad(df.namedNode('s'), df.namedNode('p'), df.literal('o'), sourceGraph); + await store.put(quad); + + await copyGraph(sourceGraph, targetGraph); + + 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); + + await clearGraph(sourceGraph); + await clearGraph(targetGraph); +}); diff --git a/flow-service/deno.lock b/flow-service/deno.lock index 53ca352..abe02ed 100644 --- a/flow-service/deno.lock +++ b/flow-service/deno.lock @@ -14,7 +14,9 @@ "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:hono@*": "4.8.4", + "npm:jsonld@*": "8.3.3", "npm:memory-level@*": "3.1.0", "npm:quadstore@*": "15.3.0", "npm:rdf-data-factory@*": "2.0.2", @@ -56,6 +58,17 @@ "@babel/helper-validator-identifier" ] }, + "@digitalbazaar/http-client@3.4.1_ky@0.33.3": { + "integrity": "sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==", + "dependencies": [ + "ky", + "ky-universal", + "undici" + ] + }, + "@fastify/busboy@2.1.1": { + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==" + }, "@floating-ui/core@1.7.2": { "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", "dependencies": [ @@ -446,6 +459,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": [ @@ -572,6 +588,12 @@ "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": [ @@ -646,6 +668,9 @@ "tslib" ] }, + "canonicalize@1.0.8": { + "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==" + }, "ccount@2.0.1": { "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" }, @@ -697,6 +722,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": [ @@ -740,6 +768,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==" }, @@ -749,9 +780,22 @@ "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==" }, @@ -1007,6 +1051,15 @@ "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==" }, @@ -1016,6 +1069,17 @@ "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==" }, @@ -1046,6 +1110,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": [ @@ -1457,6 +1527,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": [ @@ -1537,6 +1619,12 @@ "vue" ] }, + "rdf-canonize@3.4.0": { + "integrity": "sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==", + "dependencies": [ + "setimmediate" + ] + }, "rdf-data-factory@2.0.2": { "integrity": "sha512-WzPoYHwQYWvIP9k+7IBLY1b4nIDitzAK4mA37WumAF/Cjvu/KOtYJH9IPZnUTWNSd5K2+pq4vrcE9WZC4sRHhg==", "dependencies": [ @@ -1656,6 +1744,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==" }, @@ -1721,6 +1812,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": [ @@ -1822,6 +1919,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 diff --git a/flow-service/src/config/defaults.ts b/flow-service/src/config/defaults.ts index 0044c05..e29c273 100644 --- a/flow-service/src/config/defaults.ts +++ b/flow-service/src/config/defaults.ts @@ -5,11 +5,11 @@ * Based on the existing JSON-LD configuration files. */ -import type { JSONLDContext, NodeConfig, ServiceConfig } from './types.ts'; +import type { FlowServiceContext, MeshNodeConfig, ServiceConfig } from './types.ts'; import { mergeConfigs } from '../utils/merge-configs.ts'; // Standard JSON-LD Context -export const DEFAULT_CONTEXT: JSONLDContext = { +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/', @@ -18,9 +18,9 @@ export const DEFAULT_CONTEXT: JSONLDContext = { }; // Platform Node Configuration Defaults -export const PLATFORM_NODE_DEFAULTS: NodeConfig = { +export const PLATFORM_NODE_DEFAULTS: MeshNodeConfig = { '@context': DEFAULT_CONTEXT, - '@type': 'conf:NodeConfig', + '@type': 'conf:MeshNodeConfig', '@id': 'conf:defaultConfig', 'conf:versioningEnabled': true, 'conf:configInheritanceEnabled': true, @@ -48,11 +48,11 @@ export const PLATFORM_NODE_DEFAULTS: NodeConfig = { * - Omits @id to avoid conflicts in service context */ export const SERVICE_NODE_DEFAULTS: Omit< - NodeConfig, + MeshNodeConfig, '@id' | 'conf:generateResourcePages' | 'conf:stylesheetPath' > = { '@context': DEFAULT_CONTEXT, - '@type': 'conf:NodeConfig', + '@type': 'conf:MeshNodeConfig', 'conf:versioningEnabled': true, 'conf:configInheritanceEnabled': true, 'conf:distributionFormats': [ diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index 2da2904..39fc7fa 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -8,13 +8,13 @@ // Core Types export type { ContainedServicesConfig, - JSONLDContext, + FlowServiceContext, LogChannelConfig, LoggingConfig, LogLevel, - NodeConfig, - NodeConfigContext, - NodeConfigInput, + MeshNodeConfig, + MeshNodeConfigContext, + MeshNodeConfigInput, ServiceConfig, ServiceConfigContext, ServiceConfigInput, @@ -54,9 +54,9 @@ export { configExists, getNodeHierarchy, isConfigInheritanceEnabled, - loadNodeConfig, + loadMeshNodeConfig, loadServiceConfig, - saveNodeConfig, + saveMeshNodeConfig, saveServiceConfig, validateJSONLD, } from './loaders/jsonld-loader.ts'; diff --git a/flow-service/src/config/loaders/env-loader.ts b/flow-service/src/config/loaders/env-loader.ts index 6e66021..e7b4824 100644 --- a/flow-service/src/config/loaders/env-loader.ts +++ b/flow-service/src/config/loaders/env-loader.ts @@ -224,7 +224,7 @@ export function loadEnvConfig(): ServiceConfigInput { } if (hasNodeDefaults) { - nodeDefaults['@type'] = 'conf:NodeConfig'; + nodeDefaults['@type'] = 'conf:MeshNodeConfig'; configInput['fsvc:nodeDefaults'] = nodeDefaults; } diff --git a/flow-service/src/config/loaders/jsonld-loader.ts b/flow-service/src/config/loaders/jsonld-loader.ts index c10a06b..934778f 100644 --- a/flow-service/src/config/loaders/jsonld-loader.ts +++ b/flow-service/src/config/loaders/jsonld-loader.ts @@ -5,7 +5,7 @@ * Supports both service and node configuration formats. */ -import type { NodeConfigInput, ServiceConfigInput } from '../types.ts'; +import type { MeshNodeConfigInput, ServiceConfigInput } from '../types.ts'; import { ConfigError } from '../types.ts'; import { handleCaughtError } from '../../utils/logger.ts'; import { getCurrentConfigDistPath } from '../../../../flow-core/src/utils/mesh-path-utils.ts'; @@ -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 loadMeshNodeConfig( 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 MeshNodeConfigInput; } 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 saveMeshNodeConfig( nodePath: string, - config: NodeConfigInput, + config: MeshNodeConfigInput, ): 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 loadMeshNodeConfig(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 diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index 5fc94a3..bbaa721 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -1,7 +1,7 @@ import { defaultQuadstoreBundle } from '../../quadstoreDefaultBundle.ts'; -import type { ServiceConfigInput, NodeConfigInput } from '../types.ts'; +import type { ServiceConfigInput, MeshNodeConfigInput } from '../types.ts'; import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; -import { clearGraph, copyGraph } from '../../../../flow-core/src/utils/quadstore/quadstore-utils.ts'; +import { clearGraph, copyGraph, createNewGraphFromJsonLd } from '../../../../flow-core/src/utils/quadstore-utils.ts'; import { ServiceConfig } from '../index.ts'; /** @@ -9,9 +9,9 @@ import { ServiceConfig } from '../index.ts'; */ export const GRAPH_NAMES = { platformServiceDefaults: 'graph/platformServiceDefaults', - platformImplicitNodeConfig: 'graph/platformImplicitNodeConfig', + platformImplicitMeshNodeConfig: 'graph/platformImplicitMeshNodeConfig', inputServiceConfig: 'graph/inputServiceConfig', - inputNodeConfig: 'graph/inputNodeConfig', + inputMeshNodeConfig: 'graph/inputMeshNodeConfig', mergedServiceConfig: 'graph/mergedServiceConfig', // Per-node effective configs could be stored with dynamic graph names }; @@ -20,22 +20,22 @@ export const GRAPH_NAMES = { * Load platform defaults into Quadstore graphs */ export async function loadPlatformDefaults(): Promise { - await loadConfigObjectToGraph(PLATFORM_SERVICE_DEFAULTS, GRAPH_NAMES.platformServiceDefaults); - await loadConfigObjectToGraph(PLATFORM_NODE_DEFAULTS, GRAPH_NAMES.platformImplicitNodeConfig); + await createNewGraphFromJsonLd(PLATFORM_SERVICE_DEFAULTS, GRAPH_NAMES.platformServiceDefaults); + await createNewGraphFromJsonLd(PLATFORM_NODE_DEFAULTS, GRAPH_NAMES.platformImplicitMeshNodeConfig); } /** * Load input service config into Quadstore graph */ export async function loadInputServiceConfig(inputConfig: ServiceConfigInput): Promise { - await loadConfigObjectToGraph(inputConfig, GRAPH_NAMES.inputServiceConfig); + await createNewGraphFromJsonLd(inputConfig, GRAPH_NAMES.inputServiceConfig); } /** * Load input node config overrides into Quadstore graph */ -export async function loadInputNodeConfig(inputNodeConfig: NodeConfigInput): Promise { - await loadConfigObjectToGraph(inputNodeConfig, GRAPH_NAMES.inputNodeConfig); +export async function loadInputMeshNodeConfig(inputMeshNodeConfig: MeshNodeConfigInput): Promise { + await createNewGraphFromJsonLd(inputMeshNodeConfig, GRAPH_NAMES.inputMeshNodeConfig); } /** diff --git a/flow-service/src/config/node-config.default.jsonld b/flow-service/src/config/node-config.default.jsonld index 2a2ec9f..3e60db5 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:defaultMeshNodeConfig", "conf:versioningEnabled": true, "conf:configInheritanceEnabled": true, "conf:distributionFormats": [ diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index fed68ec..1a186c0 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -17,14 +17,15 @@ import { mergeConfigs } from '../../utils/merge-configs.ts'; import { handleCaughtError } from '../../utils/logger.ts'; import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; import { getConfigValue, mergeConfigContext } from './service-config-utils.ts'; +import { loadPlatformDefaults, loadInputServiceConfig, loadInputMeshNodeConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.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. + * Returns a ServiceConfigContext 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 + * @returns A ServiceConfigContext object with both input and default configuration options * @throws ConfigError if configuration resolution fails or an unexpected error occurs */ export async function resolveServiceConfig( diff --git a/flow-service/src/config/types.ts b/flow-service/src/config/types.ts index 181d152..dd57b9f 100644 --- a/flow-service/src/config/types.ts +++ b/flow-service/src/config/types.ts @@ -5,8 +5,10 @@ * Supports sparse input configurations that are validated against complete defaults. */ +import { NodeObject, ContextDefinition } from '../../../flow-core/src/deps.ts'; + // JSON-LD Context and Type Definitions -export interface JSONLDContext { +export interface FlowServiceContext extends ContextDefinition { readonly fsvc: 'https://semantic-flow.github.io/ontology/flow-service/'; readonly mesh: 'https://semantic-flow.github.io/ontology/mesh/'; readonly node: 'https://semantic-flow.github.io/ontology/node/'; @@ -14,14 +16,8 @@ export interface JSONLDContext { readonly conf: 'https://semantic-flow.github.io/ontology/config-flow/'; } -export interface JSONLDBase { - readonly '@context': JSONLDContext; - readonly '@type': string; - readonly '@id'?: string; -} - // Logging Configuration Types -export interface LogChannelConfig { +export interface LogChannelConfig extends NodeObject { readonly '@type': 'fsvc:LogChannelConfig'; readonly 'fsvc:logChannelEnabled': boolean; readonly 'fsvc:logLevel': 'debug' | 'info' | 'warn' | 'error'; @@ -39,7 +35,7 @@ export interface LogChannelConfig { | 'size-based'; } -export interface LoggingConfig { +export interface LoggingConfig extends NodeObject { readonly '@type': 'fsvc:LoggingConfig'; readonly 'fsvc:hasConsoleChannel': LogChannelConfig; readonly 'fsvc:hasFileChannel': LogChannelConfig; @@ -47,7 +43,7 @@ export interface LoggingConfig { } // Contained Services Configuration -export interface ContainedServicesConfig { +export interface ContainedServicesConfig extends NodeObject { readonly '@type': 'fsvc:ContainedServicesConfig'; readonly 'fsvc:apiEnabled': boolean; readonly 'fsvc:sparqlEnabled': boolean; @@ -57,13 +53,13 @@ export interface ContainedServicesConfig { } // Node Configuration Types (for service defaults) -export interface TemplateMapping { +export interface TemplateMapping extends NodeObject { readonly '@type': 'conf:TemplateMapping'; readonly 'conf:hasResourcePageTemplate': string; } -export interface NodeConfig extends JSONLDBase { - readonly '@type': 'conf:NodeConfig'; +export interface MeshNodeConfig extends NodeObject { + readonly '@type': 'conf:MeshNodeConfig'; readonly 'conf:versioningEnabled': boolean; readonly 'conf:distributionFormats': string[]; readonly 'conf:templateMappings'?: TemplateMapping; @@ -76,26 +72,26 @@ export interface NodeConfig extends JSONLDBase { } // Complete Service Configuration -export interface ServiceConfig extends JSONLDBase { +export interface ServiceConfig extends NodeObject { readonly '@type': 'fsvc:ServiceConfig'; readonly 'fsvc:port': number; readonly 'fsvc:host': string; readonly 'fsvc:meshPaths'?: string[]; readonly 'fsvc:hasLoggingConfig': LoggingConfig; readonly 'fsvc:hasContainedServices': ContainedServicesConfig; - readonly 'fsvc:rootNodeConfigTemplate'?: NodeConfig; + readonly 'fsvc:rootMeshNodeConfigTemplate'?: MeshNodeConfig; readonly 'fsvc:defaultDelegationChain'?: DelegationChain; readonly 'fsvc:defaultAttributedTo'?: AttributedTo; } // Attribution Configuration -export interface AttributedTo { +export interface AttributedTo extends NodeObject { readonly '@id': string; - [key: string]: unknown; + //TODO definition } // Delegation Chain Configuration -export interface DelegationStep { +export interface DelegationStep extends NodeObject { readonly '@type': 'meta:DelegationStep'; readonly 'meta:stepOrder': number; readonly 'prov:agent': { @@ -103,26 +99,26 @@ export interface DelegationStep { }; } -export interface DelegationChain { +export interface DelegationChain extends NodeObject { readonly '@type': 'meta:DelegationChain'; readonly 'meta:hasStep': DelegationStep[]; } // Partial Types for Sparse Input Configuration (mutable for construction) -export interface ServiceConfigInput extends Partial { +export interface ServiceConfigInput extends Partial { '@type'?: 'fsvc:ServiceConfig'; 'fsvc:port'?: number; 'fsvc:host'?: string; 'fsvc:meshPaths'?: string[]; 'fsvc:hasLoggingConfig'?: Partial; 'fsvc:hasContainedServices'?: Partial; - 'fsvc:nodeDefaults'?: Partial; + 'fsvc:nodeDefaults'?: Partial; 'fsvc:defaultDelegationChain'?: Partial; 'fsvc:defaultAttributedTo'?: Partial; } -export interface NodeConfigInput extends Partial { - '@type'?: 'conf:NodeConfig'; +export interface MeshNodeConfigInput extends Partial { + '@type'?: 'conf:MeshNodeConfig'; 'conf:versioningEnabled'?: boolean; 'conf:distributionFormats'?: string[]; 'conf:templateMappings'?: Partial; @@ -140,9 +136,9 @@ export interface ServiceConfigContext { readonly defaultOptions: ServiceConfig; } -export interface NodeConfigContext { - readonly inputOptions: NodeConfigInput; - readonly defaultOptions: NodeConfig; +export interface MeshNodeConfigContext { + readonly inputOptions: MeshNodeConfigInput; + readonly defaultOptions: MeshNodeConfig; } // CLI Options Type @@ -280,7 +276,7 @@ export function getSentryEnabled(context: ServiceConfigContext): boolean { * * @returns `true` if versioning is enabled; otherwise, `false` */ -export function getVersioningEnabled(context: NodeConfigContext): boolean { +export function getVersioningEnabled(context: MeshNodeConfigContext): boolean { return context.inputOptions['conf:versioningEnabled'] ?? context.defaultOptions['conf:versioningEnabled']; } diff --git a/flow-service/tests/integration/quadstore.test.ts b/flow-service/tests/integration/quadstore.test.ts index b2286ed..40badf9 100644 --- a/flow-service/tests/integration/quadstore.test.ts +++ b/flow-service/tests/integration/quadstore.test.ts @@ -1,6 +1,6 @@ import { assertEquals } from '../../../flow-core/src/deps.ts'; import { MemoryLevel, DataFactory, Quadstore, NamedNode } from '../../../flow-core/src/deps.ts'; -import { countQuadsInStream, clearGraph, copyGraph } from '../../../flow-core/src/utils/quadstore/quadstore-utils.ts'; +import { countQuadsInStream, clearGraph, copyGraph } from '../../../flow-core/src/utils/quadstore-utils.ts'; Deno.test('Quadstore clearGraph and copyGraph utilities', async () => { const backend = new MemoryLevel(); From 5676c4a11dfd31be6feb76adf6b090b1dbe5a467 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Tue, 29 Jul 2025 00:34:32 -0700 Subject: [PATCH 08/20] config/types.ts -> config-types.ts; config cleanup; --- flow-core/src/deps.ts | 9 +- flow-core/src/types.ts | 13 +++ flow-core/src/utils/quadstore-utils.ts | 105 +++++++++--------- flow-core/src/utils/rdfjs-utils.ts | 15 +-- flow-core/tests/unit/quadstore-utils.test.ts | 2 +- flow-service/deno.lock | 83 +++++++++++++- flow-service/logs/test-pretty-direct.log | 8 +- .../src/config/{types.ts => config-types.ts} | 83 -------------- flow-service/src/config/defaults.ts | 3 +- flow-service/src/config/index.ts | 18 +-- flow-service/src/config/loaders/env-loader.ts | 4 +- .../src/config/loaders/jsonld-loader.ts | 28 +---- .../src/config/loaders/quadstore-loader.ts | 11 +- .../resolution/service-config-accessor.ts | 2 +- .../resolution/service-config-loader.ts | 2 +- .../resolution/service-config-resolver.ts | 9 +- .../config/resolution/service-config-utils.ts | 2 +- .../resolution/service-config-validator.ts | 4 +- flow-service/src/quadstoreDefaultBundle.ts | 5 +- .../logging/handle-caught-error.test.ts | 2 +- .../tests/integration/quadstore.test.ts | 9 +- 21 files changed, 200 insertions(+), 217 deletions(-) create mode 100644 flow-core/src/types.ts rename flow-service/src/config/{types.ts => config-types.ts} (69%) diff --git a/flow-core/src/deps.ts b/flow-core/src/deps.ts index 6776097..e7712ce 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -20,6 +20,10 @@ 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, @@ -27,6 +31,5 @@ export type { ContextDefinition, } from "npm:@types/jsonld"; -// exported types -export type { Quad, NamedNode, Literal, Term, Stream } from 'npm:@rdfjs/types'; - +//RDF: Quad, NamedNode, Literal, Term, Stream, DefaultGraph, etc +export type * as RDF from 'npm:@rdfjs/types'; diff --git a/flow-core/src/types.ts b/flow-core/src/types.ts new file mode 100644 index 0000000..39cf59c --- /dev/null +++ b/flow-core/src/types.ts @@ -0,0 +1,13 @@ +// Imports for QuadstoreBundle types +// usage: const bundle: QuadstoreBundle> = { ... }; +import type { Quadstore, DataFactory } from './deps.ts'; +import type { AbstractLevel } from 'npm:abstract-level'; +import { Engine } from 'npm:quadstore-comunica'; + +// Quadstore Bundle Type +export interface QuadstoreBundle = AbstractLevel> { + store: Quadstore; + df: DataFactory; + backend: B; + engine?: Engine; +} diff --git a/flow-core/src/utils/quadstore-utils.ts b/flow-core/src/utils/quadstore-utils.ts index 86f5b3e..e423a56 100644 --- a/flow-core/src/utils/quadstore-utils.ts +++ b/flow-core/src/utils/quadstore-utils.ts @@ -1,9 +1,11 @@ -import { NamedNode, Stream } from '../deps.ts'; +import { NodeObject, Quadstore, DataFactory } from '../deps.ts'; +import { RDF } from '../deps.ts'; import { jsonld } from '../deps.ts'; import { defaultQuadstoreBundle } from '../../../flow-service/src/quadstoreDefaultBundle.ts'; import { jsonldToQuads } from './rdfjs-utils.ts' +import type { QuadstoreBundle } from '../types.ts'; -export function countQuadsInStream(stream: Stream): Promise { +export function countQuadsInStream(stream: RDF.Stream): Promise { return new Promise((resolve, reject) => { let count = 0; stream.on('data', () => count++); @@ -16,7 +18,7 @@ export function countQuadsInStream(stream: Stream): Promise { // returns whether there are any quads in the specified named graph. export async function isGraphEmpty( - graph: NamedNode, + graph: RDF.NamedNode, { store } = defaultQuadstoreBundle @@ -31,10 +33,10 @@ export async function isGraphEmpty( // Clears all quads in the specified named graph. Returns the number of quads deleted. export async function clearGraph( - graph: NamedNode, + graph: RDF.NamedNode, { store - } = defaultQuadstoreBundle + }: { store: Quadstore } = defaultQuadstoreBundle ): Promise { const matchStream = store.match(undefined, undefined, undefined, graph); const count = await countQuadsInStream(matchStream); @@ -50,82 +52,79 @@ export async function clearGraph( // Copies all quads from source graph to target graph. export async function copyGraph( - sourceGraph: NamedNode, - targetGraph: NamedNode, + sourceGraph: RDF.NamedNode, + targetGraph: RDF.NamedNode, { store, df, - } = defaultQuadstoreBundle -): Promise { + }: { store: Quadstore, df: DataFactory } = defaultQuadstoreBundle +): Promise { const stream = store.match(undefined, undefined, undefined, sourceGraph); // TODO: multiPut + let count = 0; for await (const q of stream as any) { const newQuad = df.quad(q.subject, q.predicate, q.object, targetGraph); await store.put(newQuad); + count++; } + return count; } // Converts a JSON-LD object to quads and puts them into the specified graph with store.multiPut export async function putJsonLdToGraph( - obj: Record, - graphName: string, + inputJsonLd: NodeObject, + graphName?: string, { - store + store, + df } = defaultQuadstoreBundle -): Promise { +): Promise { // Convert JSON-LD to RDF quads - const quads = await jsonldToQuads(obj, graphName); + const graph = graphName ? df.namedNode(graphName) : df.defaultGraph(); + const quads = await jsonldToQuads(inputJsonLd, graph); // Put quads into the store await store.multiPut(quads); -} -// For each (subject, predicate) in the jsonld, remove matching, then insert. -export async function patchJsonLdToGraph( - obj: Record, - graphName: string, - { - store, - df, - } = defaultQuadstoreBundle -): Promise { - // Convert JSON-LD to RDF quads - const quads = await jsonldToQuads(obj, graphName); - - // Remove matching quads for each subject-predicate pair - for (const q of quads) { - store.removeMatches(q.subject, q.predicate, undefined, df.namedNode(graphName)); - } + return quads.length; +} - // Insert new quads - await store.multiPut(quads); +/** + * 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; } -// Creates a new graph from a JSON-LD object, -// By default, fails if target graph isn't empty, but can overwrite ALL existing quads if specified. export async function createNewGraphFromJsonLd( - obj: Record, - graphName: string, - overwrite: boolean = false, - { - store, - df, - } = defaultQuadstoreBundle -): Promise { - if (!overwrite) { - const isEmpty = await isGraphEmpty(df.namedNode(graphName)); - if (!isEmpty) { - throw new Error(`Graph ${graphName} is not empty. Use overwrite option to clear it.`); - } - } else { - await clearGraph(df.namedNode(graphName)); - } + inputJsonLd: NodeObject, + options: CreateGraphOptions = {} +): Promise { + const { + overwrite = false, + graphName = null, + bundle = defaultQuadstoreBundle, + } = options; - // Convert JSON-LD to RDF quads - const quads = await jsonldToQuads(obj, graphName); + const { store, df } = bundle; + const graph = graphName ? df.namedNode(graphName) : df.defaultGraph(); + const quads = await jsonldToQuads(inputJsonLd, graph); + if (overwrite && graphName) clearGraph(df.namedNode(graphName)); // Put quads into the store await store.multiPut(quads); + + return quads.length; } diff --git a/flow-core/src/utils/rdfjs-utils.ts b/flow-core/src/utils/rdfjs-utils.ts index ac7a772..03e5759 100644 --- a/flow-core/src/utils/rdfjs-utils.ts +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -1,19 +1,20 @@ import { jsonld } from '../deps.ts'; -import type { Quad } from 'npm:@rdfjs/types'; +import { NodeObject } from '../deps.ts'; +import type { RDF } from '../deps.ts'; import { DataFactory } from 'npm:rdf-data-factory'; const df = new DataFactory(); // Converts a JSON-LD object to an array of RDFJS quads using a local DataFactory instance export async function jsonldToQuads( - obj: Record, - graphName: string, -): Promise { + inputJsonLd: NodeObject, + graph: RDF.NamedNode | RDF.DefaultGraph = df.defaultGraph(), +): Promise { // Expand the JSON-LD object to full form - const expanded = await jsonld.expand(obj); + const expanded = await jsonld.expand(inputJsonLd); // Convert expanded JSON-LD to RDFJS quads - const quads: Quad[] = []; + const quads: RDF.Quad[] = []; for (const node of expanded) { const subject = df.namedNode(node['@id'] as string); @@ -36,7 +37,7 @@ export async function jsonldToQuads( } const predicate = df.namedNode(key); - const quadObj = df.quad(subject, predicate, object, df.namedNode(graphName)); + const quadObj = df.quad(subject, predicate, object, graph); quads.push(quadObj); } } diff --git a/flow-core/tests/unit/quadstore-utils.test.ts b/flow-core/tests/unit/quadstore-utils.test.ts index 4a02b40..4ba5419 100644 --- a/flow-core/tests/unit/quadstore-utils.test.ts +++ b/flow-core/tests/unit/quadstore-utils.test.ts @@ -1,5 +1,5 @@ import { assertEquals } from '../../src/deps.ts'; -import { clearGraph, copyGraph, isGraphEmpty, countQuadsInStream, putJsonLdToGraph, patchJsonLdToGraph, createNewGraphFromJsonLd } from '../../src/utils/quadstore-utils.ts'; +import { clearGraph, copyGraph, isGraphEmpty, countQuadsInStream, putJsonLdToGraph, createNewGraphFromJsonLd } from '../../src/utils/quadstore-utils.ts'; import { defaultQuadstoreBundle } from '../../../flow-service/src/quadstoreDefaultBundle.ts'; Deno.test('countQuadsInStream counts quads correctly', async () => { diff --git a/flow-service/deno.lock b/flow-service/deno.lock index abe02ed..cc3a5da 100644 --- a/flow-service/deno.lock +++ b/flow-service/deno.lock @@ -15,9 +15,12 @@ "npm:@sentry/deno@*": "8.55.0", "npm:@sentry/deno@8": "8.55.0", "npm:@types/jsonld@*": "1.5.15", + "npm:@types/node@*": "22.15.15", + "npm:abstract-level@*": "3.1.0", "npm:hono@*": "4.8.4", "npm:jsonld@*": "8.3.3", "npm:memory-level@*": "3.1.0", + "npm:quadstore-comunica@*": "6.2.0_asynciterator@3.9.0_quadstore@15.3.0", "npm:quadstore@*": "15.3.0", "npm:rdf-data-factory@*": "2.0.2", "npm:zod@^3.22.0": "3.25.76" @@ -193,6 +196,12 @@ "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": [ @@ -483,6 +492,12 @@ "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==" }, @@ -1027,6 +1042,9 @@ "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==" }, @@ -1506,6 +1524,9 @@ "micromark-util-types" ] }, + "minimist@1.2.8": { + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, "module-error@1.0.2": { "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==" }, @@ -1593,10 +1614,20 @@ "property-information@7.1.0": { "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" }, + "quadstore-comunica@6.2.0_asynciterator@3.9.0_quadstore@15.3.0": { + "integrity": "sha512-diibvQsX9i8VHHX47a0+3W/P0oXm9LYC0fQgZ+hsEJ3wvBXrWs4AFZlSy7A+0ze1FSnHnvRtRu5/FohdtlkyAg==", + "dependencies": [ + "@rdfjs/types@2.0.1", + "asynciterator", + "quadstore", + "sparqlalgebrajs", + "sparqljs" + ] + }, "quadstore@15.3.0": { "integrity": "sha512-KeFNurvPg61nO/WgdFYxbO/Sj80ne1gmzMGnWI0ukIXabdLggZ7i9TERRzPoyLHiUJP+Kkc1J2lZ+wl7n8hNBA==", "dependencies": [ - "@rdfjs/types", + "@rdfjs/types@2.0.1", "abstract-level", "asynciterator", "js-sorted-set" @@ -1625,10 +1656,37 @@ "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" + "@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": { @@ -1763,6 +1821,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": [ diff --git a/flow-service/logs/test-pretty-direct.log b/flow-service/logs/test-pretty-direct.log index 4da0abc..18445ef 100644 --- a/flow-service/logs/test-pretty-direct.log +++ b/flow-service/logs/test-pretty-direct.log @@ -1,4 +1,4 @@ -[2025-07-28T17:03:58.926Z] INFO File logging test started (op=startup, [config-resolver]) -[2025-07-28T17:03:58.931Z] WARN Configuration override detected (op=config-resolve, source=environment) -[2025-07-28T17:03:58.931Z] ERROR Database connection failed (op=startup, 5000ms) -[2025-07-28T17:03:58.932Z] DEBUG Processing mesh nodes (op=scan, mesh=/data/mesh, 1250ms, 42 nodes) +[2025-07-29T07:31:15.898Z] INFO File logging test started (op=startup, [config-resolver]) +[2025-07-29T07:31:15.899Z] WARN Configuration override detected (op=config-resolve, source=environment) +[2025-07-29T07:31:15.900Z] ERROR Database connection failed (op=startup, 5000ms) +[2025-07-29T07:31:15.900Z] DEBUG Processing mesh nodes (op=scan, mesh=/data/mesh, 1250ms, 42 nodes) diff --git a/flow-service/src/config/types.ts b/flow-service/src/config/config-types.ts similarity index 69% rename from flow-service/src/config/types.ts rename to flow-service/src/config/config-types.ts index dd57b9f..87bc1d1 100644 --- a/flow-service/src/config/types.ts +++ b/flow-service/src/config/config-types.ts @@ -151,18 +151,6 @@ export interface ServiceOptions { readonly sentryEnabled?: boolean; } -// Imports for QuadstoreBundle types -// usage: const bundle: QuadstoreBundle> = { ... }; -import type { Quadstore, DataFactory } from '../../../flow-core/src/deps.ts'; -import type { AbstractLevel } from 'npm:abstract-level'; - -// Quadstore Bundle Type -export interface QuadstoreBundle = AbstractLevel> { - store: Quadstore; - df: DataFactory; - backend: B; -} - // Environment Variable Mapping export interface EnvironmentConfig { @@ -209,74 +197,3 @@ export class ConfigValidationError extends ConfigError { // Utility Types for Configuration Access export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; -/** - * Retrieves the service port from the input options if specified; otherwise, returns the default port. - * - * @returns The configured service port number - */ -export function getServicePort(context: ServiceConfigContext): number { - return context.inputOptions['fsvc:port'] ?? - context.defaultOptions['fsvc:port']; -} - -/** - * Retrieves the service host from the input options if provided; otherwise, returns the default host. - * - * @returns The configured service host name or address. - */ -export function getServiceHost(context: ServiceConfigContext): string { - return context.inputOptions['fsvc:host'] ?? - context.defaultOptions['fsvc:host']; -} - -/** - * Retrieves the console log level from the service configuration context, preferring input options over defaults. - * - * @returns The log level for the console channel. - */ -export function getConsoleLogLevel(context: ServiceConfigContext): LogLevel { - return context.inputOptions['fsvc:hasLoggingConfig'] - ?.['fsvc:hasConsoleChannel']?.['fsvc:logLevel'] ?? - context - .defaultOptions['fsvc:hasLoggingConfig']['fsvc:hasConsoleChannel'][ - 'fsvc:logLevel' - ]; -} - -/** - * Determines whether file logging is enabled, preferring the input options if specified, otherwise using the default options. - * - * @returns `true` if file logging is enabled; otherwise, `false` - */ -export function getFileLogEnabled(context: ServiceConfigContext): boolean { - return context.inputOptions['fsvc:hasLoggingConfig']?.['fsvc:hasFileChannel'] - ?.['fsvc:logChannelEnabled'] ?? - context - .defaultOptions['fsvc:hasLoggingConfig']['fsvc:hasFileChannel'][ - 'fsvc:logChannelEnabled' - ]; -} - -/** - * Determines whether Sentry logging is enabled, preferring the input configuration if specified. - * - * @returns `true` if Sentry logging is enabled; otherwise, `false` - */ -export function getSentryEnabled(context: ServiceConfigContext): boolean { - return context.inputOptions['fsvc:hasLoggingConfig'] - ?.['fsvc:hasSentryChannel']?.['fsvc:logChannelEnabled'] ?? - context - .defaultOptions['fsvc:hasLoggingConfig']['fsvc:hasSentryChannel'][ - 'fsvc:logChannelEnabled' - ]; -} - -/** - * Determines whether versioning is enabled for a node, preferring input options over defaults. - * - * @returns `true` if versioning is enabled; otherwise, `false` - */ -export function getVersioningEnabled(context: MeshNodeConfigContext): boolean { - return context.inputOptions['conf:versioningEnabled'] ?? - context.defaultOptions['conf:versioningEnabled']; -} diff --git a/flow-service/src/config/defaults.ts b/flow-service/src/config/defaults.ts index e29c273..e174610 100644 --- a/flow-service/src/config/defaults.ts +++ b/flow-service/src/config/defaults.ts @@ -5,7 +5,7 @@ * Based on the existing JSON-LD configuration files. */ -import type { FlowServiceContext, MeshNodeConfig, ServiceConfig } from './types.ts'; +import type { FlowServiceContext, MeshNodeConfig, ServiceConfig } from './config-types.ts'; import { mergeConfigs } from '../utils/merge-configs.ts'; // Standard JSON-LD Context @@ -18,6 +18,7 @@ export const DEFAULT_CONTEXT: FlowServiceContext = { }; // Platform Node Configuration Defaults +// TODO: Rename to indicate it's for the root MeshNodes of new Meshes export const PLATFORM_NODE_DEFAULTS: MeshNodeConfig = { '@context': DEFAULT_CONTEXT, '@type': 'conf:MeshNodeConfig', diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index 39fc7fa..9d92457 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -20,20 +20,11 @@ export type { 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,7 +41,6 @@ export { getServiceConfigPath, loadEnvConfig } from './loaders/env-loader.ts'; // JSON-LD File Loading export { - cloneConfig, configExists, getNodeHierarchy, isConfigInheritanceEnabled, @@ -58,7 +48,7 @@ export { loadServiceConfig, saveMeshNodeConfig, saveServiceConfig, - validateJSONLD, + validateJsonLd, } from './loaders/jsonld-loader.ts'; // Service Configuration Resolution (Cascading Pattern) @@ -79,7 +69,7 @@ export { ServiceConfigAccessor } from './resolution/service-config-accessor.ts'; export { mergeConfigs } from '../utils/merge-configs.ts'; // Import the implementations for the helper functions -import type { ServiceConfig, ServiceOptions } from './types.ts'; +import type { ServiceConfig, ServiceOptions } from './config-types.ts'; import { resolveServiceConfig } from './resolution/service-config-resolver.ts'; import { mergeConfigContext } from './resolution/service-config-utils.ts'; import { validateServiceConfig } from './resolution/service-config-validator.ts'; diff --git a/flow-service/src/config/loaders/env-loader.ts b/flow-service/src/config/loaders/env-loader.ts index e7b4824..7079dbd 100644 --- a/flow-service/src/config/loaders/env-loader.ts +++ b/flow-service/src/config/loaders/env-loader.ts @@ -8,8 +8,8 @@ import type { EnvironmentConfig, ServiceConfigInput, -} from '../types.ts'; -import { ConfigError } from '../types.ts'; +} from '../config-types.ts'; +import { ConfigError } from '../config-types.ts'; import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; /** diff --git a/flow-service/src/config/loaders/jsonld-loader.ts b/flow-service/src/config/loaders/jsonld-loader.ts index 934778f..901d304 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 { MeshNodeConfigInput, ServiceConfigInput } from '../types.ts'; -import { ConfigError } from '../types.ts'; +import type { MeshNodeConfigInput, ServiceConfigInput } from '../config-types.ts'; +import { ConfigError } from '../config-types.ts'; import { handleCaughtError } from '../../utils/logger.ts'; import { getCurrentConfigDistPath } from '../../../../flow-core/src/utils/mesh-path-utils.ts'; -import { dirname, resolve } from '../../../../flow-core/src/deps.ts'; +import { dirname, NodeObject, resolve } from '../../../../flow-core/src/deps.ts'; /** * Loads a service configuration from a JSON-LD file at the specified path. @@ -271,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 index bbaa721..6c1918c 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -1,8 +1,7 @@ import { defaultQuadstoreBundle } from '../../quadstoreDefaultBundle.ts'; -import type { ServiceConfigInput, MeshNodeConfigInput } from '../types.ts'; +import type { ServiceConfigInput, MeshNodeConfigInput } from '../config-types.ts'; import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; import { clearGraph, copyGraph, createNewGraphFromJsonLd } from '../../../../flow-core/src/utils/quadstore-utils.ts'; -import { ServiceConfig } from '../index.ts'; /** * Named graph terms for Quadstore config graphs @@ -20,22 +19,22 @@ export const GRAPH_NAMES = { * Load platform defaults into Quadstore graphs */ export async function loadPlatformDefaults(): Promise { - await createNewGraphFromJsonLd(PLATFORM_SERVICE_DEFAULTS, GRAPH_NAMES.platformServiceDefaults); - await createNewGraphFromJsonLd(PLATFORM_NODE_DEFAULTS, GRAPH_NAMES.platformImplicitMeshNodeConfig); + await createNewGraphFromJsonLd(PLATFORM_SERVICE_DEFAULTS, { graphName: GRAPH_NAMES.platformServiceDefaults }); + await createNewGraphFromJsonLd(PLATFORM_NODE_DEFAULTS, { graphName: GRAPH_NAMES.platformImplicitMeshNodeConfig }); } /** * Load input service config into Quadstore graph */ export async function loadInputServiceConfig(inputConfig: ServiceConfigInput): Promise { - await createNewGraphFromJsonLd(inputConfig, GRAPH_NAMES.inputServiceConfig); + await createNewGraphFromJsonLd(inputConfig, { graphName: GRAPH_NAMES.inputServiceConfig }); } /** * Load input node config overrides into Quadstore graph */ export async function loadInputMeshNodeConfig(inputMeshNodeConfig: MeshNodeConfigInput): Promise { - await createNewGraphFromJsonLd(inputMeshNodeConfig, GRAPH_NAMES.inputMeshNodeConfig); + await createNewGraphFromJsonLd(inputMeshNodeConfig, { graphName: GRAPH_NAMES.inputMeshNodeConfig }); } /** diff --git a/flow-service/src/config/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts index 29df178..f795ab8 100644 --- a/flow-service/src/config/resolution/service-config-accessor.ts +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -1,4 +1,4 @@ -import type { ServiceConfigContext, DelegationChain, AttributedTo } from '../types.ts'; +import type { ServiceConfigContext, DelegationChain, AttributedTo } from '../config-types.ts'; import { getConfigValue } from './service-config-utils.ts'; /** diff --git a/flow-service/src/config/resolution/service-config-loader.ts b/flow-service/src/config/resolution/service-config-loader.ts index d71a0b3..3d60e51 100644 --- a/flow-service/src/config/resolution/service-config-loader.ts +++ b/flow-service/src/config/resolution/service-config-loader.ts @@ -1,4 +1,4 @@ -import type { ServiceConfigInput, ServiceOptions } from '../types.ts'; +import type { ServiceConfigInput, ServiceOptions } from '../config-types.ts'; import { getServiceConfigPath, loadEnvConfig } from '../loaders/env-loader.ts'; import { loadServiceConfig } from '../loaders/jsonld-loader.ts'; import { mergeConfigs } from '../../utils/merge-configs.ts'; diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index 1a186c0..34be4ab 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -1,23 +1,18 @@ import type { - AttributedTo, - DelegationChain, - LogLevel, ServiceConfigContext, ServiceConfigInput, ServiceOptions, -} from '../types.ts'; +} from '../config-types.ts'; import { getServiceConfigPath, loadEnvConfig } from '../loaders/env-loader.ts'; import { loadServiceConfig } from '../loaders/jsonld-loader.ts'; import { getEnvironmentDefaults, - PLATFORM_SERVICE_DEFAULTS, } from '../defaults.ts'; -import { ConfigError } from '../types.ts'; +import { ConfigError } from '../config-types.ts'; import { mergeConfigs } from '../../utils/merge-configs.ts'; import { handleCaughtError } from '../../utils/logger.ts'; import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; import { getConfigValue, mergeConfigContext } from './service-config-utils.ts'; -import { loadPlatformDefaults, loadInputServiceConfig, loadInputMeshNodeConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.ts'; /** * Asynchronously resolves the service configuration by merging CLI options, environment variables, configuration files, and environment-specific defaults in a defined precedence order. diff --git a/flow-service/src/config/resolution/service-config-utils.ts b/flow-service/src/config/resolution/service-config-utils.ts index a6958ab..1631250 100644 --- a/flow-service/src/config/resolution/service-config-utils.ts +++ b/flow-service/src/config/resolution/service-config-utils.ts @@ -1,4 +1,4 @@ -import type { ServiceConfigContext, ServiceConfigInput } from '../types.ts'; +import type { ServiceConfigContext, ServiceConfigInput } from '../config-types.ts'; import { mergeConfigs } from '../../utils/merge-configs.ts'; import { PLATFORM_SERVICE_DEFAULTS } from '../defaults.ts'; diff --git a/flow-service/src/config/resolution/service-config-validator.ts b/flow-service/src/config/resolution/service-config-validator.ts index 2fbe622..733534e 100644 --- a/flow-service/src/config/resolution/service-config-validator.ts +++ b/flow-service/src/config/resolution/service-config-validator.ts @@ -1,5 +1,5 @@ -import type { ServiceConfigContext } from '../types.ts'; -import { ConfigError } from '../types.ts'; +import type { ServiceConfigContext } from '../config-types.ts'; +import { ConfigError } from '../config-types.ts'; import { getConfigValue } from './service-config-utils.ts'; /** diff --git a/flow-service/src/quadstoreDefaultBundle.ts b/flow-service/src/quadstoreDefaultBundle.ts index 67708b8..3b3a342 100644 --- a/flow-service/src/quadstoreDefaultBundle.ts +++ b/flow-service/src/quadstoreDefaultBundle.ts @@ -1,9 +1,10 @@ -import { Quadstore, MemoryLevel, DataFactory } from '../../flow-core/src/deps.ts'; +import { Quadstore, MemoryLevel, DataFactory, Engine } from '../../flow-core/src/deps.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 = { store, df, backend }; +export const defaultQuadstoreBundle = { store, df, backend, engine }; diff --git a/flow-service/tests/integration/logging/handle-caught-error.test.ts b/flow-service/tests/integration/logging/handle-caught-error.test.ts index 16713a2..54fbf80 100644 --- a/flow-service/tests/integration/logging/handle-caught-error.test.ts +++ b/flow-service/tests/integration/logging/handle-caught-error.test.ts @@ -11,7 +11,7 @@ import { FlowServiceError, ValidationError, } from '../../../src/utils/errors.ts'; -import { ConfigValidationError } from '../../../src/config/types.ts'; +import { ConfigValidationError } from '../../../src/config/config-types.ts'; console.log('🧪 Testing handleCaughtError function...\n'); diff --git a/flow-service/tests/integration/quadstore.test.ts b/flow-service/tests/integration/quadstore.test.ts index 40badf9..a5c3cc1 100644 --- a/flow-service/tests/integration/quadstore.test.ts +++ b/flow-service/tests/integration/quadstore.test.ts @@ -1,5 +1,6 @@ import { assertEquals } from '../../../flow-core/src/deps.ts'; -import { MemoryLevel, DataFactory, Quadstore, NamedNode } from '../../../flow-core/src/deps.ts'; +import { MemoryLevel, DataFactory, Quadstore } from '../../../flow-core/src/deps.ts'; +import { RDF } from '../../../flow-core/src/deps.ts'; import { countQuadsInStream, clearGraph, copyGraph } from '../../../flow-core/src/utils/quadstore-utils.ts'; Deno.test('Quadstore clearGraph and copyGraph utilities', async () => { @@ -10,8 +11,8 @@ Deno.test('Quadstore clearGraph and copyGraph utilities', async () => { const testQuadstoreBundle = { store, df, backend }; - const graphA: NamedNode = df.namedNode('urn:graphA'); - const graphB: NamedNode = df.namedNode('urn:graphB'); + 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); @@ -48,7 +49,7 @@ Deno.test('Quadstore clearGraph and copyGraph utilities', async () => { assertEquals(count, 1); }); -function countQuadsInGraph(graph: NamedNode, store: Quadstore): Promise { +function countQuadsInGraph(graph: RDF.NamedNode, store: Quadstore): Promise { const stream = store.match(undefined, undefined, undefined, graph); return countQuadsInStream(stream); } From ac6659bba97910c5f1b2837712919f89f9dd3245 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Tue, 29 Jul 2025 17:53:38 -0700 Subject: [PATCH 09/20] quadstore factory for testing; --- .ai-guidance.md | 4 ++ flow-core/src/utils/quadstore-utils.ts | 11 +++-- .../src/utils/quadstore/quadstore-factory.ts | 17 +++++++ flow-core/tests/unit/quadstore-utils.test.ts | 2 +- flow-service/deno.lock | 10 +++++ .../src/config/loaders/quadstore-loader.ts | 2 +- ...tBundle.ts => quadstore-default-bundle.ts} | 3 +- .../integration/quadstore-accessor.test.ts | 44 +++++++++++++++++++ ...dstore.test.ts => quadstore-utils.test.ts} | 0 9 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 flow-core/src/utils/quadstore/quadstore-factory.ts rename flow-service/src/{quadstoreDefaultBundle.ts => quadstore-default-bundle.ts} (64%) create mode 100644 flow-service/tests/integration/quadstore-accessor.test.ts rename flow-service/tests/integration/{quadstore.test.ts => quadstore-utils.test.ts} (100%) diff --git a/.ai-guidance.md b/.ai-guidance.md index 9f322bf..c7f2d9a 100644 --- a/.ai-guidance.md +++ b/.ai-guidance.md @@ -14,6 +14,10 @@ Refinement of .ai-guidance, general-guidance, and personal guidance should be an ongoing process. Continually check that guidance is up-to-date, and offer suggestions for improvement whenever possible. +## Keep files short and modular + +So important! Too-long files are cause syntax errors from agent edits. + ## Ontologies are key There are several ontologies in the "ontology" folder (which is actually its own diff --git a/flow-core/src/utils/quadstore-utils.ts b/flow-core/src/utils/quadstore-utils.ts index e423a56..6385a2a 100644 --- a/flow-core/src/utils/quadstore-utils.ts +++ b/flow-core/src/utils/quadstore-utils.ts @@ -1,7 +1,7 @@ import { NodeObject, Quadstore, DataFactory } from '../deps.ts'; import { RDF } from '../deps.ts'; import { jsonld } from '../deps.ts'; -import { defaultQuadstoreBundle } from '../../../flow-service/src/quadstoreDefaultBundle.ts'; +import { defaultQuadstoreBundle } from '../../../flow-service/src/quadstore-default-bundle.ts'; import { jsonldToQuads } from './rdfjs-utils.ts' import type { QuadstoreBundle } from '../types.ts'; @@ -60,14 +60,13 @@ export async function copyGraph( }: { store: Quadstore, df: DataFactory } = defaultQuadstoreBundle ): Promise { const stream = store.match(undefined, undefined, undefined, sourceGraph); - // TODO: multiPut - let count = 0; + const quads = []; for await (const q of stream as any) { const newQuad = df.quad(q.subject, q.predicate, q.object, targetGraph); - await store.put(newQuad); - count++; + quads.push(newQuad); } - return count; + await store.multiPut(quads); + return quads.length; } diff --git a/flow-core/src/utils/quadstore/quadstore-factory.ts b/flow-core/src/utils/quadstore/quadstore-factory.ts new file mode 100644 index 0000000..b47d874 --- /dev/null +++ b/flow-core/src/utils/quadstore/quadstore-factory.ts @@ -0,0 +1,17 @@ +// quadstoreFactory.ts +import { Quadstore, MemoryLevel, DataFactory, Engine } from '../../deps.ts'; +import { QuadstoreBundle } from '../../types.ts'; + +/** + * Creates and opens a new in-memory QuadstoreBundle. + */ +export async function createQuadstoreBundle(): Promise { + const backend = new MemoryLevel(); + const df = new DataFactory(); + const store = new Quadstore({ backend, dataFactory: df }); + const engine = new Engine(store); + + await store.open(); + + return { store, df, backend, engine }; +} diff --git a/flow-core/tests/unit/quadstore-utils.test.ts b/flow-core/tests/unit/quadstore-utils.test.ts index 4ba5419..a06f021 100644 --- a/flow-core/tests/unit/quadstore-utils.test.ts +++ b/flow-core/tests/unit/quadstore-utils.test.ts @@ -1,6 +1,6 @@ import { assertEquals } from '../../src/deps.ts'; import { clearGraph, copyGraph, isGraphEmpty, countQuadsInStream, putJsonLdToGraph, createNewGraphFromJsonLd } from '../../src/utils/quadstore-utils.ts'; -import { defaultQuadstoreBundle } from '../../../flow-service/src/quadstoreDefaultBundle.ts'; +import { quadstoreTestBundle } from '../../src/utils/quadstore/quadstore-factory.ts'; Deno.test('countQuadsInStream counts quads correctly', async () => { const { store, df } = defaultQuadstoreBundle; diff --git a/flow-service/deno.lock b/flow-service/deno.lock index cc3a5da..0606cfa 100644 --- a/flow-service/deno.lock +++ b/flow-service/deno.lock @@ -4,6 +4,7 @@ "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", @@ -61,6 +62,9 @@ "@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": [ @@ -2023,6 +2027,7 @@ }, "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": { @@ -2040,6 +2045,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", @@ -2330,6 +2339,7 @@ "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", + "https://deno.land/std@0.224.0/testing/asserts.ts": "d0cdbabadc49cc4247a50732ee0df1403fdcd0f95360294ad448ae8c240f3f5c", "https://deno.land/std@0.224.0/testing/mock.ts": "a963181c2860b6ba3eb60e08b62c164d33cf5da7cd445893499b2efda20074db", "https://deno.land/x/free_port@v1.2.0/mod.ts": "512646732aaea41fbfd1f210f3ae82660f38251777d189d290da331d0235a58e", "https://deno.land/x/hono@v4.2.8/adapter/deno/ssg.ts": "49d73dd0d351fc52a54bfc8094096aff957f5a955a3eb976844ed8d88903bc0f", diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index 6c1918c..3b7380d 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -1,4 +1,4 @@ -import { defaultQuadstoreBundle } from '../../quadstoreDefaultBundle.ts'; +import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; import type { ServiceConfigInput, MeshNodeConfigInput } from '../config-types.ts'; import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; import { clearGraph, copyGraph, createNewGraphFromJsonLd } from '../../../../flow-core/src/utils/quadstore-utils.ts'; diff --git a/flow-service/src/quadstoreDefaultBundle.ts b/flow-service/src/quadstore-default-bundle.ts similarity index 64% rename from flow-service/src/quadstoreDefaultBundle.ts rename to flow-service/src/quadstore-default-bundle.ts index 3b3a342..293fa95 100644 --- a/flow-service/src/quadstoreDefaultBundle.ts +++ b/flow-service/src/quadstore-default-bundle.ts @@ -1,4 +1,5 @@ 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(); @@ -7,4 +8,4 @@ const engine = new Engine(store); await store.open(); -export const defaultQuadstoreBundle = { store, df, backend, engine }; +export const defaultQuadstoreBundle: QuadstoreBundle = { store, df, backend, engine }; diff --git a/flow-service/tests/integration/quadstore-accessor.test.ts b/flow-service/tests/integration/quadstore-accessor.test.ts new file mode 100644 index 0000000..6b89123 --- /dev/null +++ b/flow-service/tests/integration/quadstore-accessor.test.ts @@ -0,0 +1,44 @@ +import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts"; +import { createQuadstoreBundle } from "../../../flow-core/src/utils/quadstore/quadstore-factory.ts"; +import { copyGraph, clearGraph, putJsonLdToGraph } from "../../../flow-core/src/utils/quadstore-utils.ts"; + +Deno.test("Quadstore SPARQL query integration test", async () => { + const testQuadstoreBundle = await createQuadstoreBundle(); + const df = testQuadstoreBundle.df; + const store = testQuadstoreBundle.store; + const engine = testQuadstoreBundle.engine; + + const testGraph = df.namedNode("http://example.org/testGraph"); + + // Clear graph if exists + await clearGraph(testGraph, { store }); + + // Insert test data + const testData = { + "@context": { + ex: "http://example.org/", + }, + "@id": "ex:subject1", + "ex:predicate1": "object1", + }; + await putJsonLdToGraph(testData, testGraph.value, testQuadstoreBundle); + + // Query the graph using SPARQL via Comunica engine + const query = ` + PREFIX ex: + SELECT ?o WHERE { + GRAPH <${testGraph.value}> { + ex:subject1 ex:predicate1 ?o . + } + } + `; + + const result = []; + const bindingsStream = await engine!.queryBindings(query, { sources: [store] }); + for await (const binding of bindingsStream as any) { + result.push(binding.get("o").value); + } + + assertEquals(result.length, 1); + assertEquals(result[0], "object1"); +}); diff --git a/flow-service/tests/integration/quadstore.test.ts b/flow-service/tests/integration/quadstore-utils.test.ts similarity index 100% rename from flow-service/tests/integration/quadstore.test.ts rename to flow-service/tests/integration/quadstore-utils.test.ts From 1ffab1ccceeb42089ab6bc7a2894b690a8142a8d Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Fri, 1 Aug 2025 11:32:12 -0700 Subject: [PATCH 10/20] every day a struggle; mid-aire conversion to double-quotes coming up; --- deno.json | 5 +- deno.lock | 82 +- flow-core/src/deps.ts | 2 + flow-core/src/utils/rdfjs-utils.ts | 40 +- flow-core/tests/unit/quadstore-utils.test.ts | 39 +- flow-service/deno.json | 2 +- flow-service/deno.lock | 24 + flow-service/flow-service-config.jsonld | 6 +- flow-service/logs/test-pretty-direct.log | 8 +- flow-service/src/config/config-types.ts | 2 +- flow-service/src/config/defaults.ts | 8 +- flow-service/src/config/index.ts | 28 +- .../resolution/service-config-accessor.ts | 149 +-- .../resolution/service-config-loader.ts | 3 + .../resolution/service-config-resolver.ts | 41 +- .../resolution/service-config-validator.ts | 55 +- flow-service/src/utils/logger.ts | 2 +- .../logging/formatting-direct.test.ts | 27 +- package-lock.json | 1083 +++++++++++++++++ package.json | 5 + 20 files changed, 1376 insertions(+), 235 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json 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 d98d005..4142d05 100644 --- a/deno.lock +++ b/deno.lock @@ -13,8 +13,10 @@ "npm:@sentry/deno@*": "9.40.0", "npm:@types/jsonld@*": "1.5.15", "npm:@types/node@*": "22.15.15", + "npm:abstract-level@*": "3.1.0", "npm:jsonld@*": "8.3.3", "npm:memory-level@*": "3.1.0", + "npm:quadstore-comunica@*": "6.2.0_asynciterator@3.9.0_quadstore@15.3.0", "npm:quadstore@*": "15.3.0", "npm:rdf-data-factory@*": "2.0.2" }, @@ -214,6 +216,12 @@ "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": [ @@ -480,6 +488,12 @@ "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==" }, @@ -1014,6 +1028,9 @@ "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==" }, @@ -1493,6 +1510,9 @@ "micromark-util-types" ] }, + "minimist@1.2.8": { + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, "module-error@1.0.2": { "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==" }, @@ -1580,10 +1600,20 @@ "property-information@7.1.0": { "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" }, + "quadstore-comunica@6.2.0_asynciterator@3.9.0_quadstore@15.3.0": { + "integrity": "sha512-diibvQsX9i8VHHX47a0+3W/P0oXm9LYC0fQgZ+hsEJ3wvBXrWs4AFZlSy7A+0ze1FSnHnvRtRu5/FohdtlkyAg==", + "dependencies": [ + "@rdfjs/types@2.0.1", + "asynciterator", + "quadstore", + "sparqlalgebrajs", + "sparqljs" + ] + }, "quadstore@15.3.0": { "integrity": "sha512-KeFNurvPg61nO/WgdFYxbO/Sj80ne1gmzMGnWI0ukIXabdLggZ7i9TERRzPoyLHiUJP+Kkc1J2lZ+wl7n8hNBA==", "dependencies": [ - "@rdfjs/types", + "@rdfjs/types@2.0.1", "abstract-level", "asynciterator", "js-sorted-set" @@ -1612,10 +1642,37 @@ "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" + "@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": { @@ -1750,6 +1807,27 @@ "space-separated-tokens@2.0.2": { "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" }, + "sparqlalgebrajs@5.0.1": { + "integrity": "sha512-eb6Eiq5d/UUPatyTrtIZnT1c3aKfKJpp027cv7w7fxuzCHHYqhdNgOgLa9QbSGXl4X+uxTsTwZZhAPzw5X1odQ==", + "dependencies": [ + "@types/sparqljs", + "fast-deep-equal", + "minimist", + "rdf-data-factory@2.0.2", + "rdf-isomorphic", + "rdf-string", + "rdf-terms", + "sparqljs" + ], + "bin": true + }, + "sparqljs@3.7.3": { + "integrity": "sha512-FQfHUhfwn5PD9WH6xPU7DhFfXMgqK/XoDrYDVxz/grhw66Il0OjRg3JBgwuEvwHnQt7oSTiKWEiCZCPNaUbqgg==", + "dependencies": [ + "rdf-data-factory@1.1.3" + ], + "bin": true + }, "stringify-entities@4.0.4": { "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "dependencies": [ diff --git a/flow-core/src/deps.ts b/flow-core/src/deps.ts index e7712ce..195356d 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -22,6 +22,8 @@ export const jsonld = jsonldDefault; export { Engine } from 'npm:quadstore-comunica'; + + // exported types export type { diff --git a/flow-core/src/utils/rdfjs-utils.ts b/flow-core/src/utils/rdfjs-utils.ts index 03e5759..ef8bd21 100644 --- a/flow-core/src/utils/rdfjs-utils.ts +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -10,38 +10,16 @@ export async function jsonldToQuads( inputJsonLd: NodeObject, graph: RDF.NamedNode | RDF.DefaultGraph = df.defaultGraph(), ): Promise { - // Expand the JSON-LD object to full form - const expanded = await jsonld.expand(inputJsonLd); + // Use jsonld.toRDF() to get quads directly - much simpler! + const quads = await jsonld.toRDF(inputJsonLd) as RDF.Quad[]; - // Convert expanded JSON-LD to RDFJS quads - const quads: RDF.Quad[] = []; - - for (const node of expanded) { - const subject = df.namedNode(node['@id'] as string); - - for (const [key, values] of Object.entries(node)) { - if (key === '@id' || key === '@type' || key === '@context') continue; - - for (const value of values as any[]) { - let object; - - if (value['@id']) { - object = df.namedNode(value['@id']); - } else if (value['@value'] !== undefined) { - const datatype = value['@type'] ? df.namedNode(value['@type']) : undefined; - const language = value['@language']; - object = df.literal(value['@value'], language || datatype); - } else { - // For complex nested objects, serialize as JSON string literal - object = df.literal(JSON.stringify(value)); - } - - const predicate = df.namedNode(key); - const quadObj = df.quad(subject, predicate, object, graph); - quads.push(quadObj); - } - } + // If the desired graph is the default graph, return as-is + if (graph.termType === 'DefaultGraph') { + return quads; } - return quads; + // If a specific named graph is requested, update all quads to use that graph + return quads.map(quad => + df.quad(quad.subject, quad.predicate, quad.object, graph) + ); } diff --git a/flow-core/tests/unit/quadstore-utils.test.ts b/flow-core/tests/unit/quadstore-utils.test.ts index a06f021..3b8292f 100644 --- a/flow-core/tests/unit/quadstore-utils.test.ts +++ b/flow-core/tests/unit/quadstore-utils.test.ts @@ -1,9 +1,12 @@ import { assertEquals } from '../../src/deps.ts'; import { clearGraph, copyGraph, isGraphEmpty, countQuadsInStream, putJsonLdToGraph, createNewGraphFromJsonLd } from '../../src/utils/quadstore-utils.ts'; -import { quadstoreTestBundle } from '../../src/utils/quadstore/quadstore-factory.ts'; +import { createQuadstoreBundle } from "../../../flow-core/src/utils/quadstore/quadstore-factory.ts"; Deno.test('countQuadsInStream counts quads correctly', async () => { - const { store, df } = defaultQuadstoreBundle; + const testQuadstoreBundle = await createQuadstoreBundle(); + const df = testQuadstoreBundle.df; + const store = testQuadstoreBundle.store; + const graph = df.namedNode('urn:testGraph1'); await clearGraph(graph); @@ -21,56 +24,62 @@ Deno.test('countQuadsInStream counts quads correctly', async () => { }); Deno.test('isGraphEmpty returns true for empty graph and false otherwise', async () => { - const { store, df } = defaultQuadstoreBundle; + 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); + 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); + empty = await isGraphEmpty(graph, testQuadstoreBundle); assertEquals(empty, false); await clearGraph(graph); }); Deno.test('clearGraph deletes all quads and returns count', async () => { - const { store, df } = defaultQuadstoreBundle; + const testQuadstoreBundle = await createQuadstoreBundle(); + const df = testQuadstoreBundle.df; + const store = testQuadstoreBundle.store; + const graph = df.namedNode('urn:testGraph3'); await clearGraph(graph); const quad = df.quad(df.namedNode('s'), df.namedNode('p'), df.literal('o'), graph); await store.put(quad); - const count = await clearGraph(graph); + const count = await clearGraph(graph, testQuadstoreBundle); assertEquals(count, 1); - const empty = await isGraphEmpty(graph); + const empty = await isGraphEmpty(graph, testQuadstoreBundle); assertEquals(empty, true); }); Deno.test('copyGraph copies quads from source to target graph', async () => { - const { store, df } = defaultQuadstoreBundle; + const testQuadstoreBundle = await createQuadstoreBundle(); + const df = testQuadstoreBundle.df; + const store = testQuadstoreBundle.store; + const sourceGraph = df.namedNode('urn:sourceGraph'); const targetGraph = df.namedNode('urn:targetGraph'); - await clearGraph(sourceGraph); - await clearGraph(targetGraph); + await clearGraph(sourceGraph, testQuadstoreBundle); + await clearGraph(targetGraph, testQuadstoreBundle); const quad = df.quad(df.namedNode('s'), df.namedNode('p'), df.literal('o'), sourceGraph); await store.put(quad); - await copyGraph(sourceGraph, targetGraph); + 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); - - await clearGraph(sourceGraph); - await clearGraph(targetGraph); }); diff --git a/flow-service/deno.json b/flow-service/deno.json index 661d49d..7103d2d 100644 --- a/flow-service/deno.json +++ b/flow-service/deno.json @@ -18,6 +18,6 @@ "jsxImportSource": "hono/jsx" }, "fmt": { - "singleQuote": true + "singleQuote": false } } diff --git a/flow-service/deno.lock b/flow-service/deno.lock index 0606cfa..176ab73 100644 --- a/flow-service/deno.lock +++ b/flow-service/deno.lock @@ -2031,6 +2031,30 @@ "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", diff --git a/flow-service/flow-service-config.jsonld b/flow-service/flow-service-config.jsonld index 3b60019..1f996c3 100644 --- a/flow-service/flow-service-config.jsonld +++ b/flow-service/flow-service-config.jsonld @@ -7,10 +7,12 @@ "conf": "https://semantic-flow.github.io/ontology/config-flow/", "meta": "https://semantic-flow.github.io/ontology/meta-flow/", "prov": "http://www.w3.org/ns/prov#", - "dcterms": "http://purl.org/dc/terms/" + "dcterms": "http://purl.org/dc/terms/", + "local": "http://localhost/flow-service/" }, "@type": "fsvc:ServiceConfig", - "@id": "fsvc:defaultConfig", + "@id": "local:serviceConfig", + "fsvc:host": "localhost", "fsvc:port": 8080, "fsvc:meshPaths": [ "../meshes/test-ns/" diff --git a/flow-service/logs/test-pretty-direct.log b/flow-service/logs/test-pretty-direct.log index 18445ef..f17b6a9 100644 --- a/flow-service/logs/test-pretty-direct.log +++ b/flow-service/logs/test-pretty-direct.log @@ -1,4 +1,4 @@ -[2025-07-29T07:31:15.898Z] INFO File logging test started (op=startup, [config-resolver]) -[2025-07-29T07:31:15.899Z] WARN Configuration override detected (op=config-resolve, source=environment) -[2025-07-29T07:31:15.900Z] ERROR Database connection failed (op=startup, 5000ms) -[2025-07-29T07:31:15.900Z] DEBUG Processing mesh nodes (op=scan, mesh=/data/mesh, 1250ms, 42 nodes) +[2025-07-30T01:03:13.334Z] INFO File logging test started (op=startup, [config-resolver]) +[2025-07-30T01:03:13.339Z] WARN Configuration override detected (op=config-resolve) +[2025-07-30T01:03:13.339Z] ERROR Database connection failed (op=startup, 5000ms) +[2025-07-30T01:03:13.340Z] DEBUG Processing mesh nodes (op=scan, mesh=/data/mesh, 1250ms, 42 nodes) diff --git a/flow-service/src/config/config-types.ts b/flow-service/src/config/config-types.ts index 87bc1d1..07bfe51 100644 --- a/flow-service/src/config/config-types.ts +++ b/flow-service/src/config/config-types.ts @@ -57,7 +57,7 @@ export interface TemplateMapping extends NodeObject { readonly '@type': 'conf:TemplateMapping'; readonly 'conf:hasResourcePageTemplate': string; } - +// TODO rename to export interface MeshNodeConfig extends NodeObject { readonly '@type': 'conf:MeshNodeConfig'; readonly 'conf:versioningEnabled': boolean; diff --git a/flow-service/src/config/defaults.ts b/flow-service/src/config/defaults.ts index e174610..089eb9a 100644 --- a/flow-service/src/config/defaults.ts +++ b/flow-service/src/config/defaults.ts @@ -15,6 +15,7 @@ export const DEFAULT_CONTEXT: FlowServiceContext = { node: 'https://semantic-flow.github.io/ontology/node/', flow: 'https://semantic-flow.github.io/ontology/flow/', conf: 'https://semantic-flow.github.io/ontology/config-flow/', + local: 'http://localhost/flow-service/' }; // Platform Node Configuration Defaults @@ -72,17 +73,20 @@ export const SERVICE_NODE_DEFAULTS: Omit< export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { '@context': DEFAULT_CONTEXT, '@type': 'fsvc:ServiceConfig', - '@id': 'fsvc:defaultConfig', + '@id': 'local:serviceConfig', 'fsvc:port': 3000, 'fsvc:host': 'localhost', 'fsvc:hasLoggingConfig': { + '@id': 'local:ServiceConfig#loggingConfig', '@type': 'fsvc:LoggingConfig', 'fsvc:hasConsoleChannel': { + '@id': 'local:ServiceConfig#consoleChannel', '@type': 'fsvc:LogChannelConfig', 'fsvc:logChannelEnabled': true, 'fsvc:logLevel': 'info', }, 'fsvc:hasFileChannel': { + '@id': 'local:ServiceConfig#fileChannel', '@type': 'fsvc:LogChannelConfig', 'fsvc:logChannelEnabled': false, 'fsvc:logLevel': 'warn', @@ -94,6 +98,7 @@ export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { 'fsvc:logRotationInterval': 'daily', }, 'fsvc:hasSentryChannel': { + '@id': 'local:ServiceConfig#sentryChannel', '@type': 'fsvc:LogChannelConfig', 'fsvc:logChannelEnabled': false, 'fsvc:logLevel': 'error', @@ -101,6 +106,7 @@ export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { }, }, 'fsvc:hasContainedServices': { + '@id': 'local:ServiceConfig#containedServices', '@type': 'fsvc:ContainedServicesConfig', 'fsvc:apiEnabled': true, 'fsvc:sparqlEnabled': true, diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index 9d92457..47824ba 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -75,20 +75,19 @@ import { mergeConfigContext } from './resolution/service-config-utils.ts'; import { validateServiceConfig } from './resolution/service-config-validator.ts'; import { ServiceConfigAccessor } from './resolution/service-config-accessor.ts'; import { handleCaughtError } from '../utils/logger.ts'; +import { defaultQuadstoreBundle } from '../quadstore-default-bundle.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; @@ -96,23 +95,10 @@ export async function createServiceConfig( } /** - * 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 + * @deprecated This function is no longer needed as the merged config is accessed via Quadstore. */ export async function getCompleteServiceConfig( cliOptions?: ServiceOptions, ): Promise { - try { - const context = await resolveServiceConfig(cliOptions); - validateServiceConfig(context); - return mergeConfigContext(context); - } catch (error) { - await handleCaughtError( - error, - `Failed to get complete service configuration`, - ); - throw error; - } + throw new Error('getCompleteServiceConfig is deprecated. Use ServiceConfigAccessor to query config.'); } diff --git a/flow-service/src/config/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts index f795ab8..94edb1b 100644 --- a/flow-service/src/config/resolution/service-config-accessor.ts +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -1,100 +1,79 @@ + import type { ServiceConfigContext, DelegationChain, AttributedTo } from '../config-types.ts'; -import { getConfigValue } from './service-config-utils.ts'; +import type { QuadstoreBundle } from '../../../../flow-core/src/types.ts'; +import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; +import { DataFactory } from '../../../../flow-core/src/deps.ts'; -/** - * 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'; - } + private bundle: QuadstoreBundle; + private df: DataFactory; - 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; + constructor(bundle: QuadstoreBundle = defaultQuadstoreBundle) { + this.bundle = bundle; + this.df = bundle.df; } - 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'; + private async querySingleValue(sparql: string): Promise { + if (!this.bundle.engine) { + throw new Error('SPARQL engine not initialized in Quadstore bundle'); + } + 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; + } + } + return undefined; } - 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; + async getPort(): Promise { + const sparql = ` + PREFIX fsvc: + SELECT ?value WHERE { + GRAPH { + ?s fsvc:port ?value . + } + } LIMIT 1 + `; + const result = await this.querySingleValue(sparql); + return result ? Number(result) : undefined; } - 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'; + async getHost(): Promise { + const sparql = ` + PREFIX fsvc: + SELECT ?value WHERE { + GRAPH { + ?s fsvc:host ?value . + } + } LIMIT 1 + `; + return this.querySingleValue(sparql); } - 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']; + async getMeshPaths(): Promise { + const sparql = ` + PREFIX fsvc: + SELECT ?value WHERE { + GRAPH { + ?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; } - 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', - ); - } + // Additional getters for other config values can be implemented similarly } diff --git a/flow-service/src/config/resolution/service-config-loader.ts b/flow-service/src/config/resolution/service-config-loader.ts index 3d60e51..aaf17d8 100644 --- a/flow-service/src/config/resolution/service-config-loader.ts +++ b/flow-service/src/config/resolution/service-config-loader.ts @@ -10,6 +10,9 @@ import { validateLogLevel } from '../../../../flow-core/src/platform-constants.t * @param cliOptions - Optional CLI-provided service options * @returns Merged service configuration input options */ + +//TODO: dct:conformsTo + export async function loadServiceConfigInput( cliOptions?: ServiceOptions, ): Promise { diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index 34be4ab..e68f9ad 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -23,35 +23,42 @@ import { getConfigValue, mergeConfigContext } from './service-config-utils.ts'; * @returns A ServiceConfigContext object with both input and default configuration options * @throws ConfigError if configuration resolution fails or an unexpected error occurs */ + +import { loadPlatformDefaults, loadInputServiceConfig, loadInputMeshNodeConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.ts'; + export async function resolveServiceConfig( cliOptions?: ServiceOptions, -): Promise { +): Promise { try { - let inputOptions: ServiceConfigInput = {}; + // Load platform defaults into Quadstore graphs + await loadPlatformDefaults(); + // Load environment config const envConfig = loadEnvConfig(); - inputOptions = mergeConfigs(inputOptions, envConfig); + // Load file config if specified const serviceConfigPath = getServiceConfigPath(cliOptions?.configPath); + let fileConfig: ServiceConfigInput | undefined; if (serviceConfigPath) { - const fileConfig = await loadServiceConfig(serviceConfigPath); - if (fileConfig) { - inputOptions = mergeConfigs(inputOptions, fileConfig); - } + const loadedConfig = await loadServiceConfig(serviceConfigPath); + fileConfig = loadedConfig === null ? undefined : loadedConfig; } - if (cliOptions) { - const cliConfig = convertCliOptionsToConfig(cliOptions); - inputOptions = mergeConfigs(inputOptions, cliConfig); - } + // Convert CLI options to config + const cliConfig = cliOptions ? convertCliOptionsToConfig(cliOptions) : {}; - const environment = Deno.env.get('FLOW_ENV') || 'development'; - const defaultOptions = getEnvironmentDefaults(environment); + // Merge input configs: env, file, CLI + const mergedInputConfig = mergeConfigs(mergeConfigs(envConfig, fileConfig ?? {}), cliConfig); + + // Load merged input config into Quadstore graph + await loadInputServiceConfig(mergedInputConfig); + + // TODO: Load input mesh node config if applicable + // await loadInputMeshNodeConfig(...); + + // Merge all graphs into mergedServiceConfig graph + await mergeServiceConfigGraphs(); - return { - inputOptions, - defaultOptions, - }; } catch (error) { if (error instanceof ConfigError) { await handleCaughtError(error, `Service configuration resolution failed`); diff --git a/flow-service/src/config/resolution/service-config-validator.ts b/flow-service/src/config/resolution/service-config-validator.ts index 733534e..99170d6 100644 --- a/flow-service/src/config/resolution/service-config-validator.ts +++ b/flow-service/src/config/resolution/service-config-validator.ts @@ -1,39 +1,36 @@ -import type { ServiceConfigContext } from '../config-types.ts'; + +import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; +import { DataFactory } from '../../../../flow-core/src/deps.ts'; import { ConfigError } from '../config-types.ts'; -import { getConfigValue } from './service-config-utils.ts'; +import { GRAPH_NAMES } from '../loaders/quadstore-loader.ts'; /** - * 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. + * Validates essential service configuration values in the merged config graph. + * Throws ConfigError if validation fails. */ -export function validateServiceConfig(context: ServiceConfigContext): void { - const port = getConfigValue(context, 'fsvc:port', 'fsvc:port'); - const host = getConfigValue(context, 'fsvc:host', 'fsvc:host'); +export async function validateServiceConfig(): Promise { + const { store, df } = defaultQuadstoreBundle; + const graph = df.namedNode(GRAPH_NAMES.mergedServiceConfig); - if (!port || port <= 0 || port > 65535) { - throw new ConfigError( - `Invalid port number: ${port}. Must be between 1 and 65535.`, - ); + // Validate port + const portQuads = store.match(undefined, df.namedNode('https://semantic-flow.github.io/ontology/flow-service/port'), undefined, graph); + for await (const quad of portQuads as any) { + const port = Number(quad.object.value); + if (isNaN(port) || port < 1 || port > 65535) { + throw new ConfigError(`Invalid port number: ${quad.object.value}. Must be between 1 and 65535.`); + } } - if (!host || host.trim().length === 0) { - throw new ConfigError(`Invalid host: ${host}. Host cannot be empty.`); + // Validate host presence + const hostQuads = store.match(undefined, df.namedNode('https://semantic-flow.github.io/ontology/flow-service/host'), undefined, graph); + let hostFound = false; + for await (const _ of hostQuads as any) { + hostFound = true; + break; } - - // 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.', - ); - } + 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/utils/logger.ts b/flow-service/src/utils/logger.ts index 1cd62a5..0b40741 100644 --- a/flow-service/src/utils/logger.ts +++ b/flow-service/src/utils/logger.ts @@ -218,7 +218,7 @@ function formatStructuredMessage( return JSON.stringify(baseEntry); } -function formatConsoleMessage( +export function formatConsoleMessage( level: LogLevel, message: string, context?: LogContext, diff --git a/flow-service/tests/integration/logging/formatting-direct.test.ts b/flow-service/tests/integration/logging/formatting-direct.test.ts index a7f4b08..e3e956c 100644 --- a/flow-service/tests/integration/logging/formatting-direct.test.ts +++ b/flow-service/tests/integration/logging/formatting-direct.test.ts @@ -44,30 +44,9 @@ interface LogContext { [key: string]: unknown; } -// Copy the pretty formatting function -function formatPrettyMessage( - level: LogLevel, - message: string, - context?: LogContext, -): string { - const timestamp = new Date().toISOString(); - const levelPadded = level.padEnd(5); - - let contextStr = ''; - if (context && Object.keys(context).length > 0) { - const parts = []; - if (context.operation) parts.push(`op=${context.operation}`); - if (context.meshPath) parts.push(`mesh=${context.meshPath}`); - if (context.duration) parts.push(`${context.duration}ms`); - if (context.nodeCount) parts.push(`${context.nodeCount} nodes`); - if (context.configSource) parts.push(`source=${context.configSource}`); - if (context.component) parts.push(`[${context.component}]`); - - contextStr = parts.length > 0 ? ` (${parts.join(', ')})` : ''; - } +// Import the pretty formatting function from the logger module +import { formatConsoleMessage } from '../../../src/utils/logger.ts'; - return `[${timestamp}] ${levelPadded} ${message}${contextStr}`; -} // Simple file logger class SimpleFileLogger { @@ -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/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0b61cb6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1083 @@ +{ + "name": "sflow-platform", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "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/@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/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/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-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/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/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/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/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/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/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/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..5e510b2 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "eslint": "^9.32.0" + } +} From f6c470d5fbcce5481052fc7b305f337b357e7d17 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Fri, 1 Aug 2025 17:18:01 -0700 Subject: [PATCH 11/20] i cannot believe we got back to a running service --- .gitignore | 1 + eslint.config.mjs | 19 + flow-service/flow-service-config.jsonld | 4 +- flow-service/main.ts | 19 +- flow-service/src/config/config-types.ts | 18 +- flow-service/src/config/defaults.ts | 248 +++++----- flow-service/src/config/index.ts | 13 +- flow-service/src/config/loaders/env-loader.ts | 2 +- .../src/config/loaders/jsonld-loader.ts | 14 +- .../src/config/loaders/quadstore-loader.ts | 18 +- .../src/config/node-config.default.jsonld | 2 +- .../resolution/service-config-accessor.ts | 13 +- .../resolution/service-config-resolver.ts | 4 +- flow-service/src/utils/startup-logger.ts | 45 +- package-lock.json | 431 ++++++++++++++++++ package.json | 1 + 16 files changed, 650 insertions(+), 202 deletions(-) create mode 100644 eslint.config.mjs 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/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-service/flow-service-config.jsonld b/flow-service/flow-service-config.jsonld index 1f996c3..ffe2d9a 100644 --- a/flow-service/flow-service-config.jsonld +++ b/flow-service/flow-service-config.jsonld @@ -8,10 +8,10 @@ "meta": "https://semantic-flow.github.io/ontology/meta-flow/", "prov": "http://www.w3.org/ns/prov#", "dcterms": "http://purl.org/dc/terms/", - "local": "http://localhost/flow-service/" + "mergedServiceConfig": "http://localhost/graph/mergedServiceConfig/" }, "@type": "fsvc:ServiceConfig", - "@id": "local:serviceConfig", + "@id": "mergedServiceConfig:serviceConfig", "fsvc:host": "localhost", "fsvc:port": 8080, "fsvc:meshPaths": [ diff --git a/flow-service/main.ts b/flow-service/main.ts index 6f9c89a..0a92242 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -2,7 +2,7 @@ import { OpenAPIHono } from '@hono/zod-openapi'; import { apiReference } from 'npm:@scalar/hono-api-reference'; import { health } from './src/routes/health.ts'; import { createMarkdownFromOpenApi } from 'npm:@scalar/openapi-to-markdown'; -import { createServiceConfig } from './src/config/index.ts'; +import { createServiceConfig, singletonServiceConfigAccessor } from './src/config/index.ts'; import { logStartupConfiguration, logStartupUrls, @@ -11,9 +11,8 @@ import { handleCaughtError } from './src/utils/logger.ts'; import { MESH } from '../flow-core/src/mesh-constants.ts'; // Initialize configuration system -let config; try { - config = await createServiceConfig(); + await createServiceConfig(); } catch (error) { await handleCaughtError(error, 'Failed to initialize service configuration'); console.error( @@ -24,7 +23,7 @@ try { // Log service startup with configuration info try { - logStartupConfiguration(config); + logStartupConfiguration(); } catch (error) { await handleCaughtError(error, 'Failed to log startup configuration'); console.error('⚠️ Configuration logging failed, but continuing startup...'); @@ -46,7 +45,7 @@ const content = { }, servers: [ { - url: `http://${config.host}:${config.port}`, + url: `http://${await singletonServiceConfigAccessor.getHost()}:${await singletonServiceConfigAccessor.getPort()}`, description: 'Configured server', }, ], @@ -83,15 +82,15 @@ app.route('/api', health); import { createMeshesRoutes } from './src/routes/meshes.ts'; import { createWeaveRoutes } from './src/routes/weave.ts'; -const meshes = createMeshesRoutes(config); -const weave = createWeaveRoutes(config); +const meshes = createMeshesRoutes(singletonServiceConfigAccessor); +const weave = createWeaveRoutes(singletonServiceConfigAccessor); app.route('/api', meshes); app.route('/api', weave); // Startup logging try { - logStartupUrls(config); + logStartupUrls(singletonServiceConfigAccessor); } catch (error) { await handleCaughtError(error, 'Failed to log startup URLs'); console.error('⚠️ URL logging failed, but continuing startup...'); @@ -99,8 +98,8 @@ try { try { Deno.serve({ - port: config.port, - hostname: config.host, + port: await singletonServiceConfigAccessor.getPort(), + hostname: await singletonServiceConfigAccessor.getHost(), }, app.fetch); } catch (error) { await handleCaughtError(error, 'Failed to start HTTP server'); diff --git a/flow-service/src/config/config-types.ts b/flow-service/src/config/config-types.ts index 07bfe51..4781882 100644 --- a/flow-service/src/config/config-types.ts +++ b/flow-service/src/config/config-types.ts @@ -58,8 +58,8 @@ export interface TemplateMapping extends NodeObject { readonly 'conf:hasResourcePageTemplate': string; } // TODO rename to -export interface MeshNodeConfig extends NodeObject { - readonly '@type': 'conf:MeshNodeConfig'; +export interface MeshRootNodeConfig extends NodeObject { + readonly '@type': 'conf:MeshRootNodeConfig'; readonly 'conf:versioningEnabled': boolean; readonly 'conf:distributionFormats': string[]; readonly 'conf:templateMappings'?: TemplateMapping; @@ -79,7 +79,7 @@ export interface ServiceConfig extends NodeObject { readonly 'fsvc:meshPaths'?: string[]; readonly 'fsvc:hasLoggingConfig': LoggingConfig; readonly 'fsvc:hasContainedServices': ContainedServicesConfig; - readonly 'fsvc:rootMeshNodeConfigTemplate'?: MeshNodeConfig; + readonly 'fsvc:rootMeshRootNodeConfigTemplate'?: MeshRootNodeConfig; readonly 'fsvc:defaultDelegationChain'?: DelegationChain; readonly 'fsvc:defaultAttributedTo'?: AttributedTo; } @@ -112,13 +112,13 @@ export interface ServiceConfigInput extends Partial { 'fsvc:meshPaths'?: string[]; 'fsvc:hasLoggingConfig'?: Partial; 'fsvc:hasContainedServices'?: Partial; - 'fsvc:nodeDefaults'?: Partial; + 'fsvc:nodeDefaults'?: Partial; 'fsvc:defaultDelegationChain'?: Partial; 'fsvc:defaultAttributedTo'?: Partial; } -export interface MeshNodeConfigInput extends Partial { - '@type'?: 'conf:MeshNodeConfig'; +export interface MeshRootNodeConfigInput extends Partial { + '@type'?: 'conf:MeshRootNodeConfig'; 'conf:versioningEnabled'?: boolean; 'conf:distributionFormats'?: string[]; 'conf:templateMappings'?: Partial; @@ -136,9 +136,9 @@ export interface ServiceConfigContext { readonly defaultOptions: ServiceConfig; } -export interface MeshNodeConfigContext { - readonly inputOptions: MeshNodeConfigInput; - readonly defaultOptions: MeshNodeConfig; +export interface MeshRootNodeConfigContext { + readonly inputOptions: MeshRootNodeConfigInput; + readonly defaultOptions: MeshRootNodeConfig; } // CLI Options Type diff --git a/flow-service/src/config/defaults.ts b/flow-service/src/config/defaults.ts index 089eb9a..3a3f140 100644 --- a/flow-service/src/config/defaults.ts +++ b/flow-service/src/config/defaults.ts @@ -5,40 +5,41 @@ * Based on the existing JSON-LD configuration files. */ -import type { FlowServiceContext, MeshNodeConfig, ServiceConfig } from './config-types.ts'; -import { mergeConfigs } from '../utils/merge-configs.ts'; +import type { FlowServiceContext, MeshRootNodeConfig, ServiceConfig } from "./config-types.ts"; +import { mergeConfigs } from "../utils/merge-configs.ts"; // Standard JSON-LD Context +// TODO: make local context use the config host and port export const DEFAULT_CONTEXT: FlowServiceContext = { - fsvc: 'https://semantic-flow.github.io/ontology/flow-service/', - mesh: 'https://semantic-flow.github.io/ontology/mesh/', - node: 'https://semantic-flow.github.io/ontology/node/', - flow: 'https://semantic-flow.github.io/ontology/flow/', - conf: 'https://semantic-flow.github.io/ontology/config-flow/', - local: 'http://localhost/flow-service/' + "fsvc": "https://semantic-flow.github.io/ontology/flow-service/", + "mesh": "https://semantic-flow.github.io/ontology/mesh/", + "node": "https://semantic-flow.github.io/ontology/node/", + "flow": "https://semantic-flow.github.io/ontology/flow/", + "conf": "https://semantic-flow.github.io/ontology/config-flow/", + "local": "http://localhost/graph/" }; // Platform Node Configuration Defaults // TODO: Rename to indicate it's for the root MeshNodes of new Meshes -export const PLATFORM_NODE_DEFAULTS: MeshNodeConfig = { - '@context': DEFAULT_CONTEXT, - '@type': 'conf:MeshNodeConfig', - '@id': 'conf:defaultConfig', - 'conf:versioningEnabled': true, - 'conf:configInheritanceEnabled': true, - 'conf:distributionFormats': [ - 'application/trig', - 'application/ld+json', +export const PLATFORM_NODE_DEFAULTS: MeshRootNodeConfig = { + "@id": "local:meshRootNodeConfig", + "@context": DEFAULT_CONTEXT, + "@type": "conf:MeshRootNodeConfig", + "conf:versioningEnabled": true, + "conf:configInheritanceEnabled": true, + "conf:distributionFormats": [ + "application/trig", + "application/ld+json", ], - 'conf:templateMappings': { - '@type': 'conf:TemplateMapping', - 'conf:hasResourcePageTemplate': - '/_assets/_templates/default-resource-page.html', + "conf:templateMappings": { + "@type": "conf:TemplateMapping", + "conf:hasResourcePageTemplate": + "/_assets/_templates/default-resource-page.html", }, - 'conf:generateUnifiedDataset': false, - 'conf:generateAggregatedDataset': false, - 'conf:generateResourcePages': true, - 'conf:stylesheetPath': '/_assets/css/default-resource-page.css', + "conf:generateUnifiedDataset": false, + "conf:generateAggregatedDataset": false, + "conf:generateResourcePages": true, + "conf:stylesheetPath": "/_assets/css/default-resource-page.css", }; /** @@ -50,126 +51,129 @@ export const PLATFORM_NODE_DEFAULTS: MeshNodeConfig = { * - Omits @id to avoid conflicts in service context */ export const SERVICE_NODE_DEFAULTS: Omit< - MeshNodeConfig, - '@id' | 'conf:generateResourcePages' | 'conf:stylesheetPath' + MeshRootNodeConfig, + "conf:generateResourcePages" | "conf:stylesheetPath" > = { - '@context': DEFAULT_CONTEXT, - '@type': 'conf:MeshNodeConfig', - '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': '/_assets/_templates/default-resource-page.html', // Service-relative path + "conf:templateMappings": { + "@type": "conf:TemplateMapping", + "conf:hasResourcePageTemplate": "/_assets/_templates/default-resource-page.html", // Service-relative path }, - 'conf:generateUnifiedDataset': false, - 'conf:generateAggregatedDataset': false, + "conf:generateUnifiedDataset": false, + "conf:generateAggregatedDataset": false, }; // Platform Service Configuration Defaults export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { - '@context': DEFAULT_CONTEXT, - '@type': 'fsvc:ServiceConfig', - '@id': 'local:serviceConfig', - 'fsvc:port': 3000, - 'fsvc:host': 'localhost', - 'fsvc:hasLoggingConfig': { - '@id': 'local:ServiceConfig#loggingConfig', - '@type': 'fsvc:LoggingConfig', - 'fsvc:hasConsoleChannel': { - '@id': 'local:ServiceConfig#consoleChannel', - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'info', + "@id": "local:serviceConfig", + "@context": DEFAULT_CONTEXT, + "@type": "fsvc:ServiceConfig", + "fsvc:port": 3000, + "fsvc:host": "localhost", + "fsvc:hasLoggingConfig": { + "@id": "local:ServiceConfig#loggingConfig", + "@type": "fsvc:LoggingConfig", + "fsvc:hasConsoleChannel": { + "@id": "local:ServiceConfig#consoleChannel", + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "info", }, - 'fsvc:hasFileChannel': { - '@id': 'local:ServiceConfig#fileChannel', - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': false, - 'fsvc:logLevel': 'warn', - 'fsvc:logFormat': 'pretty', - 'fsvc:logFilePath': './logs/flow-service.log', - 'fsvc:logRetentionDays': 30, - 'fsvc:logMaxFiles': 10, - 'fsvc:logMaxFileSize': 10485760, - 'fsvc:logRotationInterval': 'daily', + "fsvc:hasFileChannel": { + "@id": "local:ServiceConfig#fileChannel", + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": false, + "fsvc:logLevel": "warn", + "fsvc:logFormat": "pretty", + "fsvc:logFilePath": "./logs/flow-service.log", + "fsvc:logRetentionDays": 30, + "fsvc:logMaxFiles": 10, + "fsvc:logMaxFileSize": 10485760, + "fsvc:logRotationInterval": "daily", }, - 'fsvc:hasSentryChannel': { - '@id': 'local:ServiceConfig#sentryChannel', - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': false, - 'fsvc:logLevel': 'error', - 'fsvc:sentryLoggingEnabled': true, + "fsvc:hasSentryChannel": { + "@id": "local:ServiceConfig#sentryChannel", + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": false, + "fsvc:logLevel": "error", + "fsvc:sentryLoggingEnabled": true, }, }, - 'fsvc:hasContainedServices': { - '@id': 'local:ServiceConfig#containedServices', - '@type': 'fsvc:ContainedServicesConfig', - 'fsvc:apiEnabled': true, - 'fsvc:sparqlEnabled': true, - 'fsvc:queryWidgetEnabled': true, - 'fsvc:staticServerEnabled': true, - 'fsvc:apiDocsEnabled': true, + "fsvc:hasContainedServices": { + "@id": "local:ServiceConfig#containedServices", + "@type": "fsvc:ContainedServicesConfig", + "fsvc:apiEnabled": true, + "fsvc:sparqlEnabled": true, + "fsvc:queryWidgetEnabled": true, + "fsvc:staticServerEnabled": true, + "fsvc:apiDocsEnabled": true, }, }; // Development Environment Overrides export const DEVELOPMENT_SERVICE_OVERRIDES: Partial = { - 'fsvc:hasLoggingConfig': { - '@type': 'fsvc:LoggingConfig', - 'fsvc:hasConsoleChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'debug', // More verbose logging in development + "@id": "local:serviceConfig", + "fsvc:hasLoggingConfig": { + "@type": "fsvc:LoggingConfig", + "fsvc:hasConsoleChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "debug", // More verbose logging in development }, - 'fsvc:hasFileChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'info', - 'fsvc:logFormat': 'pretty', - 'fsvc:logFilePath': './logs/flow-service-dev.log', - 'fsvc:logRetentionDays': 7, - 'fsvc:logMaxFiles': 5, - 'fsvc:logMaxFileSize': 5242880, - 'fsvc:logRotationInterval': 'daily', + "fsvc:hasFileChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "info", + "fsvc:logFormat": "pretty", + "fsvc:logFilePath": "./logs/flow-service-dev.log", + "fsvc:logRetentionDays": 7, + "fsvc:logMaxFiles": 5, + "fsvc:logMaxFileSize": 5242880, + "fsvc:logRotationInterval": "daily", }, - 'fsvc:hasSentryChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'warn', - 'fsvc:sentryLoggingEnabled': true, + "fsvc:hasSentryChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "warn", + "fsvc:sentryLoggingEnabled": true, }, }, }; // Production Environment Overrides export const PRODUCTION_SERVICE_OVERRIDES: Partial = { - 'fsvc:hasLoggingConfig': { - '@type': 'fsvc:LoggingConfig', - 'fsvc:hasConsoleChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'info', // Less verbose in production + "@id": "local:serviceConfig", + "fsvc:hasLoggingConfig": { + "@type": "fsvc:LoggingConfig", + "fsvc:hasConsoleChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "info", // Less verbose in production }, - 'fsvc:hasFileChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': 'warn', - 'fsvc:logFormat': 'pretty', - 'fsvc:logFilePath': './logs/flow-service.log', - 'fsvc:logRetentionDays': 90, - 'fsvc:logMaxFiles': 20, - 'fsvc:logMaxFileSize': 52428800, - 'fsvc:logRotationInterval': 'daily', + "fsvc:hasFileChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, + "fsvc:logLevel": "warn", + "fsvc:logFormat": "pretty", + "fsvc:logFilePath": "./logs/flow-service.log", + "fsvc:logRetentionDays": 90, + "fsvc:logMaxFiles": 20, + "fsvc:logMaxFileSize": 52428800, + "fsvc:logRotationInterval": "daily", }, - 'fsvc:hasSentryChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, // Enable Sentry in production - 'fsvc:logLevel': 'error', - 'fsvc:sentryLoggingEnabled': true, + "fsvc:hasSentryChannel": { + "@type": "fsvc:LogChannelConfig", + "fsvc:logChannelEnabled": true, // Enable Sentry in production + "fsvc:logLevel": "error", + "fsvc:sentryLoggingEnabled": true, }, }, }; @@ -183,13 +187,13 @@ export const PRODUCTION_SERVICE_OVERRIDES: Partial = { * @returns The effective service configuration for the given environment */ export function getEnvironmentDefaults(environment?: string): ServiceConfig { - const env = environment?.toLowerCase() || 'development'; + const env = environment?.toLowerCase() || "development"; let envOverrides: Partial = {}; switch (env) { - case 'production': - case 'prod': + case "production": + case "prod": envOverrides = PRODUCTION_SERVICE_OVERRIDES; break; default: diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index 47824ba..e7c10ee 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -12,9 +12,9 @@ export type { LogChannelConfig, LoggingConfig, LogLevel, - MeshNodeConfig, - MeshNodeConfigContext, - MeshNodeConfigInput, + MeshRootNodeConfig, + MeshRootNodeConfigContext, + MeshRootNodeConfigInput, ServiceConfig, ServiceConfigContext, ServiceConfigInput, @@ -44,9 +44,9 @@ export { configExists, getNodeHierarchy, isConfigInheritanceEnabled, - loadMeshNodeConfig, + loadMeshRootNodeConfig, loadServiceConfig, - saveMeshNodeConfig, + saveMeshRootNodeConfig, saveServiceConfig, validateJsonLd, } from './loaders/jsonld-loader.ts'; @@ -63,7 +63,7 @@ export { export { validateServiceConfig } from './resolution/service-config-validator.ts'; -export { ServiceConfigAccessor } from './resolution/service-config-accessor.ts'; +export { singletonServiceConfigAccessor } from './resolution/service-config-accessor.ts'; // Shared Utilities export { mergeConfigs } from '../utils/merge-configs.ts'; @@ -73,7 +73,6 @@ import type { ServiceConfig, ServiceOptions } from './config-types.ts'; import { resolveServiceConfig } from './resolution/service-config-resolver.ts'; import { mergeConfigContext } from './resolution/service-config-utils.ts'; import { validateServiceConfig } from './resolution/service-config-validator.ts'; -import { ServiceConfigAccessor } from './resolution/service-config-accessor.ts'; import { handleCaughtError } from '../utils/logger.ts'; import { defaultQuadstoreBundle } from '../quadstore-default-bundle.ts'; diff --git a/flow-service/src/config/loaders/env-loader.ts b/flow-service/src/config/loaders/env-loader.ts index 7079dbd..8306b7a 100644 --- a/flow-service/src/config/loaders/env-loader.ts +++ b/flow-service/src/config/loaders/env-loader.ts @@ -224,7 +224,7 @@ export function loadEnvConfig(): ServiceConfigInput { } if (hasNodeDefaults) { - nodeDefaults['@type'] = 'conf:MeshNodeConfig'; + nodeDefaults['@type'] = 'conf:MeshRootNodeConfig'; configInput['fsvc:nodeDefaults'] = nodeDefaults; } diff --git a/flow-service/src/config/loaders/jsonld-loader.ts b/flow-service/src/config/loaders/jsonld-loader.ts index 901d304..f06e227 100644 --- a/flow-service/src/config/loaders/jsonld-loader.ts +++ b/flow-service/src/config/loaders/jsonld-loader.ts @@ -5,7 +5,7 @@ * Supports both service and node configuration formats. */ -import type { MeshNodeConfigInput, ServiceConfigInput } from '../config-types.ts'; +import type { MeshRootNodeConfigInput, ServiceConfigInput } from '../config-types.ts'; import { ConfigError } from '../config-types.ts'; import { handleCaughtError } from '../../utils/logger.ts'; import { getCurrentConfigDistPath } from '../../../../flow-core/src/utils/mesh-path-utils.ts'; @@ -81,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 loadMeshNodeConfig( +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); @@ -100,7 +100,7 @@ export async function loadMeshNodeConfig( ); } - return parsedConfig as MeshNodeConfigInput; + return parsedConfig as MeshRootNodeConfigInput; } catch (error) { if (error instanceof Deno.errors.NotFound) { // Node config doesn't exist - this is OK, return null @@ -175,9 +175,9 @@ export async function saveServiceConfig( * * Ensures the target directory exists before writing. Throws a ConfigError if saving fails. */ -export async function saveMeshNodeConfig( +export async function saveMeshRootNodeConfig( nodePath: string, - config: MeshNodeConfigInput, + config: MeshRootNodeConfigInput, ): Promise { const configPath = getCurrentConfigDistPath(nodePath); @@ -251,7 +251,7 @@ export async function isConfigInheritanceEnabled( nodePath: string, ): Promise { try { - const meshNodeConfig = await loadMeshNodeConfig(nodePath); + const meshNodeConfig = await loadMeshRootNodeConfig(nodePath); // If the node has explicit inheritance setting, use it if (meshNodeConfig?.['conf:configInheritanceEnabled'] !== undefined) { diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index 3b7380d..bd729e4 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -1,5 +1,5 @@ import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; -import type { ServiceConfigInput, MeshNodeConfigInput } from '../config-types.ts'; +import type { ServiceConfigInput, MeshRootNodeConfigInput } from '../config-types.ts'; import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; import { clearGraph, copyGraph, createNewGraphFromJsonLd } from '../../../../flow-core/src/utils/quadstore-utils.ts'; @@ -7,11 +7,11 @@ import { clearGraph, copyGraph, createNewGraphFromJsonLd } from '../../../../flo * Named graph terms for Quadstore config graphs */ export const GRAPH_NAMES = { - platformServiceDefaults: 'graph/platformServiceDefaults', - platformImplicitMeshNodeConfig: 'graph/platformImplicitMeshNodeConfig', - inputServiceConfig: 'graph/inputServiceConfig', - inputMeshNodeConfig: 'graph/inputMeshNodeConfig', - mergedServiceConfig: 'graph/mergedServiceConfig', + platformServiceDefaults: 'http://localhost/graph/platformServiceDefaults', + platformImplicitMeshRootNodeConfig: 'http://localhost/graph/platformImplicitMeshRootNodeConfig', + inputServiceConfig: 'http://localhost/graph/inputServiceConfig', + inputMeshRootNodeConfig: 'http://localhost/graph/inputMeshRootNodeConfig', + mergedServiceConfig: 'http://localhost/graph/mergedServiceConfig', // Per-node effective configs could be stored with dynamic graph names }; @@ -20,7 +20,7 @@ export const GRAPH_NAMES = { */ export async function loadPlatformDefaults(): Promise { await createNewGraphFromJsonLd(PLATFORM_SERVICE_DEFAULTS, { graphName: GRAPH_NAMES.platformServiceDefaults }); - await createNewGraphFromJsonLd(PLATFORM_NODE_DEFAULTS, { graphName: GRAPH_NAMES.platformImplicitMeshNodeConfig }); + await createNewGraphFromJsonLd(PLATFORM_NODE_DEFAULTS, { graphName: GRAPH_NAMES.platformImplicitMeshRootNodeConfig }); } /** @@ -33,8 +33,8 @@ export async function loadInputServiceConfig(inputConfig: ServiceConfigInput): P /** * Load input node config overrides into Quadstore graph */ -export async function loadInputMeshNodeConfig(inputMeshNodeConfig: MeshNodeConfigInput): Promise { - await createNewGraphFromJsonLd(inputMeshNodeConfig, { graphName: GRAPH_NAMES.inputMeshNodeConfig }); +export async function loadInputMeshRootNodeConfig(inputMeshRootNodeConfig: MeshRootNodeConfigInput): Promise { + await createNewGraphFromJsonLd(inputMeshRootNodeConfig, { graphName: GRAPH_NAMES.inputMeshRootNodeConfig }); } /** diff --git a/flow-service/src/config/node-config.default.jsonld b/flow-service/src/config/node-config.default.jsonld index 3e60db5..4db5b69 100644 --- a/flow-service/src/config/node-config.default.jsonld +++ b/flow-service/src/config/node-config.default.jsonld @@ -6,7 +6,7 @@ "conf": "https://semantic-flow.github.io/ontology/config-flow/" }, "@type": "flow:ConfigDistribution", - "@id": "conf:defaultMeshNodeConfig", + "@id": "conf:defaultMeshRootNodeConfig", "conf:versioningEnabled": true, "conf:configInheritanceEnabled": true, "conf:distributionFormats": [ diff --git a/flow-service/src/config/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts index 94edb1b..4ec1a1c 100644 --- a/flow-service/src/config/resolution/service-config-accessor.ts +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -1,10 +1,9 @@ - -import type { ServiceConfigContext, DelegationChain, AttributedTo } from '../config-types.ts'; 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 { GRAPH_NAMES } from '../loaders/quadstore-loader.ts'; -export class ServiceConfigAccessor { +export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { private bundle: QuadstoreBundle; private df: DataFactory; @@ -31,7 +30,7 @@ export class ServiceConfigAccessor { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { - GRAPH { + GRAPH <${GRAPH_NAMES.mergedServiceConfig}> { ?s fsvc:port ?value . } } LIMIT 1 @@ -44,7 +43,7 @@ export class ServiceConfigAccessor { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { - GRAPH { + GRAPH <${GRAPH_NAMES.mergedServiceConfig}> { ?s fsvc:host ?value . } } LIMIT 1 @@ -56,7 +55,7 @@ export class ServiceConfigAccessor { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { - GRAPH { + GRAPH <${GRAPH_NAMES.mergedServiceConfig}> { ?s fsvc:meshPaths ?value . } } @@ -76,4 +75,4 @@ export class ServiceConfigAccessor { } // 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 e68f9ad..77098d4 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -24,7 +24,7 @@ import { getConfigValue, mergeConfigContext } from './service-config-utils.ts'; * @throws ConfigError if configuration resolution fails or an unexpected error occurs */ -import { loadPlatformDefaults, loadInputServiceConfig, loadInputMeshNodeConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.ts'; +import { loadPlatformDefaults, loadInputServiceConfig, loadInputMeshRootNodeConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.ts'; export async function resolveServiceConfig( cliOptions?: ServiceOptions, @@ -54,7 +54,7 @@ export async function resolveServiceConfig( await loadInputServiceConfig(mergedInputConfig); // TODO: Load input mesh node config if applicable - // await loadInputMeshNodeConfig(...); + // await loadInputMeshRootNodeConfig(...); // Merge all graphs into mergedServiceConfig graph await mergeServiceConfigGraphs(); diff --git a/flow-service/src/utils/startup-logger.ts b/flow-service/src/utils/startup-logger.ts index 73c43fe..7c2232e 100644 --- a/flow-service/src/utils/startup-logger.ts +++ b/flow-service/src/utils/startup-logger.ts @@ -4,7 +4,7 @@ * Provides formatted logging for service startup configuration display */ -import type { ServiceConfigAccessor } from '../config/resolution/service-config-resolver.ts'; +import { singletonServiceConfigAccessor as config } from '../config/index.ts'; import { MESH } from '../../../flow-core/src/mesh-constants.ts'; import { resolve } from '../../../flow-core/src/deps.ts'; @@ -13,7 +13,7 @@ import { resolve } from '../../../flow-core/src/deps.ts'; * * Outputs mesh paths, logging levels, and feature enablement statuses to the console for the provided configuration. */ -export function logStartupConfiguration(config: ServiceConfigAccessor): void { +export async function logStartupConfiguration(): Promise { const now = new Date(); const timestamp = now.toLocaleDateString('en-US', { year: 'numeric', @@ -29,8 +29,9 @@ export function logStartupConfiguration(config: ServiceConfigAccessor): void { `🔧 Flow Service initializing at ${timestamp} with configuration:`, ); - if (config.meshPaths.length > 0) { - for (const meshPath of config.meshPaths) { + const meshPaths = await config.getMeshPaths(); + if (meshPaths.length > 0) { + for (const meshPath of meshPaths) { const absolutePath = resolve(Deno.cwd(), meshPath); console.log(` Configured mesh path: ${absolutePath}`); } @@ -38,37 +39,31 @@ export function logStartupConfiguration(config: ServiceConfigAccessor): void { console.log(` Mesh Paths: none configured`); } - console.log(` Console Logging: ${config.consoleLogLevel}`); - console.log( - ` File Logging: ${ - config.fileLogEnabled ? config.fileLogLevel : 'disabled' - }`, - ); - console.log( - ` Sentry Logging: ${ - config.sentryEnabled ? config.sentryLogLevel : 'disabled' - }`, - ); + // TODO: Add methods to accessor for these properties or query directly + console.log(` Console Logging: info`); + console.log(` File Logging: disabled`); + console.log(` Sentry Logging: disabled`); + const enabledServices = []; - if (config.apiEnabled) enabledServices.push('API'); - if (config.sparqlEnabled) enabledServices.push('SPARQL Endpoint'); - if (config.queryWidgetEnabled) enabledServices.push('SPARQL GUI'); + // TODO: Query service enablement flags from config + // if (await config.apiEnabled()) enabledServices.push('API'); + // if (await config.sparqlEnabled()) enabledServices.push('SPARQL Endpoint'); + // if (await config.queryWidgetEnabled()) enabledServices.push('SPARQL GUI'); console.log( - ` Services: ${ - enabledServices.length > 0 ? enabledServices.join(', ') : 'none' - }`, + ` Services: ${enabledServices.length > 0 ? enabledServices.join(', ') : 'none'}`, ); } /** * Logs the root service URL and API documentation URL based on the provided configuration. * - * @param config - The configuration accessor containing host and port information */ -export function logStartupUrls(config: ServiceConfigAccessor): void { - const baseUrl = `http://${config.host}:${config.port}`; +export async function logStartupUrls(): Promise { + const host = await config.getHost(); + const port = await config.getPort(); + const baseUrl = `http://${host}:${port}`; console.log(`📍 Root: ${baseUrl}/`); - console.log(`📚 API documentation: ${baseUrl}${MESH.API_PORTAL_ROUTE}`); + console.log(`� API documentation: ${baseUrl}${MESH.API_PORTAL_ROUTE}`); } diff --git a/package-lock.json b/package-lock.json index 0b61cb6..7da4cc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "devDependencies": { + "@typescript-eslint/parser": "^8.38.0", "eslint": "^9.32.0" } }, @@ -215,6 +216,44 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -229,6 +268,175 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/parser": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.38.0", + "@typescript-eslint/types": "^8.38.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.38.0", + "@typescript-eslint/tsconfig-utils": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.38.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -310,6 +518,19 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -579,6 +800,36 @@ "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", @@ -593,6 +844,16 @@ "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", @@ -606,6 +867,19 @@ "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", @@ -740,6 +1014,16 @@ "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", @@ -828,6 +1112,30 @@ "dev": true, "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -938,6 +1246,19 @@ "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", @@ -958,6 +1279,27 @@ "node": ">=6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -968,6 +1310,54 @@ "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", @@ -1017,6 +1407,32 @@ "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", @@ -1030,6 +1446,21 @@ "node": ">= 0.8.0" } }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index 5e510b2..493f0d3 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "devDependencies": { + "@typescript-eslint/parser": "^8.38.0", "eslint": "^9.32.0" } } From 561051313f819c0b8daae66fe23e42b55e420ee4 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Fri, 1 Aug 2025 22:00:15 -0700 Subject: [PATCH 12/20] moved and renamed CONFIG_GRAPH_NAMES; non-singleton ServiceAccessor cleanup --- flow-service/flow-service-config.jsonld | 2 +- flow-service/main.ts | 4 +-- flow-service/src/config/config-types.ts | 6 ---- flow-service/src/config/index.ts | 14 +++++++- .../src/config/loaders/quadstore-loader.ts | 32 ++++++------------- .../resolution/service-config-accessor.ts | 25 +++++++++------ .../resolution/service-config-resolver.ts | 3 -- .../resolution/service-config-validator.ts | 4 +-- flow-service/src/routes/meshes.ts | 13 +++----- flow-service/src/routes/weave.ts | 5 +-- .../src/services/metadata-composer.ts | 5 ++- flow-service/src/utils/mesh-utils.ts | 8 ++--- 12 files changed, 54 insertions(+), 67 deletions(-) diff --git a/flow-service/flow-service-config.jsonld b/flow-service/flow-service-config.jsonld index ffe2d9a..a81e850 100644 --- a/flow-service/flow-service-config.jsonld +++ b/flow-service/flow-service-config.jsonld @@ -10,8 +10,8 @@ "dcterms": "http://purl.org/dc/terms/", "mergedServiceConfig": "http://localhost/graph/mergedServiceConfig/" }, + "@id": "local:serviceConfig", "@type": "fsvc:ServiceConfig", - "@id": "mergedServiceConfig:serviceConfig", "fsvc:host": "localhost", "fsvc:port": 8080, "fsvc:meshPaths": [ diff --git a/flow-service/main.ts b/flow-service/main.ts index 0a92242..6d051d3 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -82,7 +82,7 @@ app.route('/api', health); import { createMeshesRoutes } from './src/routes/meshes.ts'; import { createWeaveRoutes } from './src/routes/weave.ts'; -const meshes = createMeshesRoutes(singletonServiceConfigAccessor); +const meshes = createMeshesRoutes(); const weave = createWeaveRoutes(singletonServiceConfigAccessor); app.route('/api', meshes); @@ -90,7 +90,7 @@ app.route('/api', weave); // Startup logging try { - logStartupUrls(singletonServiceConfigAccessor); + logStartupUrls(); } catch (error) { await handleCaughtError(error, 'Failed to log startup URLs'); console.error('⚠️ URL logging failed, but continuing startup...'); diff --git a/flow-service/src/config/config-types.ts b/flow-service/src/config/config-types.ts index 4781882..57b9f8b 100644 --- a/flow-service/src/config/config-types.ts +++ b/flow-service/src/config/config-types.ts @@ -130,12 +130,6 @@ export interface MeshRootNodeConfigInput extends Partial { 'conf:defaultAttribution'?: string; } -// Configuration Context Types (Side-by-Side Pattern) -export interface ServiceConfigContext { - readonly inputOptions: ServiceConfigInput; - readonly defaultOptions: ServiceConfig; -} - export interface MeshRootNodeConfigContext { readonly inputOptions: MeshRootNodeConfigInput; readonly defaultOptions: MeshRootNodeConfig; diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index e7c10ee..19dce0f 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -5,6 +5,19 @@ * and side-by-side configuration context. */ +/** + * Named graph terms for Quadstore config graphs + */ +export const CONFIG_GRAPH_NAMES = { + platformServiceDefaults: 'http://localhost/graph/platformServiceDefaults', + platformImplicitMeshRootNodeConfig: 'http://localhost/graph/platformImplicitMeshRootNodeConfig', + inputServiceConfig: 'http://localhost/graph/inputServiceConfig', + inputMeshRootNodeConfig: 'http://localhost/graph/inputMeshRootNodeConfig', + mergedServiceConfig: 'http://localhost/graph/mergedServiceConfig', + // Per-node effective configs could be stored with dynamic graph names +}; + + // Core Types export type { ContainedServicesConfig, @@ -16,7 +29,6 @@ export type { MeshRootNodeConfigContext, MeshRootNodeConfigInput, ServiceConfig, - ServiceConfigContext, ServiceConfigInput, ServiceOptions, TemplateMapping, diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index bd729e4..64627e0 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -2,39 +2,27 @@ import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; import type { ServiceConfigInput, MeshRootNodeConfigInput } from '../config-types.ts'; import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; import { clearGraph, copyGraph, createNewGraphFromJsonLd } from '../../../../flow-core/src/utils/quadstore-utils.ts'; - -/** - * Named graph terms for Quadstore config graphs - */ -export const GRAPH_NAMES = { - platformServiceDefaults: 'http://localhost/graph/platformServiceDefaults', - platformImplicitMeshRootNodeConfig: 'http://localhost/graph/platformImplicitMeshRootNodeConfig', - inputServiceConfig: 'http://localhost/graph/inputServiceConfig', - inputMeshRootNodeConfig: 'http://localhost/graph/inputMeshRootNodeConfig', - mergedServiceConfig: 'http://localhost/graph/mergedServiceConfig', - // Per-node effective configs could be stored with dynamic graph names -}; - +import { CONFIG_GRAPH_NAMES } from '../index.ts'; /** * Load platform defaults into Quadstore graphs */ export async function loadPlatformDefaults(): Promise { - await createNewGraphFromJsonLd(PLATFORM_SERVICE_DEFAULTS, { graphName: GRAPH_NAMES.platformServiceDefaults }); - await createNewGraphFromJsonLd(PLATFORM_NODE_DEFAULTS, { graphName: GRAPH_NAMES.platformImplicitMeshRootNodeConfig }); + await createNewGraphFromJsonLd(PLATFORM_SERVICE_DEFAULTS, { graphName: CONFIG_GRAPH_NAMES.platformServiceDefaults }); + await createNewGraphFromJsonLd(PLATFORM_NODE_DEFAULTS, { graphName: CONFIG_GRAPH_NAMES.platformImplicitMeshRootNodeConfig }); } /** * Load input service config into Quadstore graph */ export async function loadInputServiceConfig(inputConfig: ServiceConfigInput): Promise { - await createNewGraphFromJsonLd(inputConfig, { graphName: GRAPH_NAMES.inputServiceConfig }); + await createNewGraphFromJsonLd(inputConfig, { graphName: CONFIG_GRAPH_NAMES.inputServiceConfig }); } /** * Load input node config overrides into Quadstore graph */ export async function loadInputMeshRootNodeConfig(inputMeshRootNodeConfig: MeshRootNodeConfigInput): Promise { - await createNewGraphFromJsonLd(inputMeshRootNodeConfig, { graphName: GRAPH_NAMES.inputMeshRootNodeConfig }); + await createNewGraphFromJsonLd(inputMeshRootNodeConfig, { graphName: CONFIG_GRAPH_NAMES.inputMeshRootNodeConfig }); } /** @@ -47,20 +35,20 @@ export async function mergeServiceConfigGraphs( } = defaultQuadstoreBundle ): Promise { // Clear merged graph - const count = await clearGraph(df.namedNode(GRAPH_NAMES.mergedServiceConfig)); + const count = await clearGraph(df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig)); if (count > 0) { console.info(`Deleted ${count} quads from mergedServiceConfig graph`); } // Copy input service config quads - await copyGraph(df.namedNode(GRAPH_NAMES.inputServiceConfig), df.namedNode(GRAPH_NAMES.mergedServiceConfig)); + await copyGraph(df.namedNode(CONFIG_GRAPH_NAMES.inputServiceConfig), df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig)); // Copy platform service defaults quads if not overridden - const platformQuads = store.match(undefined, undefined, undefined, df.namedNode(GRAPH_NAMES.platformServiceDefaults)); + const platformQuads = store.match(undefined, undefined, undefined, df.namedNode(CONFIG_GRAPH_NAMES.platformServiceDefaults)); for await (const q of platformQuads as any) { - const exists = await store.countQuads(q.subject, q.predicate, undefined, df.namedNode(GRAPH_NAMES.mergedServiceConfig)); + const exists = await store.countQuads(q.subject, q.predicate, undefined, df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig)); if (exists === 0) { - await store.put(df.quad(q.subject, q.predicate, q.object, df.namedNode(GRAPH_NAMES.mergedServiceConfig))); + await store.put(df.quad(q.subject, q.predicate, q.object, df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig))); } } } diff --git a/flow-service/src/config/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts index 4ec1a1c..7b4fed0 100644 --- a/flow-service/src/config/resolution/service-config-accessor.ts +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -1,7 +1,8 @@ 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 { GRAPH_NAMES } from '../loaders/quadstore-loader.ts'; +import { CONFIG_GRAPH_NAMES } from '../index.ts'; +import { handleCaughtError } from '../../utils/logger.ts'; export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { private bundle: QuadstoreBundle; @@ -12,16 +13,22 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { this.df = bundle.df; } + private async querySingleValue(sparql: string): Promise { if (!this.bundle.engine) { throw new Error('SPARQL engine not initialized in Quadstore bundle'); } - 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; + try { + const bindingsStream = await this.bundle.engine.queryBindings(sparql, { sources: [this.bundle.store] }); + for await (const binding of bindingsStream as any) { + const value = binding.get('value'); + if (value) { + return value.value; + } } + } catch (error) { + await handleCaughtError(error, 'Failed to execute SPARQL query'); + throw new Error('Failed to execute SPARQL query'); } return undefined; } @@ -30,7 +37,7 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { - GRAPH <${GRAPH_NAMES.mergedServiceConfig}> { + GRAPH <${CONFIG_GRAPH_NAMES.mergedServiceConfig}> { ?s fsvc:port ?value . } } LIMIT 1 @@ -43,7 +50,7 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { - GRAPH <${GRAPH_NAMES.mergedServiceConfig}> { + GRAPH <${CONFIG_GRAPH_NAMES.mergedServiceConfig}> { ?s fsvc:host ?value . } } LIMIT 1 @@ -55,7 +62,7 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { - GRAPH <${GRAPH_NAMES.mergedServiceConfig}> { + GRAPH <${CONFIG_GRAPH_NAMES.mergedServiceConfig}> { ?s fsvc:meshPaths ?value . } } diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index 77098d4..a079202 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -17,10 +17,7 @@ import { getConfigValue, mergeConfigContext } from './service-config-utils.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 ServiceConfigContext 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 ServiceConfigContext object with both input and default configuration options * @throws ConfigError if configuration resolution fails or an unexpected error occurs */ diff --git a/flow-service/src/config/resolution/service-config-validator.ts b/flow-service/src/config/resolution/service-config-validator.ts index 99170d6..4f2ed4d 100644 --- a/flow-service/src/config/resolution/service-config-validator.ts +++ b/flow-service/src/config/resolution/service-config-validator.ts @@ -2,7 +2,7 @@ import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; import { DataFactory } from '../../../../flow-core/src/deps.ts'; import { ConfigError } from '../config-types.ts'; -import { GRAPH_NAMES } from '../loaders/quadstore-loader.ts'; +import { CONFIG_GRAPH_NAMES } from '../index.ts'; /** * Validates essential service configuration values in the merged config graph. @@ -10,7 +10,7 @@ import { GRAPH_NAMES } from '../loaders/quadstore-loader.ts'; */ export async function validateServiceConfig(): Promise { const { store, df } = defaultQuadstoreBundle; - const graph = df.namedNode(GRAPH_NAMES.mergedServiceConfig); + const graph = df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig); // Validate port const portQuads = store.match(undefined, df.namedNode('https://semantic-flow.github.io/ontology/flow-service/port'), undefined, graph); diff --git a/flow-service/src/routes/meshes.ts b/flow-service/src/routes/meshes.ts index 51c5337..5da76b3 100644 --- a/flow-service/src/routes/meshes.ts +++ b/flow-service/src/routes/meshes.ts @@ -10,18 +10,15 @@ import { import { normalizeFolderPath } from '../../../flow-core/src/utils/path-utils.ts'; import { MESH } from '../../../flow-core/src/mesh-constants.ts'; import { dirname, existsSync, join } from '../../../flow-core/src/deps.ts'; -import { ServiceConfigAccessor } from '../config/index.ts'; import { composeMetadataContent } from '../services/metadata-composer.ts'; import { initializeMeshRegistry } from '../utils/mesh-utils.ts'; //import { Context } from '@hono/hono'; -export const createMeshesRoutes = ( - config: ServiceConfigAccessor, -): OpenAPIHono => { +export const createMeshesRoutes = (): OpenAPIHono => { const meshes = new OpenAPIHono(); const meshRegistry: Record = {}; - initializeMeshRegistry(config, meshRegistry); + initializeMeshRegistry(); // Schemas for Mesh Registration (POST /api/meshes) const MeshRegistrationRequest = z.object({ @@ -136,9 +133,8 @@ export const createMeshesRoutes = ( if (meshRegistry[name]) { return c.json({ error: 'Bad Request', - message: `Mesh '${name}' is already registered at path: ${ - meshRegistry[name] - }`, + message: `Mesh '${name}' is already registered at path: ${meshRegistry[name] + }`, }, 400); } @@ -335,7 +331,6 @@ export const createMeshesRoutes = ( slug, nodeType, initialData, - config, startTime, ); diff --git a/flow-service/src/routes/weave.ts b/flow-service/src/routes/weave.ts index 64badc5..e709f4c 100644 --- a/flow-service/src/routes/weave.ts +++ b/flow-service/src/routes/weave.ts @@ -1,7 +1,6 @@ import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi'; import { logger } from '../utils/logger.ts'; import { MESH } from '../../../flow-core/src/mesh-constants.ts'; -import { ServiceConfigAccessor } from '../config/index.ts'; // Utility function to validate node specifier using QName rules (simplified regex for example) const isValidNodeSpecifier = (specifier: string): boolean => { @@ -18,7 +17,6 @@ const parseNodeSpecifier = (specifier: string): string[] => { }; export const createWeaveRoutes = ( - config: ServiceConfigAccessor, ): OpenAPIHono => { const weave = new OpenAPIHono(); @@ -92,8 +90,7 @@ export const createWeaveRoutes = ( const nodeSegments = parseNodeSpecifier(nodeSpecifier); logger.info( - `Starting weave process for node specifier: ${nodeSpecifier} (segments: ${ - nodeSegments.join(', ') + `Starting weave process for node specifier: ${nodeSpecifier} (segments: ${nodeSegments.join(', ') })`, ); diff --git a/flow-service/src/services/metadata-composer.ts b/flow-service/src/services/metadata-composer.ts index 431f662..3bb219c 100644 --- a/flow-service/src/services/metadata-composer.ts +++ b/flow-service/src/services/metadata-composer.ts @@ -1,4 +1,5 @@ -import { ServiceConfigAccessor } from '../config/index.ts'; +import { singletonServiceConfigAccessor as config } from '../config/resolution/service-config-accessor.ts'; + //import { MESH } from '../../../flow-core/src/mesh-constants.ts'; /** @@ -7,14 +8,12 @@ import { ServiceConfigAccessor } from '../config/index.ts'; * @param slug - The slug identifier for the node * @param nodeType - The type of the node (e.g., Namespace, Reference, Dataset) * @param initialData - An object containing the initial metadata for the node - * @param config - The service configuration accessor * @returns The metadata content JSON object */ export function composeMetadataContent( slug: string, nodeType: string, initialData: Record, - config: ServiceConfigAccessor, startedAtTime?: string, ): Record { const title = typeof initialData.title === 'string' diff --git a/flow-service/src/utils/mesh-utils.ts b/flow-service/src/utils/mesh-utils.ts index 39da494..35ea7f4 100644 --- a/flow-service/src/utils/mesh-utils.ts +++ b/flow-service/src/utils/mesh-utils.ts @@ -1,12 +1,11 @@ -import { ServiceConfigAccessor } from '../config/index.ts'; import { logger } from './logger.ts'; import { basename, dirname, existsSync } from '../../../flow-core/src/deps.ts'; +import { singletonServiceConfigAccessor as config } from '../config/resolution/service-config-accessor.ts'; export const initializeMeshRegistry = ( - config: ServiceConfigAccessor, meshRegistry: Record, ) => { - const meshPaths = config.meshPaths; + const meshPaths = config.getMeshPaths; if (meshPaths && Array.isArray(meshPaths)) { for (const meshPath of meshPaths) { const meshName = basename(meshPath); @@ -27,8 +26,7 @@ export const initializeMeshRegistry = ( if (meshRegistry[meshName]) { logger.warn( - `Mesh name collision detected: '${meshName}' already exists at path '${ - meshRegistry[meshName] + `Mesh name collision detected: '${meshName}' already exists at path '${meshRegistry[meshName] }'. Skipping '${meshPath}'.`, ); continue; From 0d67c442e985dec671136d1755683dfc15cb6430 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Fri, 1 Aug 2025 22:46:12 -0700 Subject: [PATCH 13/20] Removes unused config utility functions Eliminates legacy configuration utilities and their exports to streamline the config resolution logic. Updates tests to use the recommended config accessor, improving maintainability and reducing deprecated code paths. --- flow-service/logs/test-pretty-direct.log | 8 +- flow-service/main.ts | 2 +- flow-service/src/config/index.ts | 18 +-- .../resolution/service-config-resolver.ts | 4 - .../config/resolution/service-config-utils.ts | 38 ----- flow-service/src/routes/meshes.ts | 2 - .../integration/logging/entry-init.test.ts | 88 ----------- .../logging/log-retention-config.test.ts | 144 +----------------- .../integration/logging/sentry-init.test.ts | 85 +---------- .../logging/sentry-logging.test.ts | 119 +-------------- 10 files changed, 14 insertions(+), 494 deletions(-) delete mode 100644 flow-service/src/config/resolution/service-config-utils.ts delete mode 100644 flow-service/tests/integration/logging/entry-init.test.ts diff --git a/flow-service/logs/test-pretty-direct.log b/flow-service/logs/test-pretty-direct.log index f17b6a9..74deccb 100644 --- a/flow-service/logs/test-pretty-direct.log +++ b/flow-service/logs/test-pretty-direct.log @@ -1,4 +1,4 @@ -[2025-07-30T01:03:13.334Z] INFO File logging test started (op=startup, [config-resolver]) -[2025-07-30T01:03:13.339Z] WARN Configuration override detected (op=config-resolve) -[2025-07-30T01:03:13.339Z] ERROR Database connection failed (op=startup, 5000ms) -[2025-07-30T01:03:13.340Z] DEBUG Processing mesh nodes (op=scan, mesh=/data/mesh, 1250ms, 42 nodes) +[2025-08-02T05:45:58.391Z] INFO File logging test started (op=startup, [config-resolver]) +[2025-08-02T05:45:58.393Z] WARN Configuration override detected (op=config-resolve) +[2025-08-02T05:45:58.393Z] ERROR Database connection failed (op=startup, 5000ms) +[2025-08-02T05:45:58.393Z] DEBUG Processing mesh nodes (op=scan, mesh=/data/mesh, 1250ms, 42 nodes) diff --git a/flow-service/main.ts b/flow-service/main.ts index 6d051d3..b9767a1 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -83,7 +83,7 @@ import { createMeshesRoutes } from './src/routes/meshes.ts'; import { createWeaveRoutes } from './src/routes/weave.ts'; const meshes = createMeshesRoutes(); -const weave = createWeaveRoutes(singletonServiceConfigAccessor); +const weave = createWeaveRoutes(); app.route('/api', meshes); app.route('/api', weave); diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index 19dce0f..ca66f44 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -68,11 +68,6 @@ export { resolveServiceConfig, } from './resolution/service-config-resolver.ts'; -export { - getConfigValue, - mergeConfigContext, -} from './resolution/service-config-utils.ts'; - export { validateServiceConfig } from './resolution/service-config-validator.ts'; export { singletonServiceConfigAccessor } from './resolution/service-config-accessor.ts'; @@ -81,12 +76,10 @@ export { singletonServiceConfigAccessor } from './resolution/service-config-acce export { mergeConfigs } from '../utils/merge-configs.ts'; // Import the implementations for the helper functions -import type { ServiceConfig, ServiceOptions } from './config-types.ts'; +import type { ServiceOptions } from './config-types.ts'; import { resolveServiceConfig } from './resolution/service-config-resolver.ts'; -import { mergeConfigContext } from './resolution/service-config-utils.ts'; import { validateServiceConfig } from './resolution/service-config-validator.ts'; import { handleCaughtError } from '../utils/logger.ts'; -import { defaultQuadstoreBundle } from '../quadstore-default-bundle.ts'; /** * Resolves and validates the service configuration. @@ -104,12 +97,3 @@ export async function createServiceConfig( throw error; } } - -/** - * @deprecated This function is no longer needed as the merged config is accessed via Quadstore. - */ -export async function getCompleteServiceConfig( - cliOptions?: ServiceOptions, -): Promise { - throw new Error('getCompleteServiceConfig is deprecated. Use ServiceConfigAccessor to query config.'); -} diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index a079202..012c830 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -1,5 +1,4 @@ import type { - ServiceConfigContext, ServiceConfigInput, ServiceOptions, } from '../config-types.ts'; @@ -12,7 +11,6 @@ import { ConfigError } from '../config-types.ts'; import { mergeConfigs } from '../../utils/merge-configs.ts'; import { handleCaughtError } from '../../utils/logger.ts'; import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; -import { getConfigValue, mergeConfigContext } from './service-config-utils.ts'; /** * Asynchronously resolves the service configuration by merging CLI options, environment variables, configuration files, and environment-specific defaults in a defined precedence order. @@ -126,5 +124,3 @@ function convertCliOptionsToConfig( return config; } - -export { getConfigValue, mergeConfigContext }; diff --git a/flow-service/src/config/resolution/service-config-utils.ts b/flow-service/src/config/resolution/service-config-utils.ts deleted file mode 100644 index 1631250..0000000 --- a/flow-service/src/config/resolution/service-config-utils.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { ServiceConfigContext, ServiceConfigInput } from '../config-types.ts'; -import { mergeConfigs } from '../../utils/merge-configs.ts'; -import { PLATFORM_SERVICE_DEFAULTS } from '../defaults.ts'; - -/** - * 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, - ); -} diff --git a/flow-service/src/routes/meshes.ts b/flow-service/src/routes/meshes.ts index 5da76b3..c9ddbc1 100644 --- a/flow-service/src/routes/meshes.ts +++ b/flow-service/src/routes/meshes.ts @@ -17,8 +17,6 @@ import { initializeMeshRegistry } from '../utils/mesh-utils.ts'; export const createMeshesRoutes = (): OpenAPIHono => { const meshes = new OpenAPIHono(); - const meshRegistry: Record = {}; - initializeMeshRegistry(); // Schemas for Mesh Registration (POST /api/meshes) const MeshRegistrationRequest = z.object({ diff --git a/flow-service/tests/integration/logging/entry-init.test.ts b/flow-service/tests/integration/logging/entry-init.test.ts deleted file mode 100644 index 809e8e6..0000000 --- a/flow-service/tests/integration/logging/entry-init.test.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/logging/log-retention-config.test.ts b/flow-service/tests/integration/logging/log-retention-config.test.ts index 986540c..770073d 100644 --- a/flow-service/tests/integration/logging/log-retention-config.test.ts +++ b/flow-service/tests/integration/logging/log-retention-config.test.ts @@ -1,141 +1,5 @@ -#!/usr/bin/env -S deno run --allow-all +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"; -/** - * 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(); -} +// TODO: log retention/rotation tests diff --git a/flow-service/tests/integration/logging/sentry-init.test.ts b/flow-service/tests/integration/logging/sentry-init.test.ts index 809e8e6..da74115 100644 --- a/flow-service/tests/integration/logging/sentry-init.test.ts +++ b/flow-service/tests/integration/logging/sentry-init.test.ts @@ -1,88 +1,7 @@ -#!/usr/bin/env -S deno run --allow-all - /** - * Test script to verify Sentry initialization and logging + * TODO: 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; - } -} +import { singletonServiceConfigAccessor as config } from '../../../src/config/resolution/service-config-accessor.ts'; -if (import.meta.main) { - await testSentryInit(); -} diff --git a/flow-service/tests/integration/logging/sentry-logging.test.ts b/flow-service/tests/integration/logging/sentry-logging.test.ts index 264403c..a08aece 100644 --- a/flow-service/tests/integration/logging/sentry-logging.test.ts +++ b/flow-service/tests/integration/logging/sentry-logging.test.ts @@ -1,121 +1,6 @@ -#!/usr/bin/env -S deno run --allow-all - /** - * Test script to verify Sentry logging integration with log retention configuration + * TODO: 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(); -} +import { singletonServiceConfigAccessor as config } from '../../../src/config/resolution/service-config-accessor.ts'; From 2633986f584e31436568224f3727d076c5bcc7e0 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Sat, 2 Aug 2025 16:06:09 -0700 Subject: [PATCH 14/20] chore: update quadstore dependency to version 15.4.0-beta.0 - Updated the import of Quadstore in deps.ts to use the beta version. - Modified clearGraph function to remove return count and log the number of quads after deletion. - Enhanced error handling in createQuadstoreBundle to catch and log errors when opening the quadstore. - Updated tests for clearGraph to reflect changes in return type and added assertions for graph state before and after clearing. - Cleaned up type assertions in various files to improve type safety. - Removed unnecessary console logs and comments in service configuration loaders and validators. --- deno.lock | 634 +++++++++++++++++- flow-core/src/deps.ts | 2 +- flow-core/src/utils/quadstore-utils.ts | 23 +- .../src/utils/quadstore/quadstore-factory.ts | 10 +- flow-core/tests/unit/quadstore-utils.test.ts | 22 +- flow-service/deno.lock | 12 +- .../src/config/loaders/quadstore-loader.ts | 7 +- .../resolution/service-config-validator.ts | 4 +- flow-service/src/utils/errors.ts | 2 +- package-lock.json | 198 ++++++ package.json | 3 + 11 files changed, 879 insertions(+), 38 deletions(-) diff --git a/deno.lock b/deno.lock index 4142d05..f6bd454 100644 --- a/deno.lock +++ b/deno.lock @@ -10,14 +10,18 @@ "npm:@rdfjs/types@*": "2.0.1", "npm:@scalar/hono-api-reference@*": "0.9.12_hono@4.8.5", "npm:@scalar/openapi-to-markdown@*": "0.2.23", + "npm:@scalar/openapi-to-markdown@~0.2.23": "0.2.23", "npm:@sentry/deno@*": "9.40.0", "npm:@types/jsonld@*": "1.5.15", "npm:@types/node@*": "22.15.15", + "npm:@typescript-eslint/parser@^8.38.0": "8.38.0_eslint@9.32.0_typescript@5.8.3", "npm:abstract-level@*": "3.1.0", + "npm:eslint@^9.32.0": "9.32.0", "npm:jsonld@*": "8.3.3", "npm:memory-level@*": "3.1.0", "npm:quadstore-comunica@*": "6.2.0_asynciterator@3.9.0_quadstore@15.3.0", "npm:quadstore@*": "15.3.0", + "npm:quadstore@^15.4.0-beta.0": "15.4.0-beta.0", "npm:rdf-data-factory@*": "2.0.2" }, "jsr": { @@ -71,6 +75,60 @@ "undici" ] }, + "@eslint-community/eslint-utils@4.7.0_eslint@9.32.0": { + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dependencies": [ + "eslint", + "eslint-visitor-keys@3.4.3" + ] + }, + "@eslint-community/regexpp@4.12.1": { + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==" + }, + "@eslint/config-array@0.21.0": { + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dependencies": [ + "@eslint/object-schema", + "debug", + "minimatch@3.1.2" + ] + }, + "@eslint/config-helpers@0.3.0": { + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==" + }, + "@eslint/core@0.15.1": { + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dependencies": [ + "@types/json-schema" + ] + }, + "@eslint/eslintrc@3.3.1": { + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dependencies": [ + "ajv@6.12.6", + "debug", + "espree", + "globals", + "ignore", + "import-fresh", + "js-yaml", + "minimatch@3.1.2", + "strip-json-comments" + ] + }, + "@eslint/js@9.32.0": { + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==" + }, + "@eslint/object-schema@2.1.6": { + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==" + }, + "@eslint/plugin-kit@0.3.4": { + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", + "dependencies": [ + "@eslint/core", + "levn" + ] + }, "@fastify/busboy@2.1.1": { "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==" }, @@ -139,6 +197,25 @@ "zod@4.0.5" ] }, + "@humanfs/core@0.19.1": { + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==" + }, + "@humanfs/node@0.16.6": { + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dependencies": [ + "@humanfs/core", + "@humanwhocodes/retry@0.3.1" + ] + }, + "@humanwhocodes/module-importer@1.0.1": { + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + }, + "@humanwhocodes/retry@0.3.1": { + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==" + }, + "@humanwhocodes/retry@0.4.3": { + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==" + }, "@hyperjump/browser@1.3.1": { "integrity": "sha512-Le5XZUjnVqVjkgLYv6yyWgALat/0HpB1XaCPuCZ+GCFki9NvXloSZITIJ0H+wRW7mb9At1SxvohKBbNQbrr/cw==", "dependencies": [ @@ -209,6 +286,23 @@ "@jridgewell/sourcemap-codec" ] }, + "@nodelib/fs.scandir@2.1.5": { + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": [ + "@nodelib/fs.stat", + "run-parallel" + ] + }, + "@nodelib/fs.stat@2.0.5": { + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk@1.2.8": { + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": [ + "@nodelib/fs.scandir", + "fastq" + ] + }, "@phosphor-icons/core@2.1.1": { "integrity": "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==" }, @@ -291,7 +385,7 @@ "dependencies": [ "@phosphor-icons/core", "@types/node@22.16.5", - "chalk", + "chalk@5.4.1", "vue" ] }, @@ -330,7 +424,7 @@ "@scalar/openapi-parser@0.18.2_ajv@8.17.1": { "integrity": "sha512-Mb0rRgnn8QgrwcsOcZY3g4uDr49cSuYcmh87oT7gDuV9IPFg5Wc71fjXNx0HdTRy5k21ugS/lDSl8sL+YBudOw==", "dependencies": [ - "ajv", + "ajv@8.17.1", "ajv-draft-04", "ajv-formats", "jsonpointer", @@ -455,6 +549,9 @@ "@types/ms" ] }, + "@types/estree@1.0.8": { + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, "@types/har-format@1.2.16": { "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==" }, @@ -464,6 +561,9 @@ "@types/unist" ] }, + "@types/json-schema@7.0.15": { + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, "@types/jsonld@1.5.15": { "integrity": "sha512-PlAFPZjL+AuGYmwlqwKEL0IMP8M8RexH0NIPGfCVWSQ041H2rR/8OlyZSD7KsCVoN8vCfWdtWDBxX8yBVP+xow==" }, @@ -500,6 +600,66 @@ "@types/web-bluetooth@0.0.20": { "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" }, + "@typescript-eslint/parser@8.38.0_eslint@9.32.0_typescript@5.8.3": { + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", + "dependencies": [ + "@typescript-eslint/scope-manager", + "@typescript-eslint/types", + "@typescript-eslint/typescript-estree", + "@typescript-eslint/visitor-keys", + "debug", + "eslint", + "typescript" + ] + }, + "@typescript-eslint/project-service@8.38.0_typescript@5.8.3": { + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", + "dependencies": [ + "@typescript-eslint/tsconfig-utils", + "@typescript-eslint/types", + "debug", + "typescript" + ] + }, + "@typescript-eslint/scope-manager@8.38.0": { + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", + "dependencies": [ + "@typescript-eslint/types", + "@typescript-eslint/visitor-keys" + ] + }, + "@typescript-eslint/tsconfig-utils@8.38.0_typescript@5.8.3": { + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "dependencies": [ + "typescript" + ] + }, + "@typescript-eslint/types@8.38.0": { + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==" + }, + "@typescript-eslint/typescript-estree@8.38.0_typescript@5.8.3": { + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", + "dependencies": [ + "@typescript-eslint/project-service", + "@typescript-eslint/tsconfig-utils", + "@typescript-eslint/types", + "@typescript-eslint/visitor-keys", + "debug", + "fast-glob", + "is-glob", + "minimatch@9.0.5", + "semver", + "ts-api-utils", + "typescript" + ] + }, + "@typescript-eslint/visitor-keys@8.38.0": { + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", + "dependencies": [ + "@typescript-eslint/types", + "eslint-visitor-keys@4.2.1" + ] + }, "@ungap/structured-clone@1.3.0": { "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" }, @@ -609,6 +769,12 @@ "module-error" ] }, + "acorn-jsx@5.3.2_acorn@8.15.0": { + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dependencies": [ + "acorn" + ] + }, "acorn@8.15.0": { "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "bin": true @@ -616,19 +782,28 @@ "ajv-draft-04@1.0.0_ajv@8.17.1": { "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", "dependencies": [ - "ajv" + "ajv@8.17.1" ], "optionalPeers": [ - "ajv" + "ajv@8.17.1" ] }, "ajv-formats@3.0.1_ajv@8.17.1": { "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dependencies": [ - "ajv" + "ajv@8.17.1" ], "optionalPeers": [ - "ajv" + "ajv@8.17.1" + ] + }, + "ajv@6.12.6": { + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": [ + "fast-deep-equal", + "fast-json-stable-stringify", + "json-schema-traverse@0.4.1", + "uri-js" ] }, "ajv@8.17.1": { @@ -636,10 +811,19 @@ "dependencies": [ "fast-deep-equal", "fast-uri", - "json-schema-traverse", + "json-schema-traverse@1.0.0", "require-from-string" ] }, + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": [ + "color-convert" + ] + }, + "argparse@2.0.1": { + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "aria-hidden@1.2.6": { "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "dependencies": [ @@ -652,9 +836,31 @@ "bail@2.0.2": { "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" }, + "balanced-match@1.0.2": { + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, "base64-js@1.5.1": { "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "brace-expansion@1.1.12": { + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": [ + "balanced-match", + "concat-map" + ] + }, + "brace-expansion@2.0.2": { + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dependencies": [ + "balanced-match" + ] + }, + "braces@3.0.3": { + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": [ + "fill-range" + ] + }, "buffer-from@1.1.2": { "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, @@ -665,6 +871,9 @@ "ieee754" ] }, + "callsites@3.1.0": { + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, "camel-case@4.1.2": { "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "dependencies": [ @@ -678,6 +887,13 @@ "ccount@2.0.1": { "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" }, + "chalk@4.1.2": { + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": [ + "ansi-styles", + "supports-color" + ] + }, "chalk@5.4.1": { "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==" }, @@ -699,6 +915,15 @@ "clsx@2.1.1": { "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": [ + "color-name" + ] + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "comma-separated-tokens@2.0.3": { "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" }, @@ -708,9 +933,20 @@ "commander@2.20.3": { "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "concat-map@0.0.1": { + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, "content-type@1.0.5": { "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, + "cross-spawn@7.0.6": { + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": [ + "path-key", + "shebang-command", + "which" + ] + }, "csstype@3.1.3": { "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, @@ -741,6 +977,9 @@ "character-entities" ] }, + "deep-is@0.1.4": { + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, "defu@6.1.4": { "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==" }, @@ -766,12 +1005,95 @@ "entities@6.0.1": { "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==" }, + "escape-string-regexp@4.0.0": { + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, "escape-string-regexp@5.0.0": { "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==" }, + "eslint-scope@8.4.0": { + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dependencies": [ + "esrecurse", + "estraverse" + ] + }, + "eslint-visitor-keys@3.4.3": { + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" + }, + "eslint-visitor-keys@4.2.1": { + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" + }, + "eslint@9.32.0": { + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", + "dependencies": [ + "@eslint-community/eslint-utils", + "@eslint-community/regexpp", + "@eslint/config-array", + "@eslint/config-helpers", + "@eslint/core", + "@eslint/eslintrc", + "@eslint/js", + "@eslint/plugin-kit", + "@humanfs/node", + "@humanwhocodes/module-importer", + "@humanwhocodes/retry@0.4.3", + "@types/estree", + "@types/json-schema", + "ajv@6.12.6", + "chalk@4.1.2", + "cross-spawn", + "debug", + "escape-string-regexp@4.0.0", + "eslint-scope", + "eslint-visitor-keys@4.2.1", + "espree", + "esquery", + "esutils", + "fast-deep-equal", + "file-entry-cache", + "find-up", + "glob-parent@6.0.2", + "ignore", + "imurmurhash", + "is-glob", + "json-stable-stringify-without-jsonify", + "lodash.merge", + "minimatch@3.1.2", + "natural-compare", + "optionator" + ], + "bin": true + }, + "espree@10.4.0_acorn@8.15.0": { + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dependencies": [ + "acorn", + "acorn-jsx", + "eslint-visitor-keys@4.2.1" + ] + }, + "esquery@1.6.0": { + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dependencies": [ + "estraverse" + ] + }, + "esrecurse@4.3.0": { + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": [ + "estraverse" + ] + }, + "estraverse@5.3.0": { + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, "estree-walker@2.0.2": { "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, + "esutils@2.0.3": { + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, "event-target-shim@5.0.1": { "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, @@ -781,9 +1103,31 @@ "fast-deep-equal@3.1.3": { "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-glob@3.3.3": { + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dependencies": [ + "@nodelib/fs.stat", + "@nodelib/fs.walk", + "glob-parent@5.1.2", + "merge2", + "micromatch" + ] + }, + "fast-json-stable-stringify@2.1.0": { + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein@2.0.6": { + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, "fast-uri@3.0.6": { "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==" }, + "fastq@1.19.1": { + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dependencies": [ + "reusify" + ] + }, "fetch-blob@3.2.0": { "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "dependencies": [ @@ -791,6 +1135,32 @@ "web-streams-polyfill" ] }, + "file-entry-cache@8.0.0": { + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dependencies": [ + "flat-cache" + ] + }, + "fill-range@7.1.1": { + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": [ + "to-regex-range" + ] + }, + "find-up@5.0.0": { + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": [ + "locate-path", + "path-exists" + ] + }, + "flat-cache@4.0.1": { + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dependencies": [ + "flatted", + "keyv" + ] + }, "flatted@3.3.3": { "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" }, @@ -809,6 +1179,24 @@ "github-slugger@2.0.0": { "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" }, + "glob-parent@5.1.2": { + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": [ + "is-glob" + ] + }, + "glob-parent@6.0.2": { + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": [ + "is-glob" + ] + }, + "globals@14.0.0": { + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" + }, + "has-flag@4.0.0": { + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, "hast-util-embedded@3.0.0": { "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", "dependencies": [ @@ -1028,6 +1416,16 @@ "ieee754@1.2.1": { "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, + "ignore@5.3.2": { + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" + }, + "import-fresh@3.3.1": { + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dependencies": [ + "parent-module", + "resolve-from" + ] + }, "imurmurhash@0.1.4": { "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" }, @@ -1037,6 +1435,18 @@ "is-buffer@2.0.5": { "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": [ + "is-extglob" + ] + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, "is-obj@3.0.0": { "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==" }, @@ -1046,12 +1456,31 @@ "is-regexp@3.1.0": { "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==" }, + "isexe@2.0.0": { + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, "js-sorted-set@0.7.0": { "integrity": "sha512-NGTSMeoLNYR2BoTLhQ6w+u7Ox4PO34omb/0OBCy4gyedWeXolMGv948Ato0/Of6tfxsAjeySDymCkAj93/xkeA==" }, + "js-yaml@4.1.0": { + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": [ + "argparse" + ], + "bin": true + }, + "json-buffer@3.0.1": { + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "json-schema-traverse@0.4.1": { + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "json-schema-traverse@1.0.0": { "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, + "json-stable-stringify-without-jsonify@1.0.1": { + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, "json-stringify-deterministic@1.0.12": { "integrity": "sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g==" }, @@ -1073,6 +1502,12 @@ "just-curry-it@5.3.0": { "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==" }, + "keyv@4.5.4": { + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": [ + "json-buffer" + ] + }, "ky-universal@0.11.0_ky@0.33.3": { "integrity": "sha512-65KyweaWvk+uKKkCrfAf+xqN2/epw1IJDtlyCPxYffFCMR8u1sp2U65NtWpnozYfZxQ6IUzIlvUcw+hQ82U2Xw==", "dependencies": [ @@ -1097,6 +1532,22 @@ "leven@4.0.0": { "integrity": "sha512-puehA3YKku3osqPlNuzGDUHq8WpwXupUg1V6NXdV38G+gr+gkBwFC8g1b/+YcIvp8gnqVIus+eJCH/eGsRmJNw==" }, + "levn@0.4.1": { + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": [ + "prelude-ls", + "type-check" + ] + }, + "locate-path@6.0.0": { + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": [ + "p-locate" + ] + }, + "lodash.merge@4.6.2": { + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, "longest-streak@3.1.0": { "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==" }, @@ -1136,7 +1587,7 @@ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "dependencies": [ "@types/mdast", - "escape-string-regexp", + "escape-string-regexp@5.0.0", "unist-util-is", "unist-util-visit-parents" ] @@ -1266,6 +1717,9 @@ "module-error" ] }, + "merge2@1.4.1": { + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, "microdiff@1.5.0": { "integrity": "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==" }, @@ -1510,6 +1964,25 @@ "micromark-util-types" ] }, + "micromatch@4.0.8": { + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": [ + "braces", + "picomatch" + ] + }, + "minimatch@3.1.2": { + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": [ + "brace-expansion@1.1.12" + ] + }, + "minimatch@9.0.5": { + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": [ + "brace-expansion@2.0.2" + ] + }, "minimist@1.2.8": { "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, @@ -1527,6 +2000,9 @@ "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", "bin": true }, + "natural-compare@1.4.0": { + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, "no-case@3.0.4": { "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dependencies": [ @@ -1560,6 +2036,29 @@ "yaml" ] }, + "optionator@0.9.4": { + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dependencies": [ + "deep-is", + "fast-levenshtein", + "levn", + "prelude-ls", + "type-check", + "word-wrap" + ] + }, + "p-limit@3.1.0": { + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": [ + "yocto-queue" + ] + }, + "p-locate@5.0.0": { + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": [ + "p-limit" + ] + }, "param-case@3.0.4": { "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "dependencies": [ @@ -1567,6 +2066,12 @@ "tslib" ] }, + "parent-module@1.0.1": { + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": [ + "callsites" + ] + }, "parse5@7.3.0": { "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "dependencies": [ @@ -1580,9 +2085,18 @@ "tslib" ] }, + "path-exists@4.0.0": { + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-key@3.1.1": { + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, "picocolors@1.1.1": { "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, + "picomatch@2.3.1": { + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, "postcss@8.5.6": { "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dependencies": [ @@ -1591,6 +2105,9 @@ "source-map-js" ] }, + "prelude-ls@1.2.1": { + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + }, "pretty-bytes@6.1.1": { "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==" }, @@ -1600,12 +2117,15 @@ "property-information@7.1.0": { "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" }, + "punycode@2.3.1": { + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, "quadstore-comunica@6.2.0_asynciterator@3.9.0_quadstore@15.3.0": { "integrity": "sha512-diibvQsX9i8VHHX47a0+3W/P0oXm9LYC0fQgZ+hsEJ3wvBXrWs4AFZlSy7A+0ze1FSnHnvRtRu5/FohdtlkyAg==", "dependencies": [ "@rdfjs/types@2.0.1", "asynciterator", - "quadstore", + "quadstore@15.3.0", "sparqlalgebrajs", "sparqljs" ] @@ -1619,6 +2139,18 @@ "js-sorted-set" ] }, + "quadstore@15.4.0-beta.0": { + "integrity": "sha512-RFDcXdLxlXtqFoSGFbgc+1xmJQu9g3E/fyMFeCGaVTxftDqfkNj6BuatWCF74moAUBG/EtbhqvytJKm1IGny5w==", + "dependencies": [ + "@rdfjs/types@2.0.1", + "abstract-level", + "asynciterator", + "js-sorted-set" + ] + }, + "queue-microtask@1.2.3": { + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, "radix-vue@1.9.17_vue@3.5.18": { "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==", "dependencies": [ @@ -1785,12 +2317,37 @@ "require-from-string@2.0.2": { "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, + "resolve-from@4.0.0": { + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "reusify@1.1.0": { + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==" + }, + "run-parallel@1.2.0": { + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dependencies": [ + "queue-microtask" + ] + }, "sanitizer@0.1.3": { "integrity": "sha512-j05vL56tR90rsYqm9ZD05v6K4HI7t4yMDEvvU0x4f+IADXM9Jx1x9mzatxOs5drJq6dGhugxDW99mcPvXVLl+Q==" }, + "semver@7.7.2": { + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": true + }, "setimmediate@1.0.5": { "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, + "shebang-command@2.0.0": { + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": [ + "shebang-regex" + ] + }, + "shebang-regex@3.0.0": { + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, "source-map-js@1.2.1": { "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, @@ -1843,6 +2400,15 @@ "is-regexp" ] }, + "strip-json-comments@3.1.1": { + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "supports-color@7.2.0": { + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": [ + "has-flag" + ] + }, "tailwind-merge@2.6.0": { "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==" }, @@ -1856,6 +2422,12 @@ ], "bin": true }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": [ + "is-number" + ] + }, "trim-lines@3.0.1": { "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" }, @@ -1865,15 +2437,31 @@ "trough@2.2.0": { "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==" }, + "ts-api-utils@2.1.0_typescript@5.8.3": { + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dependencies": [ + "typescript" + ] + }, "ts-deepmerge@7.0.3": { "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==" }, "tslib@2.8.1": { "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "type-check@0.4.0": { + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": [ + "prelude-ls" + ] + }, "type-fest@4.41.0": { "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==" }, + "typescript@5.8.3": { + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "bin": true + }, "undici-types@6.21.0": { "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, @@ -1935,6 +2523,12 @@ "unist-util-visit-parents" ] }, + "uri-js@4.4.1": { + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": [ + "punycode" + ] + }, "uuid@9.0.1": { "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "bin": true @@ -1987,6 +2581,16 @@ "web-streams-polyfill@3.3.3": { "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" }, + "which@2.0.2": { + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": [ + "isexe" + ], + "bin": true + }, + "word-wrap@1.2.5": { + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" + }, "yallist@4.0.0": { "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, @@ -1994,6 +2598,9 @@ "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", "bin": true }, + "yocto-queue@0.1.0": { + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, "zod@3.24.1": { "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==" }, @@ -2356,6 +2963,13 @@ "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/flow-core/src/deps.ts b/flow-core/src/deps.ts index 195356d..12fe12a 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -13,7 +13,7 @@ export { 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 { Quadstore } from 'npm:quadstore@15.4.0-beta.0'; export { DataFactory } from 'npm:rdf-data-factory'; export { MemoryLevel } from 'npm:memory-level'; diff --git a/flow-core/src/utils/quadstore-utils.ts b/flow-core/src/utils/quadstore-utils.ts index 6385a2a..99e2277 100644 --- a/flow-core/src/utils/quadstore-utils.ts +++ b/flow-core/src/utils/quadstore-utils.ts @@ -30,25 +30,26 @@ export async function isGraphEmpty( } -// Clears all quads in the specified named graph. Returns the number of quads deleted. +// Clears all quads in the specified named graph. export async function clearGraph( graph: RDF.NamedNode, { store }: { store: Quadstore } = defaultQuadstoreBundle -): Promise { +): Promise { const matchStream = store.match(undefined, undefined, undefined, graph); - const count = await countQuadsInStream(matchStream); - - if (count > 0) { - const delStream = store.match(undefined, undefined, undefined, graph); - await store.delStream(delStream as any); - } - - return count; + //const count = await countQuadsInStream(matchStream); + //console.log(`Number of quads before delStream in graph ${graph.value}: ${count}`); + await store.delStream(matchStream); + const matchStream2 = store.match(undefined, undefined, undefined, graph); + const count2 = await countQuadsInStream(matchStream2 + + ); + console.log(`Number of quads in graph ${graph.value}: ${count2}`); } + // Copies all quads from source graph to target graph. export async function copyGraph( @@ -61,7 +62,7 @@ export async function copyGraph( ): Promise { const stream = store.match(undefined, undefined, undefined, sourceGraph); const quads = []; - for await (const q of stream as any) { + for await (const q of stream) { const newQuad = df.quad(q.subject, q.predicate, q.object, targetGraph); quads.push(newQuad); } diff --git a/flow-core/src/utils/quadstore/quadstore-factory.ts b/flow-core/src/utils/quadstore/quadstore-factory.ts index b47d874..239c372 100644 --- a/flow-core/src/utils/quadstore/quadstore-factory.ts +++ b/flow-core/src/utils/quadstore/quadstore-factory.ts @@ -11,7 +11,15 @@ export async function createQuadstoreBundle(): Promise { const store = new Quadstore({ backend, dataFactory: df }); const engine = new Engine(store); - await store.open(); + + 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/tests/unit/quadstore-utils.test.ts b/flow-core/tests/unit/quadstore-utils.test.ts index 3b8292f..ed30661 100644 --- a/flow-core/tests/unit/quadstore-utils.test.ts +++ b/flow-core/tests/unit/quadstore-utils.test.ts @@ -43,22 +43,33 @@ Deno.test('isGraphEmpty returns true for empty graph and false otherwise', async await clearGraph(graph); }); -Deno.test('clearGraph deletes all quads and returns count', async () => { + +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 count = await clearGraph(graph, testQuadstoreBundle); - assertEquals(count, 1); + 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); - const empty = await isGraphEmpty(graph, testQuadstoreBundle); - assertEquals(empty, true); }); Deno.test('copyGraph copies quads from source to target graph', async () => { @@ -69,7 +80,6 @@ Deno.test('copyGraph copies quads from source to target graph', async () => { const sourceGraph = df.namedNode('urn:sourceGraph'); const targetGraph = df.namedNode('urn:targetGraph'); - await clearGraph(sourceGraph, testQuadstoreBundle); await clearGraph(targetGraph, testQuadstoreBundle); const quad = df.quad(df.namedNode('s'), df.namedNode('p'), df.literal('o'), sourceGraph); diff --git a/flow-service/deno.lock b/flow-service/deno.lock index 176ab73..026ad18 100644 --- a/flow-service/deno.lock +++ b/flow-service/deno.lock @@ -23,6 +23,7 @@ "npm:memory-level@*": "3.1.0", "npm:quadstore-comunica@*": "6.2.0_asynciterator@3.9.0_quadstore@15.3.0", "npm:quadstore@*": "15.3.0", + "npm:quadstore@15.4.0-beta.0": "15.4.0-beta.0", "npm:rdf-data-factory@*": "2.0.2", "npm:zod@^3.22.0": "3.25.76" }, @@ -1623,7 +1624,7 @@ "dependencies": [ "@rdfjs/types@2.0.1", "asynciterator", - "quadstore", + "quadstore@15.3.0", "sparqlalgebrajs", "sparqljs" ] @@ -1637,6 +1638,15 @@ "js-sorted-set" ] }, + "quadstore@15.4.0-beta.0": { + "integrity": "sha512-RFDcXdLxlXtqFoSGFbgc+1xmJQu9g3E/fyMFeCGaVTxftDqfkNj6BuatWCF74moAUBG/EtbhqvytJKm1IGny5w==", + "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": [ diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index 64627e0..ea3183e 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -35,17 +35,14 @@ export async function mergeServiceConfigGraphs( } = defaultQuadstoreBundle ): Promise { // Clear merged graph - const count = await clearGraph(df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig)); - if (count > 0) { - console.info(`Deleted ${count} quads from mergedServiceConfig graph`); - } + await clearGraph(df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig)); // Copy input service config quads await copyGraph(df.namedNode(CONFIG_GRAPH_NAMES.inputServiceConfig), df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig)); // Copy platform service defaults quads if not overridden const platformQuads = store.match(undefined, undefined, undefined, df.namedNode(CONFIG_GRAPH_NAMES.platformServiceDefaults)); - for await (const q of platformQuads as any) { + for await (const q of platformQuads) { const exists = await store.countQuads(q.subject, q.predicate, undefined, df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig)); if (exists === 0) { await store.put(df.quad(q.subject, q.predicate, q.object, df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig))); diff --git a/flow-service/src/config/resolution/service-config-validator.ts b/flow-service/src/config/resolution/service-config-validator.ts index 4f2ed4d..c776959 100644 --- a/flow-service/src/config/resolution/service-config-validator.ts +++ b/flow-service/src/config/resolution/service-config-validator.ts @@ -14,7 +14,7 @@ export async function validateServiceConfig(): Promise { // Validate port const portQuads = store.match(undefined, df.namedNode('https://semantic-flow.github.io/ontology/flow-service/port'), undefined, graph); - for await (const quad of portQuads as any) { + for await (const quad of portQuads) { 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.`); @@ -24,7 +24,7 @@ export async function validateServiceConfig(): Promise { // Validate host presence const hostQuads = store.match(undefined, df.namedNode('https://semantic-flow.github.io/ontology/flow-service/host'), undefined, graph); let hostFound = false; - for await (const _ of hostQuads as any) { + for await (const _ of hostQuads) { hostFound = true; break; } diff --git a/flow-service/src/utils/errors.ts b/flow-service/src/utils/errors.ts index 9e7ac8b..42f49aa 100644 --- a/flow-service/src/utils/errors.ts +++ b/flow-service/src/utils/errors.ts @@ -30,7 +30,7 @@ export class ConfigurationError extends FlowServiceError { } } -// Enhanced error handler with automatic Sentry integration +// Enhanced error handler export function handleServiceError( error: unknown, context?: string, diff --git a/package-lock.json b/package-lock.json index 7da4cc9..18fb136 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,9 @@ "requires": true, "packages": { "": { + "dependencies": { + "quadstore": "^15.4.0-beta.0" + }, "devDependencies": { "@typescript-eslint/parser": "^8.38.0", "eslint": "^9.32.0" @@ -254,6 +257,15 @@ "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", @@ -268,6 +280,15 @@ "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", @@ -437,6 +458,23 @@ "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", @@ -500,6 +538,12 @@ "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", @@ -507,6 +551,26 @@ "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", @@ -531,6 +595,30 @@ "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", @@ -954,6 +1042,26 @@ "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", @@ -991,6 +1099,29 @@ "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", @@ -1031,6 +1162,12 @@ "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", @@ -1075,6 +1212,28 @@ "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", @@ -1112,6 +1271,15 @@ "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", @@ -1149,6 +1317,15 @@ "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", @@ -1279,6 +1456,21 @@ "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", @@ -1461,6 +1653,12 @@ "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", diff --git a/package.json b/package.json index 493f0d3..1fada90 100644 --- a/package.json +++ b/package.json @@ -2,5 +2,8 @@ "devDependencies": { "@typescript-eslint/parser": "^8.38.0", "eslint": "^9.32.0" + }, + "dependencies": { + "quadstore": "^15.4.0-beta.0" } } From 9d7a11ee32c3a34b43d3f063914a732d86a131a8 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Sun, 3 Aug 2025 08:57:13 -0700 Subject: [PATCH 15/20] Refactor logging implementation and enhance error handling - Updated metadata-composer.ts to use double quotes for consistency and modified the attributedTo field to use a hardcoded URL. - Refactored error handling in errors.ts to improve structured logging and context management. - Removed the logger.ts file and replaced it with a new service-logger.ts that provides service-specific logging configuration and context. - Updated mesh-utils.ts to use the new service logger and improved logging context for mesh registration. - Adjusted startup-logger.ts to ensure type safety for enabled services. - Modified various test files to import from the new service-logger.ts and updated logging calls to align with the new structured logging format. --- .ai-guidance.md | 4 + deno.lock | 2831 +---------------- flow-core/docs/error-handling-usage.md | 422 +++ flow-core/src/deps.ts | 5 +- flow-core/src/utils/logger/error-handlers.ts | 466 +++ flow-core/src/utils/logger/file-logger.ts | 201 ++ flow-core/src/utils/logger/formatters.ts | 255 ++ flow-core/src/utils/logger/index.ts | 127 + flow-core/src/utils/logger/sentry-logger.ts | 303 ++ .../src/utils/logger/structured-logger.ts | 349 ++ flow-core/src/utils/logger/types.ts | 232 ++ flow-core/src/utils/quadstore-utils.ts | 16 +- .../src/utils/quadstore/quadstore-factory.ts | 1 + .../integration/logger/error-handlers.test.ts | 186 ++ .../integration/logger/formatters.test.ts | 147 + .../tests/integration/logger/logger.test.ts | 94 + flow-service/deno.json | 3 +- flow-service/deno.lock | 28 +- flow-service/logs/test-pretty-direct.log | 8 +- flow-service/main.ts | 56 +- flow-service/src/config/config-types.ts | 14 +- flow-service/src/config/index.ts | 14 +- .../src/config/loaders/jsonld-loader.ts | 2 +- .../src/config/node-config.default.jsonld | 3 +- .../resolution/service-config-accessor.ts | 14 +- .../resolution/service-config-resolver.ts | 25 +- flow-service/src/routes/health.ts | 16 +- flow-service/src/routes/meshes.ts | 27 +- flow-service/src/routes/weave.ts | 21 +- .../src/services/metadata-composer.ts | 84 +- flow-service/src/utils/errors.ts | 37 +- flow-service/src/utils/logger.ts | 668 ---- flow-service/src/utils/mesh-utils.ts | 35 +- flow-service/src/utils/service-logger.ts | 192 ++ flow-service/src/utils/startup-logger.ts | 2 +- .../logging/formatting-direct.test.ts | 4 +- .../logging/handle-caught-error.test.ts | 2 +- .../tests/integration/logging/logger.test.ts | 68 +- .../logging/pretty-format-simple.test.ts | 18 +- .../integration/logging/sentry-init.test.ts | 2 +- .../logging/sentry-logging.test.ts | 2 +- 41 files changed, 3326 insertions(+), 3658 deletions(-) create mode 100644 flow-core/docs/error-handling-usage.md create mode 100644 flow-core/src/utils/logger/error-handlers.ts create mode 100644 flow-core/src/utils/logger/file-logger.ts create mode 100644 flow-core/src/utils/logger/formatters.ts create mode 100644 flow-core/src/utils/logger/index.ts create mode 100644 flow-core/src/utils/logger/sentry-logger.ts create mode 100644 flow-core/src/utils/logger/structured-logger.ts create mode 100644 flow-core/src/utils/logger/types.ts create mode 100644 flow-core/tests/integration/logger/error-handlers.test.ts create mode 100644 flow-core/tests/integration/logger/formatters.test.ts create mode 100644 flow-core/tests/integration/logger/logger.test.ts delete mode 100644 flow-service/src/utils/logger.ts create mode 100644 flow-service/src/utils/service-logger.ts diff --git a/.ai-guidance.md b/.ai-guidance.md index c7f2d9a..2947480 100644 --- a/.ai-guidance.md +++ b/.ai-guidance.md @@ -18,6 +18,10 @@ suggestions for improvement whenever possible. 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/deno.lock b/deno.lock index f6bd454..8703db5 100644 --- a/deno.lock +++ b/deno.lock @@ -1,2759 +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:@rdfjs/data-model@*": "2.1.1", - "npm:@rdfjs/types@*": "2.0.1", - "npm:@scalar/hono-api-reference@*": "0.9.12_hono@4.8.5", - "npm:@scalar/openapi-to-markdown@*": "0.2.23", - "npm:@scalar/openapi-to-markdown@~0.2.23": "0.2.23", - "npm:@sentry/deno@*": "9.40.0", - "npm:@types/jsonld@*": "1.5.15", - "npm:@types/node@*": "22.15.15", - "npm:@typescript-eslint/parser@^8.38.0": "8.38.0_eslint@9.32.0_typescript@5.8.3", - "npm:abstract-level@*": "3.1.0", - "npm:eslint@^9.32.0": "9.32.0", - "npm:jsonld@*": "8.3.3", - "npm:memory-level@*": "3.1.0", - "npm:quadstore-comunica@*": "6.2.0_asynciterator@3.9.0_quadstore@15.3.0", - "npm:quadstore@*": "15.3.0", - "npm:quadstore@^15.4.0-beta.0": "15.4.0-beta.0", - "npm:rdf-data-factory@*": "2.0.2" - }, - "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" - ] - }, - "@digitalbazaar/http-client@3.4.1_ky@0.33.3": { - "integrity": "sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==", - "dependencies": [ - "ky", - "ky-universal", - "undici" - ] - }, - "@eslint-community/eslint-utils@4.7.0_eslint@9.32.0": { - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dependencies": [ - "eslint", - "eslint-visitor-keys@3.4.3" - ] - }, - "@eslint-community/regexpp@4.12.1": { - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==" - }, - "@eslint/config-array@0.21.0": { - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", - "dependencies": [ - "@eslint/object-schema", - "debug", - "minimatch@3.1.2" - ] - }, - "@eslint/config-helpers@0.3.0": { - "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==" - }, - "@eslint/core@0.15.1": { - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", - "dependencies": [ - "@types/json-schema" - ] - }, - "@eslint/eslintrc@3.3.1": { - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dependencies": [ - "ajv@6.12.6", - "debug", - "espree", - "globals", - "ignore", - "import-fresh", - "js-yaml", - "minimatch@3.1.2", - "strip-json-comments" - ] - }, - "@eslint/js@9.32.0": { - "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==" - }, - "@eslint/object-schema@2.1.6": { - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==" - }, - "@eslint/plugin-kit@0.3.4": { - "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", - "dependencies": [ - "@eslint/core", - "levn" - ] - }, - "@fastify/busboy@2.1.1": { - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==" - }, - "@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" - ] - }, - "@humanfs/core@0.19.1": { - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==" - }, - "@humanfs/node@0.16.6": { - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dependencies": [ - "@humanfs/core", - "@humanwhocodes/retry@0.3.1" - ] - }, - "@humanwhocodes/module-importer@1.0.1": { - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" - }, - "@humanwhocodes/retry@0.3.1": { - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==" - }, - "@humanwhocodes/retry@0.4.3": { - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==" - }, - "@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" - ] - }, - "@nodelib/fs.scandir@2.1.5": { - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": [ - "@nodelib/fs.stat", - "run-parallel" - ] - }, - "@nodelib/fs.stat@2.0.5": { - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" - }, - "@nodelib/fs.walk@1.2.8": { - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": [ - "@nodelib/fs.scandir", - "fastq" - ] - }, - "@phosphor-icons/core@2.1.1": { - "integrity": "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==" - }, - "@rdfjs/data-model@2.1.1": { - "integrity": "sha512-6mcOI4DjIPS6MOZw23H8oAdujHCk5gippVNQ7mKwliYTvTNh+uqRM91B9OLqhoAoNcQ3t49Dx2ooIMRG9/6ooA==", - "bin": true - }, - "@rdfjs/types@1.1.2": { - "integrity": "sha512-wqpOJK1QCbmsGNtyzYnojPU8gRDPid2JO0Q0kMtb4j65xhCK880cnKAfEOwC+dX85VJcCByQx5zOwyyfCjDJsg==", - "dependencies": [ - "@types/node@22.15.15" - ] - }, - "@rdfjs/types@2.0.1": { - "integrity": "sha512-uyAzpugX7KekAXAHq26m3JlUIZJOC0uSBhpnefGV5i15bevDyyejoB7I+9MKeUrzXD8OOUI3+4FeV1wwQr5ihA==", - "dependencies": [ - "@types/node@22.15.15" - ] - }, - "@scalar/code-highlight@0.1.8": { - "integrity": "sha512-SH7gqBVGotf9Hw2srAKd/s854Wjf5lpR4FgAf/l3AlCYC5QXJ4hbj+fk1/3tvzeRFm+wvS6/ah4FuiIzvDPOug==", - "dependencies": [ - "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@22.16.5", - "chalk@5.4.1", - "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@8.17.1", - "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/estree@1.0.8": { - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" - }, - "@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/json-schema@7.0.15": { - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" - }, - "@types/jsonld@1.5.15": { - "integrity": "sha512-PlAFPZjL+AuGYmwlqwKEL0IMP8M8RexH0NIPGfCVWSQ041H2rR/8OlyZSD7KsCVoN8vCfWdtWDBxX8yBVP+xow==" - }, - "@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.15.15": { - "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==", - "dependencies": [ - "undici-types" - ] - }, - "@types/node@22.16.5": { - "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==", - "dependencies": [ - "undici-types" - ] - }, - "@types/sparqljs@3.1.12": { - "integrity": "sha512-zg/sdKKtYI0845wKPSuSgunyU1o/+7tRzMw85lHsf4p/0UbA6+65MXAyEtv1nkaqSqrq/bXm7+bqXas+Xo5dpQ==", - "dependencies": [ - "@rdfjs/types@2.0.1" - ] - }, - "@types/unist@3.0.3": { - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" - }, - "@types/web-bluetooth@0.0.20": { - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" - }, - "@typescript-eslint/parser@8.38.0_eslint@9.32.0_typescript@5.8.3": { - "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", - "dependencies": [ - "@typescript-eslint/scope-manager", - "@typescript-eslint/types", - "@typescript-eslint/typescript-estree", - "@typescript-eslint/visitor-keys", - "debug", - "eslint", - "typescript" - ] - }, - "@typescript-eslint/project-service@8.38.0_typescript@5.8.3": { - "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", - "dependencies": [ - "@typescript-eslint/tsconfig-utils", - "@typescript-eslint/types", - "debug", - "typescript" - ] - }, - "@typescript-eslint/scope-manager@8.38.0": { - "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", - "dependencies": [ - "@typescript-eslint/types", - "@typescript-eslint/visitor-keys" - ] - }, - "@typescript-eslint/tsconfig-utils@8.38.0_typescript@5.8.3": { - "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", - "dependencies": [ - "typescript" - ] - }, - "@typescript-eslint/types@8.38.0": { - "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==" - }, - "@typescript-eslint/typescript-estree@8.38.0_typescript@5.8.3": { - "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", - "dependencies": [ - "@typescript-eslint/project-service", - "@typescript-eslint/tsconfig-utils", - "@typescript-eslint/types", - "@typescript-eslint/visitor-keys", - "debug", - "fast-glob", - "is-glob", - "minimatch@9.0.5", - "semver", - "ts-api-utils", - "typescript" - ] - }, - "@typescript-eslint/visitor-keys@8.38.0": { - "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", - "dependencies": [ - "@typescript-eslint/types", - "eslint-visitor-keys@4.2.1" - ] - }, - "@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" - ] - }, - "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-jsx@5.3.2_acorn@8.15.0": { - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dependencies": [ - "acorn" - ] - }, - "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@8.17.1" - ], - "optionalPeers": [ - "ajv@8.17.1" - ] - }, - "ajv-formats@3.0.1_ajv@8.17.1": { - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dependencies": [ - "ajv@8.17.1" - ], - "optionalPeers": [ - "ajv@8.17.1" - ] - }, - "ajv@6.12.6": { - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": [ - "fast-deep-equal", - "fast-json-stable-stringify", - "json-schema-traverse@0.4.1", - "uri-js" - ] - }, - "ajv@8.17.1": { - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dependencies": [ - "fast-deep-equal", - "fast-uri", - "json-schema-traverse@1.0.0", - "require-from-string" - ] - }, - "ansi-styles@4.3.0": { - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": [ - "color-convert" - ] - }, - "argparse@2.0.1": { - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "aria-hidden@1.2.6": { - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "dependencies": [ - "tslib" - ] - }, - "asynciterator@3.9.0": { - "integrity": "sha512-bwLLTAnoE6Ap6XdjK/j8vDk2Vi9p3ojk0PFwM0SwktAG1k8pfRJF9ng+mmkaRFKdZCQQlOxcWnvOmX2NQ1HV0g==" - }, - "bail@2.0.2": { - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" - }, - "balanced-match@1.0.2": { - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base64-js@1.5.1": { - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "brace-expansion@1.1.12": { - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dependencies": [ - "balanced-match", - "concat-map" - ] - }, - "brace-expansion@2.0.2": { - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dependencies": [ - "balanced-match" - ] - }, - "braces@3.0.3": { - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dependencies": [ - "fill-range" - ] - }, - "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" - ] - }, - "callsites@3.1.0": { - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "camel-case@4.1.2": { - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dependencies": [ - "pascal-case", - "tslib" - ] - }, - "canonicalize@1.0.8": { - "integrity": "sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==" - }, - "ccount@2.0.1": { - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" - }, - "chalk@4.1.2": { - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": [ - "ansi-styles", - "supports-color" - ] - }, - "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==" - }, - "color-convert@2.0.1": { - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": [ - "color-name" - ] - }, - "color-name@1.1.4": { - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "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==" - }, - "concat-map@0.0.1": { - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "content-type@1.0.5": { - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "cross-spawn@7.0.6": { - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dependencies": [ - "path-key", - "shebang-command", - "which" - ] - }, - "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==" - }, - "data-uri-to-buffer@4.0.1": { - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" - }, - "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" - ] - }, - "deep-is@0.1.4": { - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "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@4.0.0": { - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - }, - "escape-string-regexp@5.0.0": { - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==" - }, - "eslint-scope@8.4.0": { - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dependencies": [ - "esrecurse", - "estraverse" - ] - }, - "eslint-visitor-keys@3.4.3": { - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" - }, - "eslint-visitor-keys@4.2.1": { - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" - }, - "eslint@9.32.0": { - "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", - "dependencies": [ - "@eslint-community/eslint-utils", - "@eslint-community/regexpp", - "@eslint/config-array", - "@eslint/config-helpers", - "@eslint/core", - "@eslint/eslintrc", - "@eslint/js", - "@eslint/plugin-kit", - "@humanfs/node", - "@humanwhocodes/module-importer", - "@humanwhocodes/retry@0.4.3", - "@types/estree", - "@types/json-schema", - "ajv@6.12.6", - "chalk@4.1.2", - "cross-spawn", - "debug", - "escape-string-regexp@4.0.0", - "eslint-scope", - "eslint-visitor-keys@4.2.1", - "espree", - "esquery", - "esutils", - "fast-deep-equal", - "file-entry-cache", - "find-up", - "glob-parent@6.0.2", - "ignore", - "imurmurhash", - "is-glob", - "json-stable-stringify-without-jsonify", - "lodash.merge", - "minimatch@3.1.2", - "natural-compare", - "optionator" - ], - "bin": true - }, - "espree@10.4.0_acorn@8.15.0": { - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dependencies": [ - "acorn", - "acorn-jsx", - "eslint-visitor-keys@4.2.1" - ] - }, - "esquery@1.6.0": { - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dependencies": [ - "estraverse" - ] - }, - "esrecurse@4.3.0": { - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dependencies": [ - "estraverse" - ] - }, - "estraverse@5.3.0": { - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - }, - "estree-walker@2.0.2": { - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, - "esutils@2.0.3": { - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "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==" - }, - "fast-deep-equal@3.1.3": { - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-glob@3.3.3": { - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dependencies": [ - "@nodelib/fs.stat", - "@nodelib/fs.walk", - "glob-parent@5.1.2", - "merge2", - "micromatch" - ] - }, - "fast-json-stable-stringify@2.1.0": { - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fast-levenshtein@2.0.6": { - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" - }, - "fast-uri@3.0.6": { - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==" - }, - "fastq@1.19.1": { - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dependencies": [ - "reusify" - ] - }, - "fetch-blob@3.2.0": { - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dependencies": [ - "node-domexception", - "web-streams-polyfill" - ] - }, - "file-entry-cache@8.0.0": { - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dependencies": [ - "flat-cache" - ] - }, - "fill-range@7.1.1": { - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dependencies": [ - "to-regex-range" - ] - }, - "find-up@5.0.0": { - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": [ - "locate-path", - "path-exists" - ] - }, - "flat-cache@4.0.1": { - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dependencies": [ - "flatted", - "keyv" - ] - }, - "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==" - }, - "github-slugger@2.0.0": { - "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" - }, - "glob-parent@5.1.2": { - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": [ - "is-glob" - ] - }, - "glob-parent@6.0.2": { - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": [ - "is-glob" - ] - }, - "globals@14.0.0": { - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" - }, - "has-flag@4.0.0": { - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "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==" - }, - "ieee754@1.2.1": { - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore@5.3.2": { - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" - }, - "import-fresh@3.3.1": { - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dependencies": [ - "parent-module", - "resolve-from" - ] - }, - "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-extglob@2.1.1": { - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" - }, - "is-glob@4.0.3": { - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": [ - "is-extglob" - ] - }, - "is-number@7.0.0": { - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "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==" - }, - "isexe@2.0.0": { - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "js-sorted-set@0.7.0": { - "integrity": "sha512-NGTSMeoLNYR2BoTLhQ6w+u7Ox4PO34omb/0OBCy4gyedWeXolMGv948Ato0/Of6tfxsAjeySDymCkAj93/xkeA==" - }, - "js-yaml@4.1.0": { - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": [ - "argparse" - ], - "bin": true - }, - "json-buffer@3.0.1": { - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "json-schema-traverse@0.4.1": { - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-schema-traverse@1.0.0": { - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "json-stable-stringify-without-jsonify@1.0.1": { - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" - }, - "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==" - }, - "just-clone@6.2.0": { - "integrity": "sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==" - }, - "just-curry-it@5.3.0": { - "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==" - }, - "keyv@4.5.4": { - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dependencies": [ - "json-buffer" - ] - }, - "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==" - }, - "levn@0.4.1": { - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dependencies": [ - "prelude-ls", - "type-check" - ] - }, - "locate-path@6.0.0": { - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": [ - "p-locate" - ] - }, - "lodash.merge@4.6.2": { - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "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" - ] - }, - "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": [ - "@jridgewell/sourcemap-codec" - ] - }, - "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": [ - "@types/mdast", - "escape-string-regexp@5.0.0", - "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" - ] - }, - "memory-level@3.1.0": { - "integrity": "sha512-mTqFVi5iReKcjue/pag0OY4VNU7dlagCyjjPwWGierpk1Bpl9WjOxgXIswymPW3Q9bj3Foay+Z16mPGnKzvTkQ==", - "dependencies": [ - "abstract-level", - "functional-red-black-tree", - "module-error" - ] - }, - "merge2@1.4.1": { - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" - }, - "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" - ] - }, - "micromatch@4.0.8": { - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dependencies": [ - "braces", - "picomatch" - ] - }, - "minimatch@3.1.2": { - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": [ - "brace-expansion@1.1.12" - ] - }, - "minimatch@9.0.5": { - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dependencies": [ - "brace-expansion@2.0.2" - ] - }, - "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==" - }, - "nanoid@3.3.11": { - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "bin": true - }, - "nanoid@5.1.5": { - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "bin": true - }, - "natural-compare@1.4.0": { - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" - }, - "no-case@3.0.4": { - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dependencies": [ - "lower-case", - "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": [ - "dank-each", - "dank-map", - "sanitizer" - ] - }, - "openapi3-ts@4.5.0": { - "integrity": "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==", - "dependencies": [ - "yaml" - ] - }, - "optionator@0.9.4": { - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dependencies": [ - "deep-is", - "fast-levenshtein", - "levn", - "prelude-ls", - "type-check", - "word-wrap" - ] - }, - "p-limit@3.1.0": { - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": [ - "yocto-queue" - ] - }, - "p-locate@5.0.0": { - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": [ - "p-limit" - ] - }, - "param-case@3.0.4": { - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dependencies": [ - "dot-case", - "tslib" - ] - }, - "parent-module@1.0.1": { - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": [ - "callsites" - ] - }, - "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" - ] - }, - "path-exists@4.0.0": { - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "path-key@3.1.1": { - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" - }, - "picocolors@1.1.1": { - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "picomatch@2.3.1": { - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" - }, - "postcss@8.5.6": { - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dependencies": [ - "nanoid@3.3.11", - "picocolors", - "source-map-js" - ] - }, - "prelude-ls@1.2.1": { - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" - }, - "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==" - }, - "punycode@2.3.1": { - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" - }, - "quadstore-comunica@6.2.0_asynciterator@3.9.0_quadstore@15.3.0": { - "integrity": "sha512-diibvQsX9i8VHHX47a0+3W/P0oXm9LYC0fQgZ+hsEJ3wvBXrWs4AFZlSy7A+0ze1FSnHnvRtRu5/FohdtlkyAg==", - "dependencies": [ - "@rdfjs/types@2.0.1", - "asynciterator", - "quadstore@15.3.0", - "sparqlalgebrajs", - "sparqljs" - ] - }, - "quadstore@15.3.0": { - "integrity": "sha512-KeFNurvPg61nO/WgdFYxbO/Sj80ne1gmzMGnWI0ukIXabdLggZ7i9TERRzPoyLHiUJP+Kkc1J2lZ+wl7n8hNBA==", - "dependencies": [ - "@rdfjs/types@2.0.1", - "abstract-level", - "asynciterator", - "js-sorted-set" - ] - }, - "quadstore@15.4.0-beta.0": { - "integrity": "sha512-RFDcXdLxlXtqFoSGFbgc+1xmJQu9g3E/fyMFeCGaVTxftDqfkNj6BuatWCF74moAUBG/EtbhqvytJKm1IGny5w==", - "dependencies": [ - "@rdfjs/types@2.0.1", - "abstract-level", - "asynciterator", - "js-sorted-set" - ] - }, - "queue-microtask@1.2.3": { - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "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" - ] - }, - "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": [ - "@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==" - }, - "resolve-from@4.0.0": { - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "reusify@1.1.0": { - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==" - }, - "run-parallel@1.2.0": { - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dependencies": [ - "queue-microtask" - ] - }, - "sanitizer@0.1.3": { - "integrity": "sha512-j05vL56tR90rsYqm9ZD05v6K4HI7t4yMDEvvU0x4f+IADXM9Jx1x9mzatxOs5drJq6dGhugxDW99mcPvXVLl+Q==" - }, - "semver@7.7.2": { - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "bin": true - }, - "setimmediate@1.0.5": { - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, - "shebang-command@2.0.0": { - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": [ - "shebang-regex" - ] - }, - "shebang-regex@3.0.0": { - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - }, - "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==" - }, - "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": [ - "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" - ] - }, - "strip-json-comments@3.1.1": { - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" - }, - "supports-color@7.2.0": { - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": [ - "has-flag" - ] - }, - "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 - }, - "to-regex-range@5.0.1": { - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": [ - "is-number" - ] - }, - "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-api-utils@2.1.0_typescript@5.8.3": { - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dependencies": [ - "typescript" - ] - }, - "ts-deepmerge@7.0.3": { - "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==" - }, - "tslib@2.8.1": { - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "type-check@0.4.0": { - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dependencies": [ - "prelude-ls" - ] - }, - "type-fest@4.41.0": { - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==" - }, - "typescript@5.8.3": { - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "bin": true - }, - "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": [ - "@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" - ] - }, - "uri-js@4.4.1": { - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": [ - "punycode" - ] - }, - "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==" - }, - "web-streams-polyfill@3.3.3": { - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" - }, - "which@2.0.2": { - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": [ - "isexe" - ], - "bin": true - }, - "word-wrap@1.2.5": { - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" - }, - "yallist@4.0.0": { - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yaml@2.8.0": { - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "bin": true - }, - "yocto-queue@0.1.0": { - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" - }, - "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", @@ -2880,83 +127,7 @@ "https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7", "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", - "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", - "https://deno.land/std@0.224.0/testing/mock.ts": "a963181c2860b6ba3eb60e08b62c164d33cf5da7cd445893499b2efda20074db", - "https://deno.land/x/free_port@v1.2.0/mod.ts": "512646732aaea41fbfd1f210f3ae82660f38251777d189d290da331d0235a58e", - "https://deno.land/x/opine@2.1.2/src/methods.ts": "0481daecc6068d24e9e5391818baddf555ab803d39a465dcd259161f8bd8ee49", - "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/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c" }, "workspace": { "dependencies": [ 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 12fe12a..098869f 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -13,7 +13,7 @@ export { 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@15.4.0-beta.0'; +export { Quadstore } from 'npm:quadstore'; export { DataFactory } from 'npm:rdf-data-factory'; export { MemoryLevel } from 'npm:memory-level'; @@ -35,3 +35,6 @@ export type { //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/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-utils.ts b/flow-core/src/utils/quadstore-utils.ts index 99e2277..d15400e 100644 --- a/flow-core/src/utils/quadstore-utils.ts +++ b/flow-core/src/utils/quadstore-utils.ts @@ -2,7 +2,7 @@ import { NodeObject, Quadstore, DataFactory } from '../deps.ts'; import { RDF } from '../deps.ts'; import { jsonld } from '../deps.ts'; import { defaultQuadstoreBundle } from '../../../flow-service/src/quadstore-default-bundle.ts'; -import { jsonldToQuads } from './rdfjs-utils.ts' +import { jsonldToQuads } from './rdfjs-utils.ts'; import type { QuadstoreBundle } from '../types.ts'; export function countQuadsInStream(stream: RDF.Stream): Promise { @@ -37,16 +37,16 @@ export async function clearGraph( { store }: { store: Quadstore } = defaultQuadstoreBundle -): Promise { +): 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}`); - await store.delStream(matchStream); + 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); - const count2 = await countQuadsInStream(matchStream2 - - ); + 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; } diff --git a/flow-core/src/utils/quadstore/quadstore-factory.ts b/flow-core/src/utils/quadstore/quadstore-factory.ts index 239c372..b83c8f0 100644 --- a/flow-core/src/utils/quadstore/quadstore-factory.ts +++ b/flow-core/src/utils/quadstore/quadstore-factory.ts @@ -1,6 +1,7 @@ // 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. 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-service/deno.json b/flow-service/deno.json index 7103d2d..ff2e7c0 100644 --- a/flow-service/deno.json +++ b/flow-service/deno.json @@ -5,7 +5,8 @@ "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,./logs --env-file main.ts", diff --git a/flow-service/deno.lock b/flow-service/deno.lock index 026ad18..4ea1718 100644 --- a/flow-service/deno.lock +++ b/flow-service/deno.lock @@ -16,14 +16,12 @@ "npm:@sentry/deno@*": "8.55.0", "npm:@sentry/deno@8": "8.55.0", "npm:@types/jsonld@*": "1.5.15", - "npm:@types/node@*": "22.15.15", "npm:abstract-level@*": "3.1.0", "npm:hono@*": "4.8.4", "npm:jsonld@*": "8.3.3", "npm:memory-level@*": "3.1.0", - "npm:quadstore-comunica@*": "6.2.0_asynciterator@3.9.0_quadstore@15.3.0", - "npm:quadstore@*": "15.3.0", - "npm:quadstore@15.4.0-beta.0": "15.4.0-beta.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" }, @@ -1619,27 +1617,18 @@ "property-information@7.1.0": { "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==" }, - "quadstore-comunica@6.2.0_asynciterator@3.9.0_quadstore@15.3.0": { - "integrity": "sha512-diibvQsX9i8VHHX47a0+3W/P0oXm9LYC0fQgZ+hsEJ3wvBXrWs4AFZlSy7A+0ze1FSnHnvRtRu5/FohdtlkyAg==", + "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@15.3.0", + "quadstore", "sparqlalgebrajs", "sparqljs" ] }, - "quadstore@15.3.0": { - "integrity": "sha512-KeFNurvPg61nO/WgdFYxbO/Sj80ne1gmzMGnWI0ukIXabdLggZ7i9TERRzPoyLHiUJP+Kkc1J2lZ+wl7n8hNBA==", - "dependencies": [ - "@rdfjs/types@2.0.1", - "abstract-level", - "asynciterator", - "js-sorted-set" - ] - }, - "quadstore@15.4.0-beta.0": { - "integrity": "sha512-RFDcXdLxlXtqFoSGFbgc+1xmJQu9g3E/fyMFeCGaVTxftDqfkNj6BuatWCF74moAUBG/EtbhqvytJKm1IGny5w==", + "quadstore@15.4.0": { + "integrity": "sha512-YlIksV9r7GTMfuawA1vqCEqtd88rX9SnYz+HAqDSdbGDSogyVTnOvJKe2qisy2FWuXrc3PpbKLDEOjJ/wEpgtA==", "dependencies": [ "@rdfjs/types@2.0.1", "abstract-level", @@ -2545,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/logs/test-pretty-direct.log b/flow-service/logs/test-pretty-direct.log index 74deccb..b47f4da 100644 --- a/flow-service/logs/test-pretty-direct.log +++ b/flow-service/logs/test-pretty-direct.log @@ -1,4 +1,4 @@ -[2025-08-02T05:45:58.391Z] INFO File logging test started (op=startup, [config-resolver]) -[2025-08-02T05:45:58.393Z] WARN Configuration override detected (op=config-resolve) -[2025-08-02T05:45:58.393Z] ERROR Database connection failed (op=startup, 5000ms) -[2025-08-02T05:45:58.393Z] DEBUG Processing mesh nodes (op=scan, mesh=/data/mesh, 1250ms, 42 nodes) +[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 b9767a1..1a3ca4d 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -7,14 +7,23 @@ 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 { LogContext } from '../flow-core/src/utils/logger/types.ts'; import { MESH } from '../flow-core/src/mesh-constants.ts'; // Initialize configuration system try { await createServiceConfig(); } catch (error) { - await handleCaughtError(error, 'Failed to initialize service configuration'); + const context: LogContext = { + operation: 'startup', + component: 'service-config-init', + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '0.1.0' + } + }; + await handleCaughtError(error, 'Failed to initialize service configuration', context); console.error( '❌ Service startup failed due to configuration error. Exiting...', ); @@ -25,7 +34,15 @@ try { try { logStartupConfiguration(); } catch (error) { - await handleCaughtError(error, 'Failed to log startup configuration'); + const context: LogContext = { + operation: 'startup', + component: 'startup-config-logging', + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '0.1.0' + } + }; + await handleCaughtError(error, 'Failed to log startup configuration', context); console.error('⚠️ Configuration logging failed, but continuing startup...'); } @@ -68,7 +85,15 @@ let markdown; try { markdown = await createMarkdownFromOpenApi(JSON.stringify(content)); } catch (error) { - await handleCaughtError(error, 'Failed to generate markdown documentation'); + const context: LogContext = { + operation: 'startup', + component: 'markdown-generation', + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '0.1.0' + } + }; + await handleCaughtError(error, 'Failed to generate markdown documentation', context); console.error('⚠️ Markdown generation failed, but continuing startup...'); markdown = '# API Documentation\n\nDocumentation generation failed.'; } @@ -92,7 +117,15 @@ app.route('/api', weave); try { logStartupUrls(); } catch (error) { - await handleCaughtError(error, 'Failed to log startup URLs'); + const context: LogContext = { + operation: 'startup', + component: 'startup-url-logging', + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '0.1.0' + } + }; + await handleCaughtError(error, 'Failed to log startup URLs', context); console.error('⚠️ URL logging failed, but continuing startup...'); } @@ -102,7 +135,18 @@ try { hostname: await singletonServiceConfigAccessor.getHost(), }, app.fetch); } catch (error) { - await handleCaughtError(error, 'Failed to start HTTP server'); + const context: LogContext = { + operation: 'startup', + component: 'http-server-start', + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '0.1.0' + }, + 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 index 57b9f8b..7489cc4 100644 --- a/flow-service/src/config/config-types.ts +++ b/flow-service/src/config/config-types.ts @@ -68,7 +68,7 @@ export interface MeshRootNodeConfig extends NodeObject { readonly 'conf:generateAggregatedDataset': boolean; readonly 'conf:generateResourcePages'?: boolean; readonly 'conf:stylesheetPath'?: string; - readonly 'conf:defaultAttribution'?: string; + readonly 'conf:defaultAttributedTo'?: string; } // Complete Service Configuration @@ -81,13 +81,7 @@ export interface ServiceConfig extends NodeObject { readonly 'fsvc:hasContainedServices': ContainedServicesConfig; readonly 'fsvc:rootMeshRootNodeConfigTemplate'?: MeshRootNodeConfig; readonly 'fsvc:defaultDelegationChain'?: DelegationChain; - readonly 'fsvc:defaultAttributedTo'?: AttributedTo; -} - -// Attribution Configuration -export interface AttributedTo extends NodeObject { - readonly '@id': string; - //TODO definition + readonly 'fsvc:defaultAttributedTo'?: string; } // Delegation Chain Configuration @@ -114,7 +108,7 @@ export interface ServiceConfigInput extends Partial { 'fsvc:hasContainedServices'?: Partial; 'fsvc:nodeDefaults'?: Partial; 'fsvc:defaultDelegationChain'?: Partial; - 'fsvc:defaultAttributedTo'?: Partial; + 'fsvc:defaultAttributedTo'?: string; } export interface MeshRootNodeConfigInput extends Partial { @@ -127,7 +121,7 @@ export interface MeshRootNodeConfigInput extends Partial { 'conf:generateAggregatedDataset'?: boolean; 'conf:generateResourcePages'?: boolean; 'conf:stylesheetPath'?: string; - 'conf:defaultAttribution'?: string; + 'conf:defaultAttributedTo'?: string; } export interface MeshRootNodeConfigContext { diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index ca66f44..bae89e3 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -79,7 +79,8 @@ export { mergeConfigs } from '../utils/merge-configs.ts'; import type { ServiceOptions } from './config-types.ts'; import { resolveServiceConfig } from './resolution/service-config-resolver.ts'; import { validateServiceConfig } from './resolution/service-config-validator.ts'; -import { handleCaughtError } from '../utils/logger.ts'; +import { handleCaughtError } from '../../../flow-core/src/utils/logger/error-handlers.ts'; +import { LogContext } from '../../../flow-core/src/utils/logger/types.ts'; /** * Resolves and validates the service configuration. @@ -93,7 +94,16 @@ export async function createServiceConfig( await resolveServiceConfig(cliOptions); await validateServiceConfig(); } catch (error) { - await handleCaughtError(error, `Failed to create service configuration`); + const context: LogContext = { + operation: 'config-create', + component: 'service-config-creation', + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '0.1.0' + }, + metadata: { cliOptions } + }; + await handleCaughtError(error, `Failed to create service configuration`, context); throw error; } } diff --git a/flow-service/src/config/loaders/jsonld-loader.ts b/flow-service/src/config/loaders/jsonld-loader.ts index f06e227..2c04b89 100644 --- a/flow-service/src/config/loaders/jsonld-loader.ts +++ b/flow-service/src/config/loaders/jsonld-loader.ts @@ -7,7 +7,7 @@ import type { MeshRootNodeConfigInput, ServiceConfigInput } from '../config-types.ts'; import { ConfigError } from '../config-types.ts'; -import { handleCaughtError } from '../../utils/logger.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, NodeObject, resolve } from '../../../../flow-core/src/deps.ts'; diff --git a/flow-service/src/config/node-config.default.jsonld b/flow-service/src/config/node-config.default.jsonld index 4db5b69..f153f77 100644 --- a/flow-service/src/config/node-config.default.jsonld +++ b/flow-service/src/config/node-config.default.jsonld @@ -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 index 7b4fed0..3c110de 100644 --- a/flow-service/src/config/resolution/service-config-accessor.ts +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -2,7 +2,8 @@ import type { QuadstoreBundle } from '../../../../flow-core/src/types.ts'; import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; import { DataFactory } from '../../../../flow-core/src/deps.ts'; import { CONFIG_GRAPH_NAMES } from '../index.ts'; -import { handleCaughtError } from '../../utils/logger.ts'; +import { handleCaughtError } from '../../../../flow-core/src/utils/logger/error-handlers.ts'; +import { LogContext } from '../../../../flow-core/src/utils/logger/types.ts'; export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { private bundle: QuadstoreBundle; @@ -27,7 +28,16 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { } } } catch (error) { - await handleCaughtError(error, 'Failed to execute SPARQL query'); + const context: LogContext = { + operation: 'config-query', + component: 'service-config-accessor', + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '0.1.0' + }, + metadata: { sparql } + }; + await handleCaughtError(error, 'Failed to execute SPARQL query', context); throw new Error('Failed to execute SPARQL query'); } return undefined; diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index 012c830..1f1ed85 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -9,7 +9,8 @@ import { } 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'; /** @@ -24,6 +25,9 @@ import { loadPlatformDefaults, loadInputServiceConfig, loadInputMeshRootNodeConf export async function resolveServiceConfig( cliOptions?: ServiceOptions, ): Promise { + // Define serviceConfigPath outside try block for error handling access + const serviceConfigPath = getServiceConfigPath(cliOptions?.configPath); + try { // Load platform defaults into Quadstore graphs await loadPlatformDefaults(); @@ -32,7 +36,6 @@ export async function resolveServiceConfig( const envConfig = loadEnvConfig(); // Load file config if specified - const serviceConfigPath = getServiceConfigPath(cliOptions?.configPath); let fileConfig: ServiceConfigInput | undefined; if (serviceConfigPath) { const loadedConfig = await loadServiceConfig(serviceConfigPath); @@ -55,12 +58,25 @@ export async function resolveServiceConfig( await mergeServiceConfigGraphs(); } catch (error) { + const context: LogContext = { + operation: 'config-resolve', + component: 'service-config-resolution', + configContext: { + configPath: serviceConfigPath || 'none', + configType: 'service-config' + }, + serviceContext: { + serviceName: 'flow-service', + serviceVersion: '0.1.0' + } + }; + 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( @@ -124,3 +140,4 @@ function convertCliOptionsToConfig( return config; } + 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 c9ddbc1..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, @@ -11,7 +11,7 @@ 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 { 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'; @@ -136,7 +136,11 @@ export const createMeshesRoutes = (): OpenAPIHono => { }, 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); @@ -160,6 +164,11 @@ export const createMeshesRoutes = (): OpenAPIHono => { 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 }, + }, ); } @@ -275,7 +284,17 @@ export const createMeshesRoutes = (): OpenAPIHono => { 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; diff --git a/flow-service/src/routes/weave.ts b/flow-service/src/routes/weave.ts index e709f4c..d526814 100644 --- a/flow-service/src/routes/weave.ts +++ b/flow-service/src/routes/weave.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 { MESH } from '../../../flow-core/src/mesh-constants.ts'; // Utility function to validate node specifier using QName rules (simplified regex for example) @@ -92,6 +92,14 @@ export const createWeaveRoutes = ( logger.info( `Starting weave process for node specifier: ${nodeSpecifier} (segments: ${nodeSegments.join(', ') })`, + { + operation: 'weave', + component: 'weave-processor', + metadata: { + nodeSpecifier, + nodeSegments, + }, + }, ); try { @@ -105,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/services/metadata-composer.ts b/flow-service/src/services/metadata-composer.ts index 3bb219c..7c22ac9 100644 --- a/flow-service/src/services/metadata-composer.ts +++ b/flow-service/src/services/metadata-composer.ts @@ -1,4 +1,4 @@ -import { singletonServiceConfigAccessor as config } from '../config/resolution/service-config-accessor.ts'; +import { singletonServiceConfigAccessor as config } from "../config/resolution/service-config-accessor.ts"; //import { MESH } from '../../../flow-core/src/mesh-constants.ts'; @@ -16,65 +16,65 @@ export function composeMetadataContent( initialData: Record, 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 42f49aa..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 { @@ -37,15 +38,33 @@ export function handleServiceError( 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 0b40741..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); -} - -export 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 35ea7f4..b7028be 100644 --- a/flow-service/src/utils/mesh-utils.ts +++ b/flow-service/src/utils/mesh-utils.ts @@ -1,11 +1,12 @@ -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 = ( - meshRegistry: Record, -) => { - const meshPaths = config.getMeshPaths; +// 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); @@ -14,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; } @@ -21,6 +27,11 @@ 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 }, + }, ); } @@ -28,6 +39,15 @@ export const initializeMeshRegistry = ( logger.warn( `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; } @@ -35,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-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/startup-logger.ts b/flow-service/src/utils/startup-logger.ts index 7c2232e..e55d5ab 100644 --- a/flow-service/src/utils/startup-logger.ts +++ b/flow-service/src/utils/startup-logger.ts @@ -44,7 +44,7 @@ export async function logStartupConfiguration(): Promise { console.log(` File Logging: disabled`); console.log(` Sentry Logging: disabled`); - const enabledServices = []; + 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'); diff --git a/flow-service/tests/integration/logging/formatting-direct.test.ts b/flow-service/tests/integration/logging/formatting-direct.test.ts index e3e956c..e1e5479 100644 --- a/flow-service/tests/integration/logging/formatting-direct.test.ts +++ b/flow-service/tests/integration/logging/formatting-direct.test.ts @@ -44,8 +44,8 @@ interface LogContext { [key: string]: unknown; } -// Import the pretty formatting function from the logger module -import { formatConsoleMessage } from '../../../src/utils/logger.ts'; +// Import the pretty formatting function from the formatters module +import { formatConsoleMessage } from '../../../src/utils/service-logger.ts'; // Simple file logger diff --git a/flow-service/tests/integration/logging/handle-caught-error.test.ts b/flow-service/tests/integration/logging/handle-caught-error.test.ts index 54fbf80..d678b11 100644 --- a/flow-service/tests/integration/logging/handle-caught-error.test.ts +++ b/flow-service/tests/integration/logging/handle-caught-error.test.ts @@ -5,7 +5,7 @@ * 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, diff --git a/flow-service/tests/integration/logging/logger.test.ts b/flow-service/tests/integration/logging/logger.test.ts index 99ac743..2ac4461 100644 --- a/flow-service/tests/integration/logging/logger.test.ts +++ b/flow-service/tests/integration/logging/logger.test.ts @@ -4,57 +4,81 @@ * Simple test script to verify file logging functionality */ -import { logger } from '../../../src/utils/logger.ts'; +import { logger } from '../../../src/utils/service-logger.ts'; async function testFileLogging() { console.log('🧪 Testing file logging functionality...\n'); // Test basic logging with context - await logger.info('File logging test started', { + logger.info('File logging test started', { operation: 'startup', component: 'config-resolver', - testId: 'file-logging-test-001', + metadata: { + testId: 'file-logging-test-001', + }, }); - await logger.debug('Debug message with context', { + logger.debug('Debug message with context', { operation: 'scan', component: 'mesh-scanner', - meshPath: '/test/path', - nodeCount: 5, - duration: 125, + performanceMetrics: { + duration: 125, + }, + metadata: { + meshPath: '/test/path', + nodeCount: 5, + }, }); - await logger.warn('Warning message with context', { + logger.warn('Warning message with context', { operation: 'config-resolve', component: 'config-resolver', - configSource: 'file', - schemaVersion: '1.0.0', + configContext: { + configPath: 'file', + }, + metadata: { + schemaVersion: '1.0.0', + }, }); - await logger.error('Error message with context', new Error('Test error'), { + logger.error('Error message with context', new Error('Test error'), { operation: 'api-request', component: 'api-handler', - endpoint: '/api/test', - statusCode: 500, - errorCode: 'TEST_ERROR', + apiContext: { + path: '/api/test', + statusCode: 500, + }, + errorContext: { + errorCode: 'TEST_ERROR', + }, }); // Test contextual logger const meshLogger = logger.withContext({ component: 'mesh-scanner', operation: 'scan', - meshPath: '/test/mesh', + metadata: { + meshPath: '/test/mesh', + }, }); - await meshLogger.info('Processing nodes', { - nodeCount: 10, - duration: 250, + meshLogger.info('Processing nodes', { + performanceMetrics: { + duration: 250, + }, + metadata: { + nodeCount: 10, + }, }); - await meshLogger.info('Scan completed', { - nodeCount: 10, - fileCount: 25, - duration: 500, + meshLogger.info('Scan completed', { + performanceMetrics: { + duration: 500, + }, + metadata: { + nodeCount: 10, + fileCount: 25, + }, }); console.log('\n✅ File logging test completed!'); diff --git a/flow-service/tests/integration/logging/pretty-format-simple.test.ts b/flow-service/tests/integration/logging/pretty-format-simple.test.ts index 5834260..4140083 100644 --- a/flow-service/tests/integration/logging/pretty-format-simple.test.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 index da74115..81d3674 100644 --- a/flow-service/tests/integration/logging/sentry-init.test.ts +++ b/flow-service/tests/integration/logging/sentry-init.test.ts @@ -2,6 +2,6 @@ * TODO: Test script to verify Sentry initialization and logging */ -import { initSentry, logger } from '../../../src/utils/logger.ts'; +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 index a08aece..8ae9dfe 100644 --- a/flow-service/tests/integration/logging/sentry-logging.test.ts +++ b/flow-service/tests/integration/logging/sentry-logging.test.ts @@ -2,5 +2,5 @@ * TODO: Test script to verify Sentry logging integration with log retention configuration */ -import { logger } from '../../../src/utils/logger.ts'; +import { logger } from '../../../src/utils/service-logger.ts'; import { singletonServiceConfigAccessor as config } from '../../../src/config/resolution/service-config-accessor.ts'; From d0175053b74bf79ff3e2e62eadbaba458ff4ff96 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Sun, 3 Aug 2025 22:09:00 -0700 Subject: [PATCH 16/20] consiste error handling --- flow-service/flow-service-config.jsonld | 2 +- flow-service/main.ts | 45 ++---- flow-service/src/config/config-types.ts | 4 + flow-service/src/config/index.ts | 12 +- .../resolution/service-config-accessor.ts | 10 +- .../resolution/service-config-resolver.ts | 10 +- flow-service/src/service-constants.ts | 3 + flow-service/src/utils/service-log-context.ts | 24 ++++ flow-service/src/utils/service-uri-builder.ts | 130 ++++++++++++++++++ 9 files changed, 187 insertions(+), 53 deletions(-) create mode 100644 flow-service/src/service-constants.ts create mode 100644 flow-service/src/utils/service-log-context.ts create mode 100644 flow-service/src/utils/service-uri-builder.ts diff --git a/flow-service/flow-service-config.jsonld b/flow-service/flow-service-config.jsonld index a81e850..b987bbe 100644 --- a/flow-service/flow-service-config.jsonld +++ b/flow-service/flow-service-config.jsonld @@ -8,7 +8,7 @@ "meta": "https://semantic-flow.github.io/ontology/meta-flow/", "prov": "http://www.w3.org/ns/prov#", "dcterms": "http://purl.org/dc/terms/", - "mergedServiceConfig": "http://localhost/graph/mergedServiceConfig/" + "local": "http://localhost/graph/mergedServiceConfig/" }, "@id": "local:serviceConfig", "@type": "fsvc:ServiceConfig", diff --git a/flow-service/main.ts b/flow-service/main.ts index 1a3ca4d..ce28b43 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -8,21 +8,18 @@ import { logStartupUrls, } from './src/utils/startup-logger.ts'; import { handleCaughtError } from '../flow-core/src/utils/logger/error-handlers.ts'; -import { LogContext } from '../flow-core/src/utils/logger/types.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 try { await createServiceConfig(); } catch (error) { - const context: LogContext = { + const context: LogContext = createServiceLogContext({ operation: 'startup', component: 'service-config-init', - serviceContext: { - serviceName: 'flow-service', - serviceVersion: '0.1.0' - } - }; + }); await handleCaughtError(error, 'Failed to initialize service configuration', context); console.error( '❌ Service startup failed due to configuration error. Exiting...', @@ -34,14 +31,10 @@ try { try { logStartupConfiguration(); } catch (error) { - const context: LogContext = { + const context: LogContext = createServiceLogContext({ operation: 'startup', component: 'startup-config-logging', - serviceContext: { - serviceName: 'flow-service', - serviceVersion: '0.1.0' - } - }; + }); await handleCaughtError(error, 'Failed to log startup configuration', context); console.error('⚠️ Configuration logging failed, but continuing startup...'); } @@ -75,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', }), @@ -85,14 +78,10 @@ let markdown; try { markdown = await createMarkdownFromOpenApi(JSON.stringify(content)); } catch (error) { - const context: LogContext = { + const context: LogContext = createServiceLogContext({ operation: 'startup', component: 'markdown-generation', - serviceContext: { - serviceName: 'flow-service', - serviceVersion: '0.1.0' - } - }; + }); await handleCaughtError(error, 'Failed to generate markdown documentation', context); console.error('⚠️ Markdown generation failed, but continuing startup...'); markdown = '# API Documentation\n\nDocumentation generation failed.'; @@ -117,14 +106,10 @@ app.route('/api', weave); try { logStartupUrls(); } catch (error) { - const context: LogContext = { + const context: LogContext = createServiceLogContext({ operation: 'startup', component: 'startup-url-logging', - serviceContext: { - serviceName: 'flow-service', - serviceVersion: '0.1.0' - } - }; + }); await handleCaughtError(error, 'Failed to log startup URLs', context); console.error('⚠️ URL logging failed, but continuing startup...'); } @@ -135,17 +120,13 @@ try { hostname: await singletonServiceConfigAccessor.getHost(), }, app.fetch); } catch (error) { - const context: LogContext = { + const context: LogContext = createServiceLogContext({ operation: 'startup', component: 'http-server-start', - serviceContext: { - serviceName: 'flow-service', - serviceVersion: '0.1.0' - }, 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 index 7489cc4..6542a93 100644 --- a/flow-service/src/config/config-types.ts +++ b/flow-service/src/config/config-types.ts @@ -74,6 +74,7 @@ export interface MeshRootNodeConfig extends NodeObject { // 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[]; @@ -101,6 +102,7 @@ export interface DelegationChain extends NodeObject { // 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[]; @@ -132,6 +134,7 @@ export interface MeshRootNodeConfigContext { // CLI Options Type export interface ServiceOptions { readonly configPath?: string; + readonly scheme?: 'http' | 'https'; readonly port?: number; readonly host?: string; readonly meshPaths?: string[]; @@ -143,6 +146,7 @@ export interface ServiceOptions { // 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; diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index bae89e3..3da5527 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -5,6 +5,8 @@ * and side-by-side configuration context. */ +import { createServiceLogContext } from "../utils/service-log-context.ts"; + /** * Named graph terms for Quadstore config graphs */ @@ -94,16 +96,14 @@ export async function createServiceConfig( await resolveServiceConfig(cliOptions); await validateServiceConfig(); } catch (error) { - const context: LogContext = { + const context = createServiceLogContext({ operation: 'config-create', component: 'service-config-creation', - serviceContext: { - serviceName: 'flow-service', - serviceVersion: '0.1.0' - }, metadata: { cliOptions } - }; + + }); await handleCaughtError(error, `Failed to create service configuration`, context); throw error; } } + diff --git a/flow-service/src/config/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts index 3c110de..cfd9471 100644 --- a/flow-service/src/config/resolution/service-config-accessor.ts +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -3,7 +3,7 @@ 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 { LogContext } from '../../../../flow-core/src/utils/logger/types.ts'; +import { createServiceLogContext } from '../../utils/service-log-context.ts'; export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { private bundle: QuadstoreBundle; @@ -28,15 +28,11 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { } } } catch (error) { - const context: LogContext = { + const context = createServiceLogContext({ operation: 'config-query', component: 'service-config-accessor', - serviceContext: { - serviceName: 'flow-service', - serviceVersion: '0.1.0' - }, metadata: { sparql } - }; + }); await handleCaughtError(error, 'Failed to execute SPARQL query', context); throw new Error('Failed to execute SPARQL query'); } diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index 1f1ed85..c178fcd 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -12,6 +12,7 @@ import { mergeConfigs } from '../../utils/merge-configs.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'; /** * Asynchronously resolves the service configuration by merging CLI options, environment variables, configuration files, and environment-specific defaults in a defined precedence order. @@ -58,18 +59,14 @@ export async function resolveServiceConfig( await mergeServiceConfigGraphs(); } catch (error) { - const context: LogContext = { + const context: LogContext = createServiceLogContext({ operation: 'config-resolve', component: 'service-config-resolution', configContext: { configPath: serviceConfigPath || 'none', configType: 'service-config' - }, - serviceContext: { - serviceName: 'flow-service', - serviceVersion: '0.1.0' } - }; + }); if (error instanceof ConfigError) { await handleCaughtError(error, `Service configuration resolution failed`, context); @@ -140,4 +137,3 @@ function convertCliOptionsToConfig( return config; } - 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/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-uri-builder.ts b/flow-service/src/utils/service-uri-builder.ts new file mode 100644 index 0000000..81167e3 --- /dev/null +++ b/flow-service/src/utils/service-uri-builder.ts @@ -0,0 +1,130 @@ +/** + * 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; +} + +/** + * Default configuration for URI building when config is not yet available + */ +export const DEFAULT_SERVICE_URI_CONFIG: ServiceUriConfig = { + scheme: 'http', + host: 'localhost', + port: 3000, +}; + +/** + * 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 graph URI for the given graph name + */ +export function buildServiceGraphUri(config: ServiceUriConfig, graphName: string): string { + const baseUri = buildServiceBaseUri(config); + return `${baseUri}/graph/${graphName}`; +} + +/** + * Builds the complete set of config graph names based on service configuration + */ +export function buildConfigGraphNames(config: ServiceUriConfig) { + return { + platformServiceDefaults: buildServiceGraphUri(config, 'platformServiceDefaults'), + platformImplicitMeshRootNodeConfig: buildServiceGraphUri(config, 'platformImplicitMeshRootNodeConfig'), + inputServiceConfig: buildServiceGraphUri(config, 'inputServiceConfig'), + inputMeshRootNodeConfig: buildServiceGraphUri(config, 'inputMeshRootNodeConfig'), + mergedServiceConfig: buildServiceGraphUri(config, 'mergedServiceConfig'), + }; +} + +/** + * Builds a JSON-LD context with dynamic local URI + */ +export function buildServiceContext(config: ServiceUriConfig) { + const baseUri = buildServiceBaseUri(config); + 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/", + "meta": "https://semantic-flow.github.io/ontology/meta-flow/", + "prov": "http://www.w3.org/ns/prov#", + "dcterms": "http://purl.org/dc/terms/", + "local": `${baseUri}/graph/mergedServiceConfig/` + }; +} + +/** + * Builds a service-relative URI for the given path + */ +export function buildServiceUri(config: ServiceUriConfig, path: string): string { + const baseUri = buildServiceBaseUri(config); + const cleanPath = path.startsWith('/') ? path : `/${path}`; + return `${baseUri}${cleanPath}`; +} + +/** + * Singleton holder for current service URI configuration + * This is updated during service configuration resolution + */ +class ServiceUriConfigManager { + private _config: ServiceUriConfig = DEFAULT_SERVICE_URI_CONFIG; + private _isInitialized = false; + + setConfig(config: ServiceUriConfig): void { + this._config = config; + this._isInitialized = true; + } + + getConfig(): ServiceUriConfig { + return this._config; + } + + isInitialized(): boolean { + return this._isInitialized; + } + + reset(): void { + this._config = DEFAULT_SERVICE_URI_CONFIG; + this._isInitialized = false; + } +} + +export const serviceUriConfigManager = new ServiceUriConfigManager(); + +/** + * Convenience functions that use the current service configuration + */ +export function getCurrentServiceBaseUri(): string { + return buildServiceBaseUri(serviceUriConfigManager.getConfig()); +} + +export function getCurrentConfigGraphNames() { + return buildConfigGraphNames(serviceUriConfigManager.getConfig()); +} + +export function getCurrentServiceContext() { + return buildServiceContext(serviceUriConfigManager.getConfig()); +} + +export function getCurrentServiceUri(path: string): string { + return buildServiceUri(serviceUriConfigManager.getConfig(), path); +} From 8c54aeda2108f79531c0fd01a8ae07733235b6d1 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Mon, 4 Aug 2025 17:22:37 -0700 Subject: [PATCH 17/20] Refactor service configuration to support dynamic URIs and improve environment variable handling --- flow-service/flow-service-config.jsonld | 21 +++--- flow-service/src/config/defaults.ts | 25 ++++--- flow-service/src/config/index.ts | 13 ++-- flow-service/src/config/loaders/env-loader.ts | 8 +++ .../resolution/service-config-resolver.ts | 22 +++++- flow-service/src/utils/service-uri-builder.ts | 67 +++++++++---------- 6 files changed, 89 insertions(+), 67 deletions(-) diff --git a/flow-service/flow-service-config.jsonld b/flow-service/flow-service-config.jsonld index b987bbe..30beef6 100644 --- a/flow-service/flow-service-config.jsonld +++ b/flow-service/flow-service-config.jsonld @@ -6,30 +6,26 @@ "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/", - "local": "http://localhost/graph/mergedServiceConfig/" + "prov": "http://www.w3.org/ns/prov#" }, - "@id": "local:serviceConfig", + "@id": "_service-config", "@type": "fsvc:ServiceConfig", + "fsvc:scheme": "http", "fsvc:host": "localhost", - "fsvc:port": 8080, + "fsvc:port": 31415, "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": "https://spectacular-voyage.github.io/ns/spectacular-voyage", "fsvc:defaultRights": "Copyright 2025 David Richardson. Licensed under CC BY-SA 4.0.", "fsvc:defaultDelegationChain": { + "@id": "_service/defaultDelegationChain", "@type": "meta:DelegationChain", "meta:hasStep": [ { + "@id": "_service/defaultDelegationChain#1", "@type": "meta:DelegationStep", "meta:stepOrder": 1, "prov:agent": { @@ -37,6 +33,7 @@ } }, { + "@id": "_service/defaultDelegationChain#2", "@type": "meta:DelegationStep", "meta:stepOrder": 2, "prov:agent": { diff --git a/flow-service/src/config/defaults.ts b/flow-service/src/config/defaults.ts index 3a3f140..382a363 100644 --- a/flow-service/src/config/defaults.ts +++ b/flow-service/src/config/defaults.ts @@ -7,20 +7,29 @@ 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 -// TODO: make local context use the config host and port +// 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/", - "local": "http://localhost/graph/" }; -// Platform Node Configuration Defaults -// TODO: Rename to indicate it's for the root MeshNodes of new Meshes export const PLATFORM_NODE_DEFAULTS: MeshRootNodeConfig = { "@id": "local:meshRootNodeConfig", "@context": DEFAULT_CONTEXT, @@ -71,12 +80,12 @@ export const SERVICE_NODE_DEFAULTS: Omit< "conf:generateAggregatedDataset": false, }; -// Platform Service Configuration Defaults export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { - "@id": "local:serviceConfig", + "@id": "serviceConfig", "@context": DEFAULT_CONTEXT, "@type": "fsvc:ServiceConfig", - "fsvc:port": 3000, + "fsvc:scheme": "http", + "fsvc:port": 31415, "fsvc:host": "localhost", "fsvc:hasLoggingConfig": { "@id": "local:ServiceConfig#loggingConfig", diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index 3da5527..cb1198f 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -11,13 +11,12 @@ import { createServiceLogContext } from "../utils/service-log-context.ts"; * Named graph terms for Quadstore config graphs */ export const CONFIG_GRAPH_NAMES = { - platformServiceDefaults: 'http://localhost/graph/platformServiceDefaults', - platformImplicitMeshRootNodeConfig: 'http://localhost/graph/platformImplicitMeshRootNodeConfig', - inputServiceConfig: 'http://localhost/graph/inputServiceConfig', - inputMeshRootNodeConfig: 'http://localhost/graph/inputMeshRootNodeConfig', - mergedServiceConfig: 'http://localhost/graph/mergedServiceConfig', - // Per-node effective configs could be stored with dynamic graph names -}; + platformServiceDefaults: 'platformServiceDefaults', + platformImplicitMeshRootNodeConfig: 'platformImplicitMeshRootNodeConfig', + inputServiceConfig: 'inputServiceConfig', + inputMeshRootNodeConfig: 'inputMeshRootNodeConfig', + mergedServiceConfig: 'mergedServiceConfig', +} // Core Types diff --git a/flow-service/src/config/loaders/env-loader.ts b/flow-service/src/config/loaders/env-loader.ts index 8306b7a..cbb5c3c 100644 --- a/flow-service/src/config/loaders/env-loader.ts +++ b/flow-service/src/config/loaders/env-loader.ts @@ -25,6 +25,13 @@ export function loadEnvConfig(): ServiceConfigInput { const configInput: ServiceConfigInput = {}; // 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) { @@ -239,6 +246,7 @@ export function loadEnvConfig(): ServiceConfigInput { function getEnvironmentVariables(): EnvironmentConfig { return { 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'), diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index c178fcd..4996025 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -6,6 +6,7 @@ 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 '../config-types.ts'; import { mergeConfigs } from '../../utils/merge-configs.ts'; @@ -13,6 +14,7 @@ import { handleCaughtError } from '../../../../flow-core/src/utils/logger/error- 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. @@ -30,9 +32,6 @@ export async function resolveServiceConfig( const serviceConfigPath = getServiceConfigPath(cliOptions?.configPath); try { - // Load platform defaults into Quadstore graphs - await loadPlatformDefaults(); - // Load environment config const envConfig = loadEnvConfig(); @@ -49,6 +48,19 @@ export async function resolveServiceConfig( // Merge input configs: env, file, CLI const mergedInputConfig = mergeConfigs(mergeConfigs(envConfig, fileConfig ?? {}), cliConfig); + // 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 (now uses dynamic URIs) + await loadPlatformDefaults(); + // Load merged input config into Quadstore graph await loadInputServiceConfig(mergedInputConfig); @@ -96,6 +108,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; } diff --git a/flow-service/src/utils/service-uri-builder.ts b/flow-service/src/utils/service-uri-builder.ts index 81167e3..f90a774 100644 --- a/flow-service/src/utils/service-uri-builder.ts +++ b/flow-service/src/utils/service-uri-builder.ts @@ -12,14 +12,6 @@ export interface ServiceUriConfig { readonly port: number; } -/** - * Default configuration for URI building when config is not yet available - */ -export const DEFAULT_SERVICE_URI_CONFIG: ServiceUriConfig = { - scheme: 'http', - host: 'localhost', - port: 3000, -}; /** * Builds a base service URI from the given configuration @@ -54,23 +46,6 @@ export function buildConfigGraphNames(config: ServiceUriConfig) { }; } -/** - * Builds a JSON-LD context with dynamic local URI - */ -export function buildServiceContext(config: ServiceUriConfig) { - const baseUri = buildServiceBaseUri(config); - 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/", - "meta": "https://semantic-flow.github.io/ontology/meta-flow/", - "prov": "http://www.w3.org/ns/prov#", - "dcterms": "http://purl.org/dc/terms/", - "local": `${baseUri}/graph/mergedServiceConfig/` - }; -} /** * Builds a service-relative URI for the given path @@ -86,25 +61,46 @@ export function buildServiceUri(config: ServiceUriConfig, path: string): string * This is updated during service configuration resolution */ class ServiceUriConfigManager { - private _config: ServiceUriConfig = DEFAULT_SERVICE_URI_CONFIG; - private _isInitialized = false; + private _config: ServiceUriConfig | null = null; + private _cachedGraphNames: ReturnType | null = null; + private _cachedBaseUri: string | null = null; setConfig(config: ServiceUriConfig): void { this._config = config; - this._isInitialized = true; + // Update cached values when config changes + this._cachedGraphNames = buildConfigGraphNames(config); + this._cachedBaseUri = buildServiceBaseUri(config); } getConfig(): ServiceUriConfig { + if (!this._config) { + throw new Error('Service URI configuration not initialized. Call setConfig() first.'); + } return this._config; } + getConfigGraphNames() { + if (!this._cachedGraphNames) { + throw new Error('Service URI configuration not initialized. Call setConfig() first.'); + } + return this._cachedGraphNames; + } + + getServiceBaseUri(): string { + if (!this._cachedBaseUri) { + throw new Error('Service URI configuration not initialized. Call setConfig() first.'); + } + return this._cachedBaseUri; + } + isInitialized(): boolean { - return this._isInitialized; + return this._config !== null; } reset(): void { - this._config = DEFAULT_SERVICE_URI_CONFIG; - this._isInitialized = false; + this._config = null; + this._cachedGraphNames = null; + this._cachedBaseUri = null; } } @@ -114,17 +110,14 @@ export const serviceUriConfigManager = new ServiceUriConfigManager(); * Convenience functions that use the current service configuration */ export function getCurrentServiceBaseUri(): string { - return buildServiceBaseUri(serviceUriConfigManager.getConfig()); + return serviceUriConfigManager.getServiceBaseUri(); } export function getCurrentConfigGraphNames() { - return buildConfigGraphNames(serviceUriConfigManager.getConfig()); -} - -export function getCurrentServiceContext() { - return buildServiceContext(serviceUriConfigManager.getConfig()); + return serviceUriConfigManager.getConfigGraphNames(); } export function getCurrentServiceUri(path: string): string { return buildServiceUri(serviceUriConfigManager.getConfig(), path); } + From 8942f98210acc5898a4a23382816b2f0a8081f77 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Mon, 4 Aug 2025 17:23:00 -0700 Subject: [PATCH 18/20] feat: Enhance RDFJS utilities and service configuration - Added `expandRelativeIds` function to expand relative IDs in JSON-LD objects based on a provided base IRI. - Updated `jsonldToQuads` function to utilize `jsonld.toRDF` for converting JSON-LD to RDFJS quads. - Modified `flow-service-config.jsonld` to adjust service configuration, including changes to ports and rights holders. - Refactored `config-types.ts` to standardize property names and types across interfaces. - Updated default service configurations in `defaults.ts` to reflect new structure and IDs. - Enhanced environment variable loading in `env-loader.ts` to include new service configuration properties. - Implemented dynamic URI handling in `quadstore-loader.ts` for loading platform defaults and input configurations. - Adjusted service configuration resolution logic in `service-config-resolver.ts` to utilize updated loading functions. - Cleaned up service URI building logic in `service-uri-builder.ts` to simplify graph URI construction. --- flow-core/src/utils/quadstore-utils.ts | 16 +- flow-core/src/utils/rdfjs-utils.ts | 31 +++- flow-service/flow-service-config.jsonld | 16 +- flow-service/src/config/config-types.ts | 166 +++++++++--------- flow-service/src/config/defaults.ts | 20 +-- flow-service/src/config/loaders/env-loader.ts | 159 +++++++++-------- .../src/config/loaders/quadstore-loader.ts | 45 +++-- .../resolution/service-config-accessor.ts | 7 +- .../resolution/service-config-loader.ts | 1 + .../resolution/service-config-resolver.ts | 8 +- .../resolution/service-config-validator.ts | 13 +- flow-service/src/utils/service-uri-builder.ts | 43 +---- 12 files changed, 282 insertions(+), 243 deletions(-) diff --git a/flow-core/src/utils/quadstore-utils.ts b/flow-core/src/utils/quadstore-utils.ts index d15400e..5f137ab 100644 --- a/flow-core/src/utils/quadstore-utils.ts +++ b/flow-core/src/utils/quadstore-utils.ts @@ -1,6 +1,5 @@ import { NodeObject, Quadstore, DataFactory } from '../deps.ts'; import { RDF } from '../deps.ts'; -import { jsonld } from '../deps.ts'; import { defaultQuadstoreBundle } from '../../../flow-service/src/quadstore-default-bundle.ts'; import { jsonldToQuads } from './rdfjs-utils.ts'; import type { QuadstoreBundle } from '../types.ts'; @@ -40,12 +39,13 @@ export async function clearGraph( ): 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}`); + //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 matchStream3 = store.match(undefined, undefined, undefined, graph); const count2 = await countQuadsInStream(matchStream3); console.log(`Number of quads in graph ${graph.value}: ${count2}`); + */ return count; } @@ -63,6 +63,7 @@ export async function copyGraph( 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); } @@ -121,6 +122,15 @@ export async function createNewGraphFromJsonLd( 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 diff --git a/flow-core/src/utils/rdfjs-utils.ts b/flow-core/src/utils/rdfjs-utils.ts index ef8bd21..674d3fd 100644 --- a/flow-core/src/utils/rdfjs-utils.ts +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -6,11 +6,13 @@ 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 { - // Use jsonld.toRDF() to get quads directly - much simpler! + + const quads = await jsonld.toRDF(inputJsonLd) as RDF.Quad[]; // If the desired graph is the default graph, return as-is @@ -23,3 +25,30 @@ export async function jsonldToQuads( 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-service/flow-service-config.jsonld b/flow-service/flow-service-config.jsonld index 30beef6..383e635 100644 --- a/flow-service/flow-service-config.jsonld +++ b/flow-service/flow-service-config.jsonld @@ -1,31 +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#" }, - "@id": "_service-config", + "@id": "_service-config/", "@type": "fsvc:ServiceConfig", "fsvc:scheme": "http", "fsvc:host": "localhost", - "fsvc:port": 31415, + "fsvc:port": 8080, "fsvc:meshPaths": [ "../meshes/test-ns/" ], "fsvc:defaultLicense": "https://creativecommons.org/licenses/by-sa/4.0/", "fsvc:defaultAttributedTo": "http://djradon.github.io/ns/djradon", - "fsvc:defaultRightsHolder": "https://spectacular-voyage.github.io/ns/spectacular-voyage", + "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/defaultDelegationChain", + "@id": "_service-config/defaultDelegationChain", "@type": "meta:DelegationChain", "meta:hasStep": [ { - "@id": "_service/defaultDelegationChain#1", + "@id": "_service-config/defaultDelegationChain#1", "@type": "meta:DelegationStep", "meta:stepOrder": 1, "prov:agent": { @@ -33,7 +29,7 @@ } }, { - "@id": "_service/defaultDelegationChain#2", + "@id": "_service-config/defaultDelegationChain#2", "@type": "meta:DelegationStep", "meta:stepOrder": 2, "prov:agent": { diff --git a/flow-service/src/config/config-types.ts b/flow-service/src/config/config-types.ts index 6542a93..2bc4855 100644 --- a/flow-service/src/config/config-types.ts +++ b/flow-service/src/config/config-types.ts @@ -5,125 +5,125 @@ * Supports sparse input configurations that are validated against complete defaults. */ -import { NodeObject, ContextDefinition } from '../../../flow-core/src/deps.ts'; +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/'; + 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'; + 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; + 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; + 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; + 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; + 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; + 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; + 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[]; + 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; + "@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; + "@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 { @@ -134,7 +134,7 @@ export interface MeshRootNodeConfigContext { // CLI Options Type export interface ServiceOptions { readonly configPath?: string; - readonly scheme?: 'http' | 'https'; + readonly scheme?: "http" | "https"; readonly port?: number; readonly host?: string; readonly meshPaths?: string[]; @@ -171,7 +171,7 @@ export interface EnvironmentConfig { export class ConfigError extends Error { constructor(message: string, public override readonly cause?: Error) { super(message); - this.name = 'ConfigError'; + this.name = "ConfigError"; } } @@ -182,10 +182,10 @@ export class ConfigValidationError extends ConfigError { cause?: Error, ) { super(message, cause); - this.name = 'ConfigValidationError'; + this.name = "ConfigValidationError"; } } // Utility Types for Configuration Access -export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; +export type LogLevel = "debug" | "info" | "warn" | "error"; diff --git a/flow-service/src/config/defaults.ts b/flow-service/src/config/defaults.ts index 382a363..8bdfe30 100644 --- a/flow-service/src/config/defaults.ts +++ b/flow-service/src/config/defaults.ts @@ -31,7 +31,7 @@ export const DEFAULT_CONTEXT: FlowServiceContext = { }; export const PLATFORM_NODE_DEFAULTS: MeshRootNodeConfig = { - "@id": "local:meshRootNodeConfig", + "@id": "_service-config/meshRootNodeConfig", "@context": DEFAULT_CONTEXT, "@type": "conf:MeshRootNodeConfig", "conf:versioningEnabled": true, @@ -72,32 +72,28 @@ export const SERVICE_NODE_DEFAULTS: Omit< "application/trig", "application/ld+json", ], - "conf:templateMappings": { - "@type": "conf:TemplateMapping", - "conf:hasResourcePageTemplate": "/_assets/_templates/default-resource-page.html", // Service-relative path - }, "conf:generateUnifiedDataset": false, "conf:generateAggregatedDataset": false, }; export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { - "@id": "serviceConfig", + "@id": "_service-config/", "@context": DEFAULT_CONTEXT, "@type": "fsvc:ServiceConfig", "fsvc:scheme": "http", "fsvc:port": 31415, "fsvc:host": "localhost", "fsvc:hasLoggingConfig": { - "@id": "local:ServiceConfig#loggingConfig", + "@id": "_service-config/loggingConfig", "@type": "fsvc:LoggingConfig", "fsvc:hasConsoleChannel": { - "@id": "local:ServiceConfig#consoleChannel", + "@id": "_service-config/loggingConfig/consoleChannel", "@type": "fsvc:LogChannelConfig", "fsvc:logChannelEnabled": true, "fsvc:logLevel": "info", }, "fsvc:hasFileChannel": { - "@id": "local:ServiceConfig#fileChannel", + "@id": "_service-config/loggingConfig/fileChannel", "@type": "fsvc:LogChannelConfig", "fsvc:logChannelEnabled": false, "fsvc:logLevel": "warn", @@ -109,7 +105,7 @@ export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { "fsvc:logRotationInterval": "daily", }, "fsvc:hasSentryChannel": { - "@id": "local:ServiceConfig#sentryChannel", + "@id": "_service-config/loggingConfig/sentryChannel", "@type": "fsvc:LogChannelConfig", "fsvc:logChannelEnabled": false, "fsvc:logLevel": "error", @@ -117,7 +113,7 @@ export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { }, }, "fsvc:hasContainedServices": { - "@id": "local:ServiceConfig#containedServices", + "@id": "_service-config/containedServices", "@type": "fsvc:ContainedServicesConfig", "fsvc:apiEnabled": true, "fsvc:sparqlEnabled": true, @@ -129,7 +125,7 @@ export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { // Development Environment Overrides export const DEVELOPMENT_SERVICE_OVERRIDES: Partial = { - "@id": "local:serviceConfig", + "@id": "_service-config/", "fsvc:hasLoggingConfig": { "@type": "fsvc:LoggingConfig", "fsvc:hasConsoleChannel": { diff --git a/flow-service/src/config/loaders/env-loader.ts b/flow-service/src/config/loaders/env-loader.ts index cbb5c3c..1992965 100644 --- a/flow-service/src/config/loaders/env-loader.ts +++ b/flow-service/src/config/loaders/env-loader.ts @@ -8,9 +8,10 @@ import type { EnvironmentConfig, ServiceConfigInput, -} from '../config-types.ts'; -import { ConfigError } from '../config-types.ts'; -import { validateLogLevel } from '../../../../flow-core/src/platform-constants.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,36 +20,42 @@ import { validateLogLevel } from '../../../../flow-core/src/platform-constants.t * * @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 (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; } } @@ -63,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) { @@ -88,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) { @@ -111,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 @@ -120,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; } } @@ -159,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 @@ -191,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; } } @@ -199,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 @@ -216,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:MeshRootNodeConfig'; - configInput['fsvc:nodeDefaults'] = nodeDefaults; + nodeDefaults["@id"] = "_service-config/meshRootNodeConfig"; + nodeDefaults["@type"] = "conf:MeshRootNodeConfig"; + configInput["fsvc:nodeDefaults"] = nodeDefaults; } - + //console.log(JSON.stringify(configInput)) return configInput; } @@ -245,26 +256,26 @@ export function loadEnvConfig(): ServiceConfigInput { */ function getEnvironmentVariables(): EnvironmentConfig { return { - 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'), + "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"), }; } @@ -281,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; @@ -305,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/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index ea3183e..d10636b 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -3,26 +3,43 @@ import type { ServiceConfigInput, MeshRootNodeConfigInput } from '../config-type import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; import { clearGraph, copyGraph, createNewGraphFromJsonLd } from '../../../../flow-core/src/utils/quadstore-utils.ts'; import { CONFIG_GRAPH_NAMES } from '../index.ts'; +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 loadPlatformDefaults(): Promise { - await createNewGraphFromJsonLd(PLATFORM_SERVICE_DEFAULTS, { graphName: CONFIG_GRAPH_NAMES.platformServiceDefaults }); - await createNewGraphFromJsonLd(PLATFORM_NODE_DEFAULTS, { graphName: CONFIG_GRAPH_NAMES.platformImplicitMeshRootNodeConfig }); +export async function loadPlatformServiceDefaults(): Promise { + + 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 }); +} + +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 { - await createNewGraphFromJsonLd(inputConfig, { graphName: CONFIG_GRAPH_NAMES.inputServiceConfig }); + //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 { - await createNewGraphFromJsonLd(inputMeshRootNodeConfig, { graphName: CONFIG_GRAPH_NAMES.inputMeshRootNodeConfig }); + const uri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.inputMeshRootNodeConfig); + const expandedInputMeshRootNodeConfig = expandRelativeIds(inputMeshRootNodeConfig, uri); + await createNewGraphFromJsonLd(expandedInputMeshRootNodeConfig, { graphName: uri }); } /** @@ -34,20 +51,28 @@ export async function mergeServiceConfigGraphs( 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(CONFIG_GRAPH_NAMES.mergedServiceConfig)); + await clearGraph(df.namedNode(mergedUri)); // Copy input service config quads - await copyGraph(df.namedNode(CONFIG_GRAPH_NAMES.inputServiceConfig), df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig)); + 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(CONFIG_GRAPH_NAMES.platformServiceDefaults)); + 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(CONFIG_GRAPH_NAMES.mergedServiceConfig)); + const exists = await store.countQuads(q.subject, q.predicate, undefined, df.namedNode(mergedUri)); if (exists === 0) { - await store.put(df.quad(q.subject, q.predicate, q.object, df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig))); + 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/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts index cfd9471..790edce 100644 --- a/flow-service/src/config/resolution/service-config-accessor.ts +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -4,6 +4,7 @@ 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; @@ -43,7 +44,7 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { - GRAPH <${CONFIG_GRAPH_NAMES.mergedServiceConfig}> { + GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { ?s fsvc:port ?value . } } LIMIT 1 @@ -56,7 +57,7 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { - GRAPH <${CONFIG_GRAPH_NAMES.mergedServiceConfig}> { + GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { ?s fsvc:host ?value . } } LIMIT 1 @@ -68,7 +69,7 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { - GRAPH <${CONFIG_GRAPH_NAMES.mergedServiceConfig}> { + GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { ?s fsvc:meshPaths ?value . } } diff --git a/flow-service/src/config/resolution/service-config-loader.ts b/flow-service/src/config/resolution/service-config-loader.ts index aaf17d8..528ef60 100644 --- a/flow-service/src/config/resolution/service-config-loader.ts +++ b/flow-service/src/config/resolution/service-config-loader.ts @@ -12,6 +12,7 @@ import { validateLogLevel } from '../../../../flow-core/src/platform-constants.t */ //TODO: dct:conformsTo +// currently unused, see "resolveServiceConfig" export async function loadServiceConfigInput( cliOptions?: ServiceOptions, diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index 4996025..798b9cb 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -23,7 +23,7 @@ import { serviceUriConfigManager, type ServiceUriConfig } from '../../utils/serv * @throws ConfigError if configuration resolution fails or an unexpected error occurs */ -import { loadPlatformDefaults, loadInputServiceConfig, loadInputMeshRootNodeConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.ts'; +import { loadPlatformServiceDefaults, loadInputServiceConfig, loadInputMeshRootNodeConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.ts'; export async function resolveServiceConfig( cliOptions?: ServiceOptions, @@ -34,7 +34,7 @@ export async function resolveServiceConfig( try { // Load environment config const envConfig = loadEnvConfig(); - + //console.log(envConfig) // Load file config if specified let fileConfig: ServiceConfigInput | undefined; if (serviceConfigPath) { @@ -58,8 +58,8 @@ export async function resolveServiceConfig( // Initialize the service URI configuration manager early, before any quadstore operations serviceUriConfigManager.setConfig(serviceUriConfig); - // Load platform defaults into Quadstore graphs (now uses dynamic URIs) - await loadPlatformDefaults(); + // Load platform defaults into Quadstore graphs + await loadPlatformServiceDefaults(); // Load merged input config into Quadstore graph await loadInputServiceConfig(mergedInputConfig); diff --git a/flow-service/src/config/resolution/service-config-validator.ts b/flow-service/src/config/resolution/service-config-validator.ts index c776959..cc98c29 100644 --- a/flow-service/src/config/resolution/service-config-validator.ts +++ b/flow-service/src/config/resolution/service-config-validator.ts @@ -1,8 +1,8 @@ import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; -import { DataFactory } from '../../../../flow-core/src/deps.ts'; import { ConfigError } from '../config-types.ts'; import { CONFIG_GRAPH_NAMES } from '../index.ts'; +import { getCurrentServiceUri } from '../../utils/service-uri-builder.ts'; /** * Validates essential service configuration values in the merged config graph. @@ -10,11 +10,18 @@ import { CONFIG_GRAPH_NAMES } from '../index.ts'; */ export async function validateServiceConfig(): Promise { const { store, df } = defaultQuadstoreBundle; - const graph = df.namedNode(CONFIG_GRAPH_NAMES.mergedServiceConfig); - + 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.`); diff --git a/flow-service/src/utils/service-uri-builder.ts b/flow-service/src/utils/service-uri-builder.ts index f90a774..7463af8 100644 --- a/flow-service/src/utils/service-uri-builder.ts +++ b/flow-service/src/utils/service-uri-builder.ts @@ -22,38 +22,16 @@ export function buildServiceBaseUri(config: ServiceUriConfig): string { ? '' : `:${config.port}`; - return `${config.scheme}://${config.host}${port}`; + return `${config.scheme}://${config.host}${port}/`; } -/** - * Builds a service graph URI for the given graph name - */ -export function buildServiceGraphUri(config: ServiceUriConfig, graphName: string): string { - const baseUri = buildServiceBaseUri(config); - return `${baseUri}/graph/${graphName}`; -} - -/** - * Builds the complete set of config graph names based on service configuration - */ -export function buildConfigGraphNames(config: ServiceUriConfig) { - return { - platformServiceDefaults: buildServiceGraphUri(config, 'platformServiceDefaults'), - platformImplicitMeshRootNodeConfig: buildServiceGraphUri(config, 'platformImplicitMeshRootNodeConfig'), - inputServiceConfig: buildServiceGraphUri(config, 'inputServiceConfig'), - inputMeshRootNodeConfig: buildServiceGraphUri(config, 'inputMeshRootNodeConfig'), - mergedServiceConfig: buildServiceGraphUri(config, 'mergedServiceConfig'), - }; -} - - /** * Builds a service-relative URI for the given path */ export function buildServiceUri(config: ServiceUriConfig, path: string): string { const baseUri = buildServiceBaseUri(config); - const cleanPath = path.startsWith('/') ? path : `/${path}`; - return `${baseUri}${cleanPath}`; + const cleanPath = path.replace(/^\/+/, ''); + return `${baseUri}${cleanPath}/`; } /** @@ -62,13 +40,11 @@ export function buildServiceUri(config: ServiceUriConfig, path: string): string */ class ServiceUriConfigManager { private _config: ServiceUriConfig | null = null; - private _cachedGraphNames: ReturnType | null = null; private _cachedBaseUri: string | null = null; setConfig(config: ServiceUriConfig): void { this._config = config; // Update cached values when config changes - this._cachedGraphNames = buildConfigGraphNames(config); this._cachedBaseUri = buildServiceBaseUri(config); } @@ -79,13 +55,6 @@ class ServiceUriConfigManager { return this._config; } - getConfigGraphNames() { - if (!this._cachedGraphNames) { - throw new Error('Service URI configuration not initialized. Call setConfig() first.'); - } - return this._cachedGraphNames; - } - getServiceBaseUri(): string { if (!this._cachedBaseUri) { throw new Error('Service URI configuration not initialized. Call setConfig() first.'); @@ -99,7 +68,6 @@ class ServiceUriConfigManager { reset(): void { this._config = null; - this._cachedGraphNames = null; this._cachedBaseUri = null; } } @@ -113,11 +81,6 @@ export function getCurrentServiceBaseUri(): string { return serviceUriConfigManager.getServiceBaseUri(); } -export function getCurrentConfigGraphNames() { - return serviceUriConfigManager.getConfigGraphNames(); -} - export function getCurrentServiceUri(path: string): string { return buildServiceUri(serviceUriConfigManager.getConfig(), path); } - From 5046780879efe3642aeca88960c253116653b0e2 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Mon, 4 Aug 2025 17:58:41 -0700 Subject: [PATCH 19/20] removed redundant service-config-loader --- flow-service/src/config/index.ts | 1 - .../src/config/loaders/quadstore-loader.ts | 24 ++++- .../resolution/service-config-loader.ts | 96 ------------------- .../resolution/service-config-resolver.ts | 5 +- 4 files changed, 21 insertions(+), 105 deletions(-) delete mode 100644 flow-service/src/config/resolution/service-config-loader.ts diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index cb1198f..108f0fc 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -81,7 +81,6 @@ 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'; -import { LogContext } from '../../../flow-core/src/utils/logger/types.ts'; /** * Resolves and validates the service configuration. diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index d10636b..206befd 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -2,6 +2,7 @@ import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; import type { ServiceConfigInput, MeshRootNodeConfigInput } from '../config-types.ts'; import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; import { clearGraph, copyGraph, createNewGraphFromJsonLd } from '../../../../flow-core/src/utils/quadstore-utils.ts'; +import { 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"; @@ -10,10 +11,25 @@ import { getCurrentServiceUri } from "../../utils/service-uri-builder.ts"; */ export async function loadPlatformServiceDefaults(): Promise { - 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 }); + 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 { diff --git a/flow-service/src/config/resolution/service-config-loader.ts b/flow-service/src/config/resolution/service-config-loader.ts deleted file mode 100644 index 528ef60..0000000 --- a/flow-service/src/config/resolution/service-config-loader.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { ServiceConfigInput, ServiceOptions } from '../config-types.ts'; -import { getServiceConfigPath, loadEnvConfig } from '../loaders/env-loader.ts'; -import { loadServiceConfig } from '../loaders/jsonld-loader.ts'; -import { mergeConfigs } from '../../utils/merge-configs.ts'; -import { validateLogLevel } from '../../../../flow-core/src/platform-constants.ts'; - -/** - * Loads and merges service configuration input options from CLI, environment variables, and config file. - * - * @param cliOptions - Optional CLI-provided service options - * @returns Merged service configuration input options - */ - -//TODO: dct:conformsTo -// currently unused, see "resolveServiceConfig" - -export async function loadServiceConfigInput( - cliOptions?: ServiceOptions, -): Promise { - // 1. Start with empty input options - let inputOptions: ServiceConfigInput = {}; - - // 2. Merge environment variables (cascading pattern) - const envConfig = loadEnvConfig(); - inputOptions = mergeConfigs(inputOptions, envConfig); - - // 3. Load and merge service config file (cascading pattern) - const serviceConfigPath = getServiceConfigPath(cliOptions?.configPath); - if (serviceConfigPath) { - const fileConfig = await loadServiceConfig(serviceConfigPath); - if (fileConfig) { - inputOptions = mergeConfigs(inputOptions, fileConfig); - } - } - - // 4. Apply CLI overrides (cascading pattern) - if (cliOptions) { - const cliConfig = convertCliOptionsToConfig(cliOptions); - inputOptions = mergeConfigs(inputOptions, cliConfig); - } - - return inputOptions; -} - -/** - * Converts CLI-provided service options into the internal ServiceConfigInput format. - * - * @param cliOptions - The service options provided via the command line interface - * @returns A partial service configuration object representing the CLI options in config format - */ -function convertCliOptionsToConfig( - cliOptions: ServiceOptions, -): ServiceConfigInput { - const config: ServiceConfigInput = {}; - - if (cliOptions.port !== undefined) { - config['fsvc:port'] = cliOptions.port; - } - - if (cliOptions.host !== undefined) { - config['fsvc:host'] = cliOptions.host; - } - - if (cliOptions.meshPaths !== undefined && cliOptions.meshPaths.length > 0) { - config['fsvc:meshPaths'] = cliOptions.meshPaths; - } - - if (cliOptions.logLevel !== undefined) { - config['fsvc:hasLoggingConfig'] = { - '@type': 'fsvc:LoggingConfig', - 'fsvc:hasConsoleChannel': { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': true, - 'fsvc:logLevel': validateLogLevel(cliOptions.logLevel), - }, - }; - } - - if (cliOptions.sentryEnabled !== undefined) { - if (!config['fsvc:hasLoggingConfig']) { - config['fsvc:hasLoggingConfig'] = { - '@type': 'fsvc:LoggingConfig', - }; - } - const loggingConfig = config['fsvc:hasLoggingConfig'] as Record< - string, - unknown - >; - loggingConfig['fsvc:hasSentryChannel'] = { - '@type': 'fsvc:LogChannelConfig', - 'fsvc:logChannelEnabled': cliOptions.sentryEnabled, - }; - } - - return config; -} diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index 798b9cb..4f80fe6 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -4,10 +4,7 @@ import type { } 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 { PLATFORM_SERVICE_DEFAULTS } from '../defaults.ts'; import { ConfigError } from '../config-types.ts'; import { mergeConfigs } from '../../utils/merge-configs.ts'; import { handleCaughtError } from '../../../../flow-core/src/utils/logger/error-handlers.ts'; From 87f16b1b78e5ab46db47890e1a7dd68b97809fa4 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Mon, 4 Aug 2025 18:16:15 -0700 Subject: [PATCH 20/20] refactor: Move quadstore utility functions to a dedicated directory --- flow-core/src/utils/{ => quadstore}/quadstore-utils.ts | 10 +++++----- flow-core/tests/unit/quadstore-utils.test.ts | 2 +- flow-service/src/config/loaders/quadstore-loader.ts | 2 +- .../tests/integration/quadstore-accessor.test.ts | 2 +- flow-service/tests/integration/quadstore-utils.test.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename flow-core/src/utils/{ => quadstore}/quadstore-utils.ts (93%) diff --git a/flow-core/src/utils/quadstore-utils.ts b/flow-core/src/utils/quadstore/quadstore-utils.ts similarity index 93% rename from flow-core/src/utils/quadstore-utils.ts rename to flow-core/src/utils/quadstore/quadstore-utils.ts index 5f137ab..9415559 100644 --- a/flow-core/src/utils/quadstore-utils.ts +++ b/flow-core/src/utils/quadstore/quadstore-utils.ts @@ -1,8 +1,8 @@ -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'; +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) => { diff --git a/flow-core/tests/unit/quadstore-utils.test.ts b/flow-core/tests/unit/quadstore-utils.test.ts index ed30661..d7d9c0a 100644 --- a/flow-core/tests/unit/quadstore-utils.test.ts +++ b/flow-core/tests/unit/quadstore-utils.test.ts @@ -1,5 +1,5 @@ import { assertEquals } from '../../src/deps.ts'; -import { clearGraph, copyGraph, isGraphEmpty, countQuadsInStream, putJsonLdToGraph, createNewGraphFromJsonLd } from '../../src/utils/quadstore-utils.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 () => { diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index 206befd..dbe84c1 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -1,7 +1,7 @@ import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; import type { ServiceConfigInput, MeshRootNodeConfigInput } from '../config-types.ts'; import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.ts'; -import { clearGraph, copyGraph, createNewGraphFromJsonLd } from '../../../../flow-core/src/utils/quadstore-utils.ts'; +import { 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"; diff --git a/flow-service/tests/integration/quadstore-accessor.test.ts b/flow-service/tests/integration/quadstore-accessor.test.ts index 6b89123..a0a349f 100644 --- a/flow-service/tests/integration/quadstore-accessor.test.ts +++ b/flow-service/tests/integration/quadstore-accessor.test.ts @@ -1,6 +1,6 @@ import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts"; import { createQuadstoreBundle } from "../../../flow-core/src/utils/quadstore/quadstore-factory.ts"; -import { copyGraph, clearGraph, putJsonLdToGraph } from "../../../flow-core/src/utils/quadstore-utils.ts"; +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(); diff --git a/flow-service/tests/integration/quadstore-utils.test.ts b/flow-service/tests/integration/quadstore-utils.test.ts index a5c3cc1..68e3680 100644 --- a/flow-service/tests/integration/quadstore-utils.test.ts +++ b/flow-service/tests/integration/quadstore-utils.test.ts @@ -1,7 +1,7 @@ import { assertEquals } from '../../../flow-core/src/deps.ts'; import { MemoryLevel, DataFactory, Quadstore } from '../../../flow-core/src/deps.ts'; import { RDF } from '../../../flow-core/src/deps.ts'; -import { countQuadsInStream, clearGraph, copyGraph } from '../../../flow-core/src/utils/quadstore-utils.ts'; +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();