From 5b6f774b8e04fee35518df2650c8cd7d6186c598 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Tue, 5 Aug 2025 04:26:16 -0700 Subject: [PATCH 01/10] Remove unused dependencies from package.json and add SPARQL utility functions for querying single and multiple values in sparql-utils.ts. --- flow-core/src/utils/sparql-utils.ts | 50 + flow-service/flow-service-config.jsonld | 3 +- .../resolution/service-config-accessor.ts | 69 +- flow-service/src/utils/service-logger.ts | 2 +- flow-service/src/utils/startup-logger.ts | 3 +- package-lock.json | 1712 ----------------- package.json | 3 - 7 files changed, 76 insertions(+), 1766 deletions(-) create mode 100644 flow-core/src/utils/sparql-utils.ts diff --git a/flow-core/src/utils/sparql-utils.ts b/flow-core/src/utils/sparql-utils.ts new file mode 100644 index 0000000..50d023e --- /dev/null +++ b/flow-core/src/utils/sparql-utils.ts @@ -0,0 +1,50 @@ +// Utility functions for SPARQL queries used across the codebase + +import type { QuadstoreBundle } from '../types.ts'; + +export async function querySingleValue( + bundle: QuadstoreBundle, + sparql: string, +): Promise { + if (!bundle.engine) { + throw new Error('SPARQL engine not initialized in Quadstore bundle'); + } + const values: string[] = []; + try { + const bindingsStream = await bundle.engine.queryBindings(sparql, { sources: [bundle.store] }); + for await (const binding of bindingsStream as any) { + const value = binding.get('value'); + if (value) { + values.push(value.value); + } + } + } catch (error) { + throw new Error(`Failed to execute SPARQL query: ${error instanceof Error ? error.message : String(error)}`); + } + if (values.length > 1) { + throw new Error('Expected single result but multiple values were returned'); + } + return values.length === 1 ? values[0] : undefined; +} + +export async function queryMultipleValues( + bundle: QuadstoreBundle, + sparql: string, +): Promise { + if (!bundle.engine) { + throw new Error('SPARQL engine not initialized in Quadstore bundle'); + } + const values: string[] = []; + try { + const bindingsStream = await bundle.engine.queryBindings(sparql, { sources: [bundle.store] }); + for await (const binding of bindingsStream as any) { + const value = binding.get('value'); + if (value) { + values.push(value.value); + } + } + } catch (error) { + throw new Error(`Failed to execute SPARQL query: ${error instanceof Error ? error.message : String(error)}`); + } + return values; +} diff --git a/flow-service/flow-service-config.jsonld b/flow-service/flow-service-config.jsonld index 383e635..57e46e2 100644 --- a/flow-service/flow-service-config.jsonld +++ b/flow-service/flow-service-config.jsonld @@ -10,7 +10,8 @@ "fsvc:host": "localhost", "fsvc:port": 8080, "fsvc:meshPaths": [ - "../meshes/test-ns/" + "../meshes/test-ns/", + "../meshes/ns/" ], "fsvc:defaultLicense": "https://creativecommons.org/licenses/by-sa/4.0/", "fsvc:defaultAttributedTo": "http://djradon.github.io/ns/djradon", diff --git a/flow-service/src/config/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts index 790edce..d65d26e 100644 --- a/flow-service/src/config/resolution/service-config-accessor.ts +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -1,43 +1,15 @@ + import type { QuadstoreBundle } from '../../../../flow-core/src/types.ts'; import { defaultQuadstoreBundle } from '../../quadstore-default-bundle.ts'; -import { DataFactory } from '../../../../flow-core/src/deps.ts'; import { CONFIG_GRAPH_NAMES } from '../index.ts'; -import { handleCaughtError } from '../../../../flow-core/src/utils/logger/error-handlers.ts'; -import { createServiceLogContext } from '../../utils/service-log-context.ts'; import { getCurrentServiceUri } from '../../utils/service-uri-builder.ts'; +import { querySingleValue, queryMultipleValues } from '../../../../flow-core/src/utils/sparql-utils.ts'; export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { private bundle: QuadstoreBundle; - private df: DataFactory; constructor(bundle: QuadstoreBundle = defaultQuadstoreBundle) { this.bundle = bundle; - this.df = bundle.df; - } - - - private async querySingleValue(sparql: string): Promise { - if (!this.bundle.engine) { - throw new Error('SPARQL engine not initialized in Quadstore bundle'); - } - try { - const bindingsStream = await this.bundle.engine.queryBindings(sparql, { sources: [this.bundle.store] }); - for await (const binding of bindingsStream as any) { - const value = binding.get('value'); - if (value) { - return value.value; - } - } - } catch (error) { - const context = createServiceLogContext({ - operation: 'config-query', - component: 'service-config-accessor', - metadata: { sparql } - }); - await handleCaughtError(error, 'Failed to execute SPARQL query', context); - throw new Error('Failed to execute SPARQL query'); - } - return undefined; } async getPort(): Promise { @@ -47,9 +19,9 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { ?s fsvc:port ?value . } - } LIMIT 1 + } `; - const result = await this.querySingleValue(sparql); + const result = await querySingleValue(this.bundle, sparql); return result ? Number(result) : undefined; } @@ -60,9 +32,21 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { ?s fsvc:host ?value . } - } LIMIT 1 + } + `; + return await querySingleValue(this.bundle, sparql); + } + + async getScheme(): Promise { + const sparql = ` + PREFIX fsvc: + SELECT ?value WHERE { + GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { + ?s fsvc:scheme ?value . + } + } `; - return this.querySingleValue(sparql); + return await querySingleValue(this.bundle, sparql); } async getMeshPaths(): Promise { @@ -74,19 +58,8 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { } } `; - 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; + return await queryMultipleValues(this.bundle, sparql); } - - // Additional getters for other config values can be implemented similarly })(); + +// Additional getters for other config values can be implemented similarly diff --git a/flow-service/src/utils/service-logger.ts b/flow-service/src/utils/service-logger.ts index bbfec4e..0d5a322 100644 --- a/flow-service/src/utils/service-logger.ts +++ b/flow-service/src/utils/service-logger.ts @@ -134,7 +134,7 @@ export function createMeshLogger( /** * Create a logger with configuration operation context * @param configPath - Path to configuration file - * @param configType - Type of configuration (e.g., 'service', 'mesh', 'env') + * @param configType - Type of configuration (e.g., 'service', 'node') * @param validationStage - Current validation stage * @returns Logger instance with config context */ diff --git a/flow-service/src/utils/startup-logger.ts b/flow-service/src/utils/startup-logger.ts index e55d5ab..b124b6b 100644 --- a/flow-service/src/utils/startup-logger.ts +++ b/flow-service/src/utils/startup-logger.ts @@ -62,7 +62,8 @@ export async function logStartupConfiguration(): Promise { export async function logStartupUrls(): Promise { const host = await config.getHost(); const port = await config.getPort(); - const baseUrl = `http://${host}:${port}`; + const scheme = await config.getScheme() || 'http'; + const baseUrl = `${scheme}://${host}:${port}`; console.log(`📍 Root: ${baseUrl}/`); console.log(`� API documentation: ${baseUrl}${MESH.API_PORTAL_ROUTE}`); diff --git a/package-lock.json b/package-lock.json index 18fb136..e69de29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1712 +0,0 @@ -{ - "name": "sflow-platform", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "quadstore": "^15.4.0-beta.0" - }, - "devDependencies": { - "@typescript-eslint/parser": "^8.38.0", - "eslint": "^9.32.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", - "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "9.32.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", - "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", - "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.15.1", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@rdfjs/types": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-2.0.1.tgz", - "integrity": "sha512-uyAzpugX7KekAXAHq26m3JlUIZJOC0uSBhpnefGV5i15bevDyyejoB7I+9MKeUrzXD8OOUI3+4FeV1wwQr5ihA==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", - "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.8.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", - "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/typescript-estree": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", - "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.38.0", - "@typescript-eslint/types": "^8.38.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", - "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", - "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", - "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", - "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.38.0", - "@typescript-eslint/tsconfig-utils": "8.38.0", - "@typescript-eslint/types": "8.38.0", - "@typescript-eslint/visitor-keys": "8.38.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", - "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.38.0", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/abstract-level": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-3.1.0.tgz", - "integrity": "sha512-j2e+TsAxy7Ri+0h7dJqwasymgt0zHBWX4+nMk3XatyuqgHfdstBJ9wsMfbiGwE1O+QovRyPcVAqcViMYdyPaaw==", - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "is-buffer": "^2.0.5", - "level-supports": "^6.2.0", - "level-transcoder": "^1.0.1", - "maybe-combine-errors": "^1.0.0", - "module-error": "^1.0.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/asynciterator": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/asynciterator/-/asynciterator-3.9.0.tgz", - "integrity": "sha512-bwLLTAnoE6Ap6XdjK/j8vDk2Vi9p3ojk0PFwM0SwktAG1k8pfRJF9ng+mmkaRFKdZCQQlOxcWnvOmX2NQ1HV0g==", - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", - "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.15.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.32.0", - "@eslint/plugin-kit": "^0.3.4", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-sorted-set": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/js-sorted-set/-/js-sorted-set-0.7.0.tgz", - "integrity": "sha512-NGTSMeoLNYR2BoTLhQ6w+u7Ox4PO34omb/0OBCy4gyedWeXolMGv948Ato0/Of6tfxsAjeySDymCkAj93/xkeA==", - "license": "Public Domain" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/level-supports": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-6.2.0.tgz", - "integrity": "sha512-QNxVXP0IRnBmMsJIh+sb2kwNCYcKciQZJEt+L1hPCHrKNELllXhvrlClVHXBYZVT+a7aTSM6StgNXdAldoab3w==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/level-transcoder": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", - "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "module-error": "^1.0.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/maybe-combine-errors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/maybe-combine-errors/-/maybe-combine-errors-1.0.0.tgz", - "integrity": "sha512-eefp6IduNPT6fVdwPp+1NgD0PML1NU5P6j1Mj5nz1nidX8/sWY7119WL8vTAHgqfsY74TzW0w1XPgdYEKkGZ5A==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/module-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", - "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/quadstore": { - "version": "15.4.0-beta.0", - "resolved": "https://registry.npmjs.org/quadstore/-/quadstore-15.4.0-beta.0.tgz", - "integrity": "sha512-RFDcXdLxlXtqFoSGFbgc+1xmJQu9g3E/fyMFeCGaVTxftDqfkNj6BuatWCF74moAUBG/EtbhqvytJKm1IGny5w==", - "license": "MIT", - "dependencies": { - "@rdfjs/types": "^2.0.1", - "abstract-level": "^3.1.0", - "asynciterator": "^3.9.0", - "js-sorted-set": "^0.7.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "license": "MIT" - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/package.json b/package.json index 1fada90..493f0d3 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,5 @@ "devDependencies": { "@typescript-eslint/parser": "^8.38.0", "eslint": "^9.32.0" - }, - "dependencies": { - "quadstore": "^15.4.0-beta.0" } } From 92585c19a272dc1c484cfd53c6d1afe372e2c03e Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Tue, 5 Aug 2025 12:50:30 -0700 Subject: [PATCH 02/10] Refactor service configuration accessors and enhance logging capabilities --- .../src/utils/quadstore/quadstore-utils.ts | 7 +- flow-core/src/utils/rdfjs-utils.ts | 23 +++++ flow-service/flow-service-config.jsonld | 1 - flow-service/src/config/config-types.ts | 3 +- flow-service/src/config/defaults.ts | 9 +- flow-service/src/config/loaders/env-loader.ts | 11 +-- .../src/config/loaders/quadstore-loader.ts | 35 ++++--- .../resolution/service-config-accessor.ts | 98 ++++++++++++++++--- .../resolution/service-config-resolver.ts | 20 ++-- flow-service/src/utils/startup-logger.ts | 35 ++++++- 10 files changed, 180 insertions(+), 62 deletions(-) diff --git a/flow-core/src/utils/quadstore/quadstore-utils.ts b/flow-core/src/utils/quadstore/quadstore-utils.ts index 9415559..638faad 100644 --- a/flow-core/src/utils/quadstore/quadstore-utils.ts +++ b/flow-core/src/utils/quadstore/quadstore-utils.ts @@ -41,7 +41,7 @@ export async function clearGraph( const count = await countQuadsInStream(matchStream); //console.log(`Number of quads before delStream in graph ${graph.value}: ${count}`); const matchStream2 = store.match(undefined, undefined, undefined, graph); - await store.delStream(matchStream2); + await store.delStream(matchStream2 as any); /*const matchStream3 = store.match(undefined, undefined, undefined, graph); const count2 = await countQuadsInStream(matchStream3); console.log(`Number of quads in graph ${graph.value}: ${count2}`); @@ -62,8 +62,9 @@ export async function copyGraph( ): Promise { const stream = store.match(undefined, undefined, undefined, sourceGraph); const quads = []; - for await (const q of stream) { - //console.log(`Copying quad: ${q.subject.value} ${q.predicate.value} ${q.object.value} to graph ${targetGraph.value}`); + console.log("COPIED QUADS:"); + for await (const q of stream as any) { + console.log(`${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); } diff --git a/flow-core/src/utils/rdfjs-utils.ts b/flow-core/src/utils/rdfjs-utils.ts index 674d3fd..6834b98 100644 --- a/flow-core/src/utils/rdfjs-utils.ts +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -26,6 +26,29 @@ export async function jsonldToQuads( ); } +export function relativeizeIds(inputQuads: RDF.Quad[], baseIRI: string): RDF.Quad[] { + // 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}`); + } + + return inputQuads.map(quad => { + const subject = quad.subject.value.replace(baseIRI, ''); + const predicate = quad.predicate.value.replace(baseIRI, ''); + const object = quad.object.value.replace(baseIRI, ''); + const graph = quad.graph ? quad.graph.value.replace(baseIRI, '') : undefined; + + return { + '@id': subject, + [predicate]: object, + '@graph': graph, + }; + }); +} + export function expandRelativeIds(inputJsonLd: NodeObject, baseIRI: string): any { // Validate baseIRI format if (!baseIRI.startsWith("http://") && !baseIRI.startsWith("https://")) { diff --git a/flow-service/flow-service-config.jsonld b/flow-service/flow-service-config.jsonld index 57e46e2..57d2012 100644 --- a/flow-service/flow-service-config.jsonld +++ b/flow-service/flow-service-config.jsonld @@ -8,7 +8,6 @@ "@type": "fsvc:ServiceConfig", "fsvc:scheme": "http", "fsvc:host": "localhost", - "fsvc:port": 8080, "fsvc:meshPaths": [ "../meshes/test-ns/", "../meshes/ns/" diff --git a/flow-service/src/config/config-types.ts b/flow-service/src/config/config-types.ts index 2bc4855..81b672b 100644 --- a/flow-service/src/config/config-types.ts +++ b/flow-service/src/config/config-types.ts @@ -24,7 +24,6 @@ export interface LogChannelConfig extends NodeObject { 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; @@ -83,6 +82,8 @@ export interface ServiceConfig extends NodeObject { readonly "fsvc:rootMeshRootNodeConfigTemplate"?: MeshRootNodeConfig; readonly "fsvc:defaultDelegationChain"?: DelegationChain; readonly "fsvc:defaultAttributedTo"?: string; + readonly "fsvc:defaultRights"?: string[]; + readonly "fsvc:defaultRightsHolder"?: string; } // Delegation Chain Configuration diff --git a/flow-service/src/config/defaults.ts b/flow-service/src/config/defaults.ts index 8bdfe30..e5d65d2 100644 --- a/flow-service/src/config/defaults.ts +++ b/flow-service/src/config/defaults.ts @@ -108,8 +108,7 @@ export const PLATFORM_SERVICE_DEFAULTS: ServiceConfig = { "@id": "_service-config/loggingConfig/sentryChannel", "@type": "fsvc:LogChannelConfig", "fsvc:logChannelEnabled": false, - "fsvc:logLevel": "error", - "fsvc:sentryLoggingEnabled": true, + "fsvc:logLevel": "error" }, }, "fsvc:hasContainedServices": { @@ -147,8 +146,7 @@ export const DEVELOPMENT_SERVICE_OVERRIDES: Partial = { "fsvc:hasSentryChannel": { "@type": "fsvc:LogChannelConfig", "fsvc:logChannelEnabled": true, - "fsvc:logLevel": "warn", - "fsvc:sentryLoggingEnabled": true, + "fsvc:logLevel": "warn" }, }, }; @@ -177,8 +175,7 @@ export const PRODUCTION_SERVICE_OVERRIDES: Partial = { "fsvc:hasSentryChannel": { "@type": "fsvc:LogChannelConfig", "fsvc:logChannelEnabled": true, // Enable Sentry in production - "fsvc:logLevel": "error", - "fsvc:sentryLoggingEnabled": true, + "fsvc:logLevel": "error" }, }, }; diff --git a/flow-service/src/config/loaders/env-loader.ts b/flow-service/src/config/loaders/env-loader.ts index 1992965..56e9f36 100644 --- a/flow-service/src/config/loaders/env-loader.ts +++ b/flow-service/src/config/loaders/env-loader.ts @@ -162,8 +162,8 @@ export function loadEnvConfig(): ServiceConfigInput { } // Sentry logging - if (env.FLOW_SENTRY_ENABLED) { - const enabled = parseBoolean(env.FLOW_SENTRY_ENABLED); + if (env.FLOW_SENTRY_LOGGING_ENABLED) { + const enabled = parseBoolean(env.FLOW_SENTRY_LOGGING_ENABLED); if (enabled !== undefined) { const sentryChannel: Record = { "@id": "_service-config/loggingConfig/sentryChannel", @@ -175,13 +175,6 @@ export function loadEnvConfig(): ServiceConfigInput { 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; - } - } - loggingConfig["fsvc:hasSentryChannel"] = sentryChannel; hasLoggingConfig = true; } diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index dbe84c1..aacabb4 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -14,6 +14,7 @@ export async function loadPlatformServiceDefaults(): Promise { try { const uri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.platformServiceDefaults); const expandedPlatformServiceDefaults = expandRelativeIds(PLATFORM_SERVICE_DEFAULTS, uri); + //console.log(`Loading platform service defaults into graph:\n ${JSON.stringify(expandedPlatformServiceDefaults)}`); // use the Service URI for the graph name await createNewGraphFromJsonLd(expandedPlatformServiceDefaults, { graphName: uri }); } catch (error) { @@ -39,14 +40,19 @@ export async function loadPlatformImplicitMeshRootNodeConfig(): Promise { } /** - * Load input service config into Quadstore graph + * Load input service config into Quadstore graphs (both into inputServiceConfig and as a base for mergedServiceConfig) */ export async function loadInputServiceConfig(inputConfig: ServiceConfigInput): Promise { //console.log(inputConfig); - const uri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.inputServiceConfig); - const expandedInputServiceConfig = expandRelativeIds(inputConfig, uri); + const inputUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.inputServiceConfig); + const mergedUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig); + + const expandedInputServiceConfig = expandRelativeIds(inputConfig, inputUri); //console.log(expandedInputServiceConfig) - await createNewGraphFromJsonLd(expandedInputServiceConfig, { graphName: uri }); + await createNewGraphFromJsonLd(expandedInputServiceConfig, { graphName: inputUri }); + + const expandedMergedServiceConfig = expandRelativeIds(inputConfig, mergedUri); + await createNewGraphFromJsonLd(expandedMergedServiceConfig, { graphName: mergedUri }); } /** @@ -68,27 +74,26 @@ export async function mergeServiceConfigGraphs( } = defaultQuadstoreBundle ): Promise { - const inputUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.inputServiceConfig); const mergedUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig); const defaultUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.platformServiceDefaults); - // Clear merged graph - await clearGraph(df.namedNode(mergedUri)); - - // Copy input service config quads - await copyGraph(df.namedNode(inputUri), df.namedNode(mergedUri)); // Copy platform service defaults quads if not overridden const platformQuads = store.match(undefined, undefined, undefined, df.namedNode(defaultUri)); + let platformQuadsCopied = 0; for await (const q of platformQuads) { - const exists = await store.countQuads(q.subject, q.predicate, undefined, df.namedNode(mergedUri)); - if (exists === 0) { - console.log(`platform quad copied: ${q.subject.value} ${q.predicate.value} ${q.object.value} in graph ${mergedUri}`); + const existingQuads = await store.get({ subject: q.subject, predicate: q.predicate, object: undefined, graph: df.namedNode(mergedUri) }); + //console.log(`${existingQuads.items}\n`) + + if (existingQuads.items.length === 0) { + //console.log(`platform quad copied: ${q.subject.value} ${q.predicate.value} ${q.object.value} in graph ${mergedUri}`); await store.put(df.quad(q.subject, q.predicate, q.object, df.namedNode(mergedUri))); + platformQuadsCopied++; + } else if (existingQuads.items.length > 1) { + console.error(`Multiple quads found for ${q.subject.value} ${q.predicate.value} in graph ${mergedUri}. This may indicate a configuration error.`); } } - - + //console.log(`platform quads copied: ${platformQuadsCopied}`); } diff --git a/flow-service/src/config/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts index d65d26e..7a9a515 100644 --- a/flow-service/src/config/resolution/service-config-accessor.ts +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -5,60 +5,130 @@ import { CONFIG_GRAPH_NAMES } from '../index.ts'; import { getCurrentServiceUri } from '../../utils/service-uri-builder.ts'; import { querySingleValue, queryMultipleValues } from '../../../../flow-core/src/utils/sparql-utils.ts'; + export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { private bundle: QuadstoreBundle; + private initialized: boolean = false; constructor(bundle: QuadstoreBundle = defaultQuadstoreBundle) { this.bundle = bundle; } - async getPort(): Promise { + isInitialized(): boolean { + return this.initialized; + } + + setInitialized(value: boolean): void { + this.initialized = value; + } + + async getConfigValue(property: string): Promise { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { - ?s fsvc:port ?value . + ?s ${property} ?value . } } `; - const result = await querySingleValue(this.bundle, sparql); - return result ? Number(result) : undefined; + return await querySingleValue(this.bundle, sparql); } - async getHost(): Promise { + async getMultipleConfigValues(property: string): Promise { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { - ?s fsvc:host ?value . + ?s ${property} ?value . } } `; - return await querySingleValue(this.bundle, sparql); + return await queryMultipleValues(this.bundle, sparql); + } + + async getPort(): Promise { + const result = await this.getConfigValue('fsvc:port'); + return result ? Number(result) : undefined; + } + + async getHost(): Promise { + return this.getConfigValue('fsvc:host'); } async getScheme(): Promise { + return this.getConfigValue('fsvc:scheme'); + } + + async getMeshPaths(): Promise { + return this.getMultipleConfigValues('fsvc:meshPaths'); + } + + // Custom accessors for logging channels + + async getConsoleLoggingConfig(): Promise<{ enabled: boolean; level?: string }> { const sparql = ` PREFIX fsvc: - SELECT ?value WHERE { + SELECT ?enabled ?level WHERE { GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { - ?s fsvc:scheme ?value . + ?s fsvc:hasConsoleChannel ?channel . + ?channel fsvc:logChannelEnabled ?enabled . + OPTIONAL { ?channel fsvc:logLevel ?level . } } } `; - return await querySingleValue(this.bundle, sparql); + const enabledStr = await this.getConfigValueFromSparql(sparql, 'enabled'); + const level = await this.getConfigValueFromSparql(sparql, 'level'); + return { enabled: enabledStr === 'true', level: level ?? undefined }; } - async getMeshPaths(): Promise { + async getFileLoggingConfig(): Promise<{ enabled: boolean; level?: string }> { const sparql = ` PREFIX fsvc: - SELECT ?value WHERE { + SELECT ?enabled ?level WHERE { GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { - ?s fsvc:meshPaths ?value . + ?s fsvc:hasFileChannel ?channel . + ?channel fsvc:logChannelEnabled ?enabled . + OPTIONAL { ?channel fsvc:logLevel ?level . } } } `; - return await queryMultipleValues(this.bundle, sparql); + const enabledStr = await this.getConfigValueFromSparql(sparql, 'enabled'); + const level = await this.getConfigValueFromSparql(sparql, 'level'); + return { enabled: enabledStr === 'true', level: level ?? undefined }; + } + + async getSentryLoggingConfig(): Promise<{ enabled: boolean; level?: string }> { + const sparql = ` + PREFIX fsvc: + SELECT ?enabled ?level WHERE { + GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { + ?s fsvc:hasSentryChannel ?channel . + ?channel fsvc:logChannelEnabled ?enabled . + OPTIONAL { ?channel fsvc:logLevel ?level . } + } + } + `; + const enabledStr = await this.getConfigValueFromSparql(sparql, 'enabled'); + const level = await this.getConfigValueFromSparql(sparql, 'level'); + return { enabled: enabledStr === 'true', level: level ?? undefined }; + } + + private async getConfigValueFromSparql(sparql: string, variable: string): Promise { + if (!this.bundle.engine) { + throw new Error('SPARQL engine not initialized in Quadstore bundle'); + } + try { + const bindingsStream = await this.bundle.engine.queryBindings(sparql, { sources: [this.bundle.store] }); + for await (const binding of bindingsStream as any) { + const value = binding.get(variable); + if (value) { + return value.value; + } + } + } catch (error) { + throw new Error(`Failed to execute SPARQL query: ${error instanceof Error ? error.message : String(error)}`); + } + 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 4f80fe6..029430e 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -21,6 +21,7 @@ import { serviceUriConfigManager, type ServiceUriConfig } from '../../utils/serv */ import { loadPlatformServiceDefaults, loadInputServiceConfig, loadInputMeshRootNodeConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.ts'; +import { singletonServiceConfigAccessor } from "./service-config-accessor.ts"; export async function resolveServiceConfig( cliOptions?: ServiceOptions, @@ -46,6 +47,7 @@ export async function resolveServiceConfig( const mergedInputConfig = mergeConfigs(mergeConfigs(envConfig, fileConfig ?? {}), cliConfig); // Extract service URI configuration from merged config, using platform defaults as fallback + // TODO: const serviceUriConfig: ServiceUriConfig = { scheme: mergedInputConfig['fsvc:scheme'] ?? PLATFORM_SERVICE_DEFAULTS['fsvc:scheme'], host: mergedInputConfig['fsvc:host'] ?? PLATFORM_SERVICE_DEFAULTS['fsvc:host'], @@ -80,16 +82,18 @@ export async function resolveServiceConfig( if (error instanceof ConfigError) { await handleCaughtError(error, `Service configuration resolution failed`, context); throw error; + } else { + 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( + `Failed to resolve service configuration: ${errorMessage}`, + cause, + ); } - - 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( - `Failed to resolve service configuration: ${errorMessage}`, - cause, - ); } + // mark the singletonServiceConfigAccessor as initialized + singletonServiceConfigAccessor.setInitialized(true); } /** diff --git a/flow-service/src/utils/startup-logger.ts b/flow-service/src/utils/startup-logger.ts index b124b6b..8215251 100644 --- a/flow-service/src/utils/startup-logger.ts +++ b/flow-service/src/utils/startup-logger.ts @@ -8,12 +8,19 @@ 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'; + /** * Logs the service startup configuration details with a timestamp in US locale. * * Outputs mesh paths, logging levels, and feature enablement statuses to the console for the provided configuration. */ export async function logStartupConfiguration(): Promise { + if (!config.isInitialized()) { + console.error('⚠️ Service configuration is not initialized. Skipping startup logging.'); + Deno.exit(0); + } + + const now = new Date(); const timestamp = now.toLocaleDateString('en-US', { year: 'numeric', @@ -39,10 +46,28 @@ export async function logStartupConfiguration(): Promise { console.log(` Mesh Paths: none configured`); } - // 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`); + + // Use custom config accessors for logging channels + try { + const consoleConfig = await config.getConsoleLoggingConfig(); + console.log(` Console Logging: ${consoleConfig.enabled ? (consoleConfig.level ?? 'enabled') : 'disabled'}`); + } catch { + console.log(` Console Logging: error fetching config`); + } + + try { + const fileConfig = await config.getFileLoggingConfig(); + console.log(` File Logging: ${fileConfig.enabled ? (fileConfig.level ?? 'enabled') : 'disabled'}`); + } catch { + console.log(` File Logging: error fetching config`); + } + + try { + const sentryConfig = await config.getSentryLoggingConfig(); + console.log(` Sentry Logging: ${sentryConfig.enabled ? (sentryConfig.level ?? 'enabled') : 'disabled'}`); + } catch { + console.log(` Sentry Logging: error fetching config`); + } const enabledServices: string[] = []; // TODO: Query service enablement flags from config @@ -66,5 +91,5 @@ export async function logStartupUrls(): Promise { const baseUrl = `${scheme}://${host}:${port}`; console.log(`📍 Root: ${baseUrl}/`); - console.log(`� API documentation: ${baseUrl}${MESH.API_PORTAL_ROUTE}`); + console.log(`📍 API documentation: ${baseUrl}${MESH.API_PORTAL_ROUTE}`); } From 8b1fe6de7c5c4d4ed9dd6625752d2beb095e4875 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Tue, 5 Aug 2025 13:01:14 -0700 Subject: [PATCH 03/10] renamed logger/types.ts to logger/logger-types.ts --- flow-core/src/utils/logger/error-handlers.ts | 2 +- flow-core/src/utils/logger/formatters.ts | 2 +- flow-core/src/utils/logger/index.ts | 6 +++--- flow-core/src/utils/logger/{types.ts => logger-types.ts} | 0 flow-core/src/utils/logger/sentry-logger.ts | 2 +- flow-core/src/utils/logger/structured-logger.ts | 2 +- flow-core/tests/integration/logger/error-handlers.test.ts | 2 +- flow-core/tests/integration/logger/formatters.test.ts | 2 +- flow-service/main.ts | 2 +- .../src/config/resolution/service-config-resolver.ts | 2 +- flow-service/src/utils/service-log-context.ts | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) rename flow-core/src/utils/logger/{types.ts => logger-types.ts} (100%) diff --git a/flow-core/src/utils/logger/error-handlers.ts b/flow-core/src/utils/logger/error-handlers.ts index 2a3362a..8611296 100644 --- a/flow-core/src/utils/logger/error-handlers.ts +++ b/flow-core/src/utils/logger/error-handlers.ts @@ -4,7 +4,7 @@ * better error tracking and debugging across the platform. */ -import type { LogContext, ErrorHandlingOptions } from './types.ts'; +import type { LogContext, ErrorHandlingOptions } from './logger-types.ts'; import { createEnhancedLogger } from './structured-logger.ts'; import { mergeLogContext } from './formatters.ts'; diff --git a/flow-core/src/utils/logger/formatters.ts b/flow-core/src/utils/logger/formatters.ts index 3abd4f5..7066a7f 100644 --- a/flow-core/src/utils/logger/formatters.ts +++ b/flow-core/src/utils/logger/formatters.ts @@ -3,7 +3,7 @@ * Provides consistent formatting for console output, file logging, and structured data. */ -import type { LogContext } from './types.ts'; +import type { LogContext } from './logger-types.ts'; /** * Log level type using string literals diff --git a/flow-core/src/utils/logger/index.ts b/flow-core/src/utils/logger/index.ts index ce91a22..242d5a2 100644 --- a/flow-core/src/utils/logger/index.ts +++ b/flow-core/src/utils/logger/index.ts @@ -10,9 +10,9 @@ export type { LoggerConfig, ErrorSeverity, ErrorHandlingOptions, -} from './types.ts'; +} from './logger-types.ts'; -export { LogLevel } from './types.ts'; +export { LogLevel } from './logger-types.ts'; // Export formatting utilities export type { LogLevel as LogLevelString } from './formatters.ts'; @@ -60,7 +60,7 @@ export { } from './error-handlers.ts'; // Import types for function signatures -import type { StructuredLogger } from './types.ts'; +import type { StructuredLogger } from './logger-types.ts'; import type { EnhancedStructuredLogger } from './structured-logger.ts'; import { createLogger, createEnhancedLogger } from './structured-logger.ts'; diff --git a/flow-core/src/utils/logger/types.ts b/flow-core/src/utils/logger/logger-types.ts similarity index 100% rename from flow-core/src/utils/logger/types.ts rename to flow-core/src/utils/logger/logger-types.ts diff --git a/flow-core/src/utils/logger/sentry-logger.ts b/flow-core/src/utils/logger/sentry-logger.ts index 228bd7a..d300783 100644 --- a/flow-core/src/utils/logger/sentry-logger.ts +++ b/flow-core/src/utils/logger/sentry-logger.ts @@ -4,7 +4,7 @@ */ import { Sentry } from '../../deps.ts'; -import type { LogContext } from './types.ts'; +import type { LogContext } from './logger-types.ts'; import type { LogLevel } from './formatters.ts'; import { extractErrorContext } from './formatters.ts'; diff --git a/flow-core/src/utils/logger/structured-logger.ts b/flow-core/src/utils/logger/structured-logger.ts index df70804..9138193 100644 --- a/flow-core/src/utils/logger/structured-logger.ts +++ b/flow-core/src/utils/logger/structured-logger.ts @@ -3,7 +3,7 @@ * Integrates console output, file logging, and Sentry error reporting with structured context. */ -import type { LogContext, StructuredLogger, LoggerConfig } from './types.ts'; +import type { LogContext, StructuredLogger, LoggerConfig } from './logger-types.ts'; import type { LogLevel } from './formatters.ts'; import { formatConsoleMessage, diff --git a/flow-core/tests/integration/logger/error-handlers.test.ts b/flow-core/tests/integration/logger/error-handlers.test.ts index 631fb7c..69c38a5 100644 --- a/flow-core/tests/integration/logger/error-handlers.test.ts +++ b/flow-core/tests/integration/logger/error-handlers.test.ts @@ -10,7 +10,7 @@ import { handleError, createDefaultEnhancedLogger } from '../../../src/utils/logger/index.ts'; -import type { LogContext } from '../../../src/utils/logger/types.ts'; +import type { LogContext } from '../../../src/utils/logger/logger-types.ts'; console.log('🧪 Testing handleCaughtError and handleError functions with LogContext...\n'); diff --git a/flow-core/tests/integration/logger/formatters.test.ts b/flow-core/tests/integration/logger/formatters.test.ts index df382ff..062a4ae 100644 --- a/flow-core/tests/integration/logger/formatters.test.ts +++ b/flow-core/tests/integration/logger/formatters.test.ts @@ -10,7 +10,7 @@ import { createContextSummary, mergeLogContext } from '../../../src/utils/logger/index.ts'; -import type { LogContext } from '../../../src/utils/logger/types.ts'; +import type { LogContext } from '../../../src/utils/logger/logger-types.ts'; console.log('🧪 Testing flow-core formatting functions...\n'); diff --git a/flow-service/main.ts b/flow-service/main.ts index ce28b43..b88d1fc 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -8,7 +8,7 @@ import { logStartupUrls, } from './src/utils/startup-logger.ts'; import { handleCaughtError } from '../flow-core/src/utils/logger/error-handlers.ts'; -import type { LogContext } from '../flow-core/src/utils/logger/types.ts'; +import type { LogContext } from '../flow-core/src/utils/logger/logger-types.ts'; import { createServiceLogContext } from './src/utils/service-log-context.ts'; import { MESH } from '../flow-core/src/mesh-constants.ts'; diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index 029430e..f518c80 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -8,7 +8,7 @@ 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'; -import { LogContext } from '../../../../flow-core/src/utils/logger/types.ts'; +import { LogContext } from '../../../../flow-core/src/utils/logger/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'; diff --git a/flow-service/src/utils/service-log-context.ts b/flow-service/src/utils/service-log-context.ts index da79a42..8b4e7a7 100644 --- a/flow-service/src/utils/service-log-context.ts +++ b/flow-service/src/utils/service-log-context.ts @@ -1,4 +1,4 @@ -import type { LogContext } from '../../../flow-core/src/utils/logger/types.ts'; +import type { LogContext } from '../../../flow-core/src/utils/logger/logger-types.ts'; import { FLOW_SERVICE_VERSION, FLOW_SERVICE_NAME } from '../service-constants.ts'; /** From 236a2f277406e65e7657e25fdeea1d167281d98b Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Wed, 6 Aug 2025 15:54:09 -0700 Subject: [PATCH 04/10] Enhance logging capabilities by adding SPARQL context support and refactoring logger configuration management; standardized LogLevel --- deno.lock | 3 +- flow-core/src/platform-constants.ts | 4 +- .../src/utils/logger/component-logger.ts | 53 +++++++++++++++++++ flow-core/src/utils/logger/formatters.ts | 37 ++++++------- flow-core/src/utils/logger/index.ts | 6 +-- flow-core/src/utils/logger/logger-types.ts | 32 +++++++---- flow-core/src/utils/logger/sentry-logger.ts | 13 +++-- flow-service/main.ts | 8 +++ flow-service/src/config/config-types.ts | 3 +- flow-service/src/utils/service-logger.ts | 5 +- .../logging/formatting-direct.test.ts | 10 ++-- 11 files changed, 121 insertions(+), 53 deletions(-) create mode 100644 flow-core/src/utils/logger/component-logger.ts diff --git a/deno.lock b/deno.lock index 8703db5..c6ef000 100644 --- a/deno.lock +++ b/deno.lock @@ -138,8 +138,7 @@ "packageJson": { "dependencies": [ "npm:@typescript-eslint/parser@^8.38.0", - "npm:eslint@^9.32.0", - "npm:quadstore@^15.4.0-beta.0" + "npm:eslint@^9.32.0" ] } } diff --git a/flow-core/src/platform-constants.ts b/flow-core/src/platform-constants.ts index 352f452..ec220cc 100644 --- a/flow-core/src/platform-constants.ts +++ b/flow-core/src/platform-constants.ts @@ -1,6 +1,4 @@ -export const validLogLevels = ['debug', 'info', 'warn', 'error'] as const; - -export type LogLevel = typeof validLogLevels[number]; +import { LogLevel, validLogLevels } from './utils/logger/logger-types.ts'; /** * Validates that the provided log level is one of the allowed values. diff --git a/flow-core/src/utils/logger/component-logger.ts b/flow-core/src/utils/logger/component-logger.ts new file mode 100644 index 0000000..91eb3ff --- /dev/null +++ b/flow-core/src/utils/logger/component-logger.ts @@ -0,0 +1,53 @@ +import { + EnhancedStructuredLogger, + LoggerConfig, + createEnhancedLogger, +} from "./index.ts"; + +let injectedLoggerConfig: LoggerConfig | undefined; +let globalLogger: EnhancedStructuredLogger | undefined; + +/** + * Inject a LoggerConfig to be used globally by flow-core. + * Should be called once by flow-service at startup. + */ +export function setGlobalLoggerConfig(config: LoggerConfig): void { + injectedLoggerConfig = config; + globalLogger = undefined; // force re-creation with new config +} + +/** + * Resets logger for testing or reconfiguration + */ +export function resetGlobalLogger(): void { + injectedLoggerConfig = undefined; + globalLogger = undefined; +} + +/** + * Get the global enhanced logger, initializing if necessary. + */ +function getGlobalLogger(): EnhancedStructuredLogger { + if (!globalLogger) { + globalLogger = createEnhancedLogger(injectedLoggerConfig ?? { + enableConsole: true, + enableFile: false, + enableSentry: false, + }); + } + return globalLogger; +} + +/** + * Get a logger scoped to the current file/module. + * Auto-fills the `component` field from import.meta. + */ +export function getComponentLogger(meta: ImportMeta): EnhancedStructuredLogger { + const component = deriveComponentName(meta); + return getGlobalLogger().forComponent(component) as EnhancedStructuredLogger; +} + +function deriveComponentName(meta: ImportMeta): string { + const url = new URL(meta.url); + return url.pathname.split("/").pop()?.replace(".ts", "") ?? "unknown"; +} diff --git a/flow-core/src/utils/logger/formatters.ts b/flow-core/src/utils/logger/formatters.ts index 7066a7f..5b4a51c 100644 --- a/flow-core/src/utils/logger/formatters.ts +++ b/flow-core/src/utils/logger/formatters.ts @@ -3,12 +3,7 @@ * Provides consistent formatting for console output, file logging, and structured data. */ -import type { LogContext } from './logger-types.ts'; - -/** - * Log level type using string literals - */ -export type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL'; +import type { LogContext, LogLevel } from './logger-types.ts'; /** * Console color codes for terminal output @@ -24,14 +19,14 @@ const colors = { } as const; /** - * Log level numeric values for comparison + * Log level numeric values for comparison (using lowercase keys) */ export const LogLevels = { - DEBUG: 10, - INFO: 20, - WARN: 30, - ERROR: 40, - CRITICAL: 50, + debug: 10, + info: 20, + warn: 30, + error: 40, + critical: 50, } as const; /** @@ -53,8 +48,8 @@ export function colorize(color: keyof typeof colors, text: string): string { */ 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; + (Deno.env.get('FLOW_ENV') !== 'production' ? 'debug' : 'info'); + const currentLevel = LogLevels[configLevel as LogLevel] || LogLevels.info; return LogLevels[level] >= currentLevel; } @@ -119,13 +114,13 @@ export function formatConsoleMessage( 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 coloredLevel = level === 'error' || level === 'critical' + ? colorize('red', level.toUpperCase()) + : level === 'warn' + ? colorize('yellow', level.toUpperCase()) + : level === 'debug' + ? colorize('blue', level.toUpperCase()) + : colorize('green', level.toUpperCase()); const coloredPrefix = colorize('gray', `[${timestamp}]`) + ` ${coloredLevel.padEnd(5)}`; diff --git a/flow-core/src/utils/logger/index.ts b/flow-core/src/utils/logger/index.ts index 242d5a2..4e94036 100644 --- a/flow-core/src/utils/logger/index.ts +++ b/flow-core/src/utils/logger/index.ts @@ -12,10 +12,10 @@ export type { ErrorHandlingOptions, } from './logger-types.ts'; -export { LogLevel } from './logger-types.ts'; +export type { LogLevel } from './logger-types.ts'; +export { validLogLevels, LogLevelValues } from './logger-types.ts'; -// Export formatting utilities -export type { LogLevel as LogLevelString } from './formatters.ts'; +// Export formatting utilities (LogLevel type is now imported from logger-types.ts) export { colorize, shouldLog, diff --git a/flow-core/src/utils/logger/logger-types.ts b/flow-core/src/utils/logger/logger-types.ts index e28d319..66034fb 100644 --- a/flow-core/src/utils/logger/logger-types.ts +++ b/flow-core/src/utils/logger/logger-types.ts @@ -73,6 +73,11 @@ export interface LogContext { instanceId?: string; }; + /** SPARQL query context for SPARQL operations */ + sparqlContext?: { + query?: string; + }; + /** Additional arbitrary metadata */ metadata?: Record; } @@ -146,16 +151,25 @@ export interface StructuredLogger { } /** - * Log level enumeration for controlling log output + * Log level type using lowercase string literals for controlling log output */ -export enum LogLevel { - DEBUG = 0, - INFO = 1, - WARN = 2, - ERROR = 3, - CRITICAL = 4, - OFF = 5, -} +export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'critical'; + +/** + * Valid log levels array for validation and iteration + */ +export const validLogLevels = ['debug', 'info', 'warn', 'error', 'critical'] as const; + +/** + * Log level numeric values for comparison and filtering + */ +export const LogLevelValues = { + debug: 0, + info: 1, + warn: 2, + error: 3, + critical: 4, +} as const; /** * Logger configuration interface diff --git a/flow-core/src/utils/logger/sentry-logger.ts b/flow-core/src/utils/logger/sentry-logger.ts index d300783..fdab2cb 100644 --- a/flow-core/src/utils/logger/sentry-logger.ts +++ b/flow-core/src/utils/logger/sentry-logger.ts @@ -4,8 +4,7 @@ */ import { Sentry } from '../../deps.ts'; -import type { LogContext } from './logger-types.ts'; -import type { LogLevel } from './formatters.ts'; +import type { LogContext, LogLevel } from './logger-types.ts'; import { extractErrorContext } from './formatters.ts'; /** @@ -284,15 +283,15 @@ export function resetSentry(): void { */ function mapLogLevelToSentry(level: LogLevel): 'debug' | 'info' | 'warning' | 'error' | 'fatal' { switch (level) { - case 'DEBUG': + case 'debug': return 'debug'; - case 'INFO': + case 'info': return 'info'; - case 'WARN': + case 'warn': return 'warning'; - case 'ERROR': + case 'error': return 'error'; - case 'CRITICAL': + case 'critical': return 'fatal'; default: return 'info'; diff --git a/flow-service/main.ts b/flow-service/main.ts index b88d1fc..1fc8aa8 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -11,6 +11,14 @@ import { handleCaughtError } from '../flow-core/src/utils/logger/error-handlers. import type { LogContext } from '../flow-core/src/utils/logger/logger-types.ts'; import { createServiceLogContext } from './src/utils/service-log-context.ts'; import { MESH } from '../flow-core/src/mesh-constants.ts'; +import { setGlobalLoggerConfig } from "../flow-core/src/utils/logger/component-logger.ts"; +import { SERVICE_LOGGER_CONFIG } from "./src/utils/service-logger.ts"; +import { getComponentLogger } from '../flow-core/src/utils/logger/component-logger.ts'; + +// initialize a logger with default config until we can process the service config +setGlobalLoggerConfig(SERVICE_LOGGER_CONFIG); +let logger = getComponentLogger(import.meta); +logger.info('Starting Flow Service with initial logger configuration'); // Initialize configuration system try { diff --git a/flow-service/src/config/config-types.ts b/flow-service/src/config/config-types.ts index 81b672b..8f3d0c9 100644 --- a/flow-service/src/config/config-types.ts +++ b/flow-service/src/config/config-types.ts @@ -6,6 +6,7 @@ */ import { NodeObject, ContextDefinition } from "../../../flow-core/src/deps.ts"; +import { LogLevel } from "../../../flow-core/src/utils/logger/logger-types.ts"; // JSON-LD Context and Type Definitions export interface FlowServiceContext extends ContextDefinition { @@ -188,5 +189,5 @@ export class ConfigValidationError extends ConfigError { } // Utility Types for Configuration Access -export type LogLevel = "debug" | "info" | "warn" | "error"; +// LogLevel is now imported from the canonical source diff --git a/flow-service/src/utils/service-logger.ts b/flow-service/src/utils/service-logger.ts index 0d5a322..74dae72 100644 --- a/flow-service/src/utils/service-logger.ts +++ b/flow-service/src/utils/service-logger.ts @@ -11,6 +11,7 @@ import { type StructuredLogger, type EnhancedStructuredLogger, } from '../../../flow-core/src/utils/logger/index.ts'; +import { FLOW_SERVICE_VERSION } from "../service-constants.ts"; // Re-export formatters and error handlers for test access export { formatConsoleMessage } from '../../../flow-core/src/utils/logger/formatters.ts'; @@ -21,7 +22,7 @@ export { handleCaughtError } from '../../../flow-core/src/utils/logger/error-han */ const SERVICE_CONTEXT = { serviceName: 'flow-service', - serviceVersion: Deno.env.get('FLOW_VERSION') || '1.0.0', + serviceVersion: Deno.env.get('FLOW_VERSION') || FLOW_SERVICE_VERSION, environment: Deno.env.get('FLOW_ENV') || 'development', instanceId: Deno.env.get('FLOW_INSTANCE_ID') || crypto.randomUUID(), } as const; @@ -29,7 +30,7 @@ const SERVICE_CONTEXT = { /** * Service-specific logger configuration */ -const SERVICE_LOGGER_CONFIG: LoggerConfig = { +export const SERVICE_LOGGER_CONFIG: LoggerConfig = { enableConsole: true, enableFile: Deno.env.get('FLOW_LOG_FILE_ENABLED') === 'true', enableSentry: Deno.env.get('FLOW_SENTRY_ENABLED') === 'true', diff --git a/flow-service/tests/integration/logging/formatting-direct.test.ts b/flow-service/tests/integration/logging/formatting-direct.test.ts index e1e5479..71d22e9 100644 --- a/flow-service/tests/integration/logging/formatting-direct.test.ts +++ b/flow-service/tests/integration/logging/formatting-direct.test.ts @@ -9,7 +9,7 @@ import { dirname, ensureDir } from '../../../../flow-core/src/deps.ts'; -type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'CRITICAL'; +import type { LogLevel } from '../../../../flow-core/src/utils/logger/logger-types.ts'; interface LogContext { operation?: @@ -88,7 +88,7 @@ const fileLogger = new SimpleFileLogger(logFile); // Test different log levels with various contexts const testCases = [ { - level: 'INFO' as LogLevel, + level: 'info' as LogLevel, message: 'File logging test started', context: { operation: 'startup' as const, @@ -96,7 +96,7 @@ const testCases = [ }, }, { - level: 'WARN' as LogLevel, + level: 'warn' as LogLevel, message: 'Configuration override detected', context: { operation: 'config-resolve' as const, @@ -104,12 +104,12 @@ const testCases = [ }, }, { - level: 'ERROR' as LogLevel, + level: 'error' as LogLevel, message: 'Database connection failed', context: { operation: 'startup' as const, duration: 5000 }, }, { - level: 'DEBUG' as LogLevel, + level: 'debug' as LogLevel, message: 'Processing mesh nodes', context: { operation: 'scan' as const, From e8d9d998832d12c227b9ddf986f503d24d8493d2 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Wed, 6 Aug 2025 16:39:14 -0700 Subject: [PATCH 05/10] harmonize LoggingConfig with the flow-service-ontology --- flow-core/src/utils/logger/index.ts | 45 ++++++-- flow-core/src/utils/logger/logger-types.ts | 104 ++++++++++++++---- .../src/utils/logger/structured-logger.ts | 99 +++++++++++------ flow-service/src/config/config-types.ts | 2 +- flow-service/src/config/index.ts | 1 - flow-service/src/utils/service-logger.ts | 36 +++--- 6 files changed, 202 insertions(+), 85 deletions(-) diff --git a/flow-core/src/utils/logger/index.ts b/flow-core/src/utils/logger/index.ts index 4e94036..2999d44 100644 --- a/flow-core/src/utils/logger/index.ts +++ b/flow-core/src/utils/logger/index.ts @@ -7,9 +7,16 @@ export type { LogContext, StructuredLogger, - LoggerConfig, + LoggingConfig, + LogChannelConfig, + ConsoleChannelConfig, + FileChannelConfig, + SentryChannelConfig, + SentryConfig, ErrorSeverity, ErrorHandlingOptions, + // Deprecated - use LoggingConfig instead + LoggerConfig, } from './logger-types.ts'; export type { LogLevel } from './logger-types.ts'; @@ -76,9 +83,21 @@ export function createDefaultLogger( ): StructuredLogger { return createLogger( { - enableConsole: true, - enableFile: true, - enableSentry: true, + consoleChannel: { + logChannelEnabled: true, + logLevel: 'info', + logFormat: 'pretty', + }, + fileChannel: { + logChannelEnabled: true, + logLevel: 'info', + logFormat: 'json', + logFilePath: './logs/app.log', + }, + sentryChannel: { + logChannelEnabled: true, + logLevel: 'error', + }, serviceContext: { serviceName: appName, serviceVersion: appVersion || 'unknown', @@ -107,9 +126,21 @@ export function createDefaultEnhancedLogger( ): EnhancedStructuredLogger { return createEnhancedLogger( { - enableConsole: true, - enableFile: true, - enableSentry: true, + consoleChannel: { + logChannelEnabled: true, + logLevel: 'info', + logFormat: 'pretty', + }, + fileChannel: { + logChannelEnabled: true, + logLevel: 'info', + logFormat: 'json', + logFilePath: './logs/app.log', + }, + sentryChannel: { + logChannelEnabled: true, + logLevel: 'error', + }, serviceContext: { serviceName: appName, serviceVersion: appVersion || 'unknown', diff --git a/flow-core/src/utils/logger/logger-types.ts b/flow-core/src/utils/logger/logger-types.ts index 66034fb..2fa4a85 100644 --- a/flow-core/src/utils/logger/logger-types.ts +++ b/flow-core/src/utils/logger/logger-types.ts @@ -172,36 +172,66 @@ export const LogLevelValues = { } as const; /** - * Logger configuration interface + * Base log channel configuration interface aligned with ontology fsvc:LogChannelConfig */ -export interface LoggerConfig { - /** Minimum log level to output */ - level?: LogLevel; +export interface LogChannelConfig { + /** Whether this logging channel is enabled (fsvc:logChannelEnabled) */ + logChannelEnabled: boolean; - /** Whether to enable console output */ - enableConsole?: boolean; + /** Minimum log level for this channel (fsvc:logLevel) */ + logLevel: LogLevel; - /** Whether to enable file output */ - enableFile?: boolean; + /** Log format for this channel (fsvc:logFormat) */ + logFormat?: 'json' | 'pretty'; +} - /** Whether to enable Sentry integration */ - enableSentry?: boolean; +/** + * Console channel configuration interface + */ +export interface ConsoleChannelConfig extends LogChannelConfig { + // Console channels only need the base properties +} - /** File logging configuration */ - fileConfig?: { - logDir?: string; - maxFileSize?: number; - maxFiles?: number; - rotateDaily?: boolean; - }; +/** + * File channel configuration interface + */ +export interface FileChannelConfig extends LogChannelConfig { + /** Log file path (fsvc:logFilePath) */ + logFilePath?: string; - /** Sentry configuration */ - sentryConfig?: { - dsn?: string; - environment?: string; - release?: string; - sampleRate?: number; - }; + /** Log retention days (fsvc:logRetentionDays) */ + logRetentionDays?: number; + + /** Maximum number of log files (fsvc:logMaxFiles) */ + logMaxFiles?: number; + + /** Maximum log file size in bytes (fsvc:logMaxFileSize) */ + logMaxFileSize?: number; + + /** Log rotation interval (fsvc:logRotationInterval) */ + logRotationInterval?: 'daily' | 'weekly' | 'monthly' | 'size-based'; +} + +/** + * Sentry channel configuration interface + */ +export interface SentryChannelConfig extends LogChannelConfig { + /** Sentry DSN for error reporting (fsvc:sentryDsn) */ + sentryDsn?: string; +} + +/** + * Logging configuration interface aligned with ontology fsvc:LoggingConfig + */ +export interface LoggingConfig { + /** Console logging channel configuration (fsvc:hasConsoleChannel) */ + consoleChannel?: ConsoleChannelConfig; + + /** File logging channel configuration (fsvc:hasFileChannel) */ + fileChannel?: FileChannelConfig; + + /** Sentry logging channel configuration (fsvc:hasSentryChannel) */ + sentryChannel?: SentryChannelConfig; /** Service context applied to all logs */ serviceContext?: { @@ -212,6 +242,32 @@ export interface LoggerConfig { }; } +/** + * Separate Sentry general configuration (not part of logging config) + * For tracing and other non-logging Sentry features + */ +export interface SentryConfig { + /** Sentry DSN */ + dsn: string; + + /** Environment name */ + environment?: string; + + /** Release version */ + release?: string; + + /** Traces sample rate for performance monitoring */ + tracesSampleRate?: number; + + /** Debug mode */ + debug?: boolean; +} + +/** + * @deprecated Use LoggingConfig instead. Will be removed in next major version. + */ +export type LoggerConfig = LoggingConfig; + /** * Error severity levels for error handling */ diff --git a/flow-core/src/utils/logger/structured-logger.ts b/flow-core/src/utils/logger/structured-logger.ts index 9138193..efe1267 100644 --- a/flow-core/src/utils/logger/structured-logger.ts +++ b/flow-core/src/utils/logger/structured-logger.ts @@ -3,8 +3,7 @@ * Integrates console output, file logging, and Sentry error reporting with structured context. */ -import type { LogContext, StructuredLogger, LoggerConfig } from './logger-types.ts'; -import type { LogLevel } from './formatters.ts'; +import type { LogContext, StructuredLogger, LoggingConfig, LogLevel } from './logger-types.ts'; import { formatConsoleMessage, formatStructuredMessage, @@ -23,19 +22,30 @@ import { */ export class StructuredLoggerImpl implements StructuredLogger { private baseContext: LogContext; - private config: LoggerConfig; + private config: LoggingConfig; /** * Create a new StructuredLogger instance * @param baseContext - Base context applied to all log entries * @param config - Logger configuration */ - constructor(baseContext: LogContext = {}, config: LoggerConfig = {}) { + constructor(baseContext: LogContext = {}, config: LoggingConfig = {}) { this.baseContext = baseContext; this.config = { - enableConsole: true, - enableFile: true, - enableSentry: true, + consoleChannel: { + logChannelEnabled: true, + logLevel: 'info', + logFormat: 'pretty', + }, + fileChannel: { + logChannelEnabled: false, + logLevel: 'info', + logFormat: 'json', + }, + sentryChannel: { + logChannelEnabled: false, + logLevel: 'error', + }, ...config, }; } @@ -48,9 +58,9 @@ export class StructuredLoggerImpl implements StructuredLogger { context?: LogContext, meta?: Record, ): Promise { - if (!shouldLog('DEBUG')) return; + if (!shouldLog('debug')) return; const mergedContext = this.mergeContexts(context, meta); - await this.writeToAllChannels('DEBUG', message, mergedContext); + await this.writeToAllChannels('debug', message, mergedContext); } /** @@ -61,9 +71,9 @@ export class StructuredLoggerImpl implements StructuredLogger { context?: LogContext, meta?: Record, ): Promise { - if (!shouldLog('INFO')) return; + if (!shouldLog('info')) return; const mergedContext = this.mergeContexts(context, meta); - await this.writeToAllChannels('INFO', message, mergedContext); + await this.writeToAllChannels('info', message, mergedContext); } /** @@ -74,9 +84,9 @@ export class StructuredLoggerImpl implements StructuredLogger { context?: LogContext, meta?: Record, ): Promise { - if (!shouldLog('WARN')) return; + if (!shouldLog('warn')) return; const mergedContext = this.mergeContexts(context, meta); - await this.writeToAllChannels('WARN', message, mergedContext); + await this.writeToAllChannels('warn', message, mergedContext); } /** @@ -87,9 +97,9 @@ export class StructuredLoggerImpl implements StructuredLogger { context?: LogContext, meta?: Record, ): Promise { - if (!shouldLog('ERROR')) return; + if (!shouldLog('error')) return; const mergedContext = this.mergeContexts(context, meta); - await this.writeToAllChannels('ERROR', message, mergedContext); + await this.writeToAllChannels('error', message, mergedContext); } /** @@ -101,7 +111,7 @@ export class StructuredLoggerImpl implements StructuredLogger { meta?: Record, ): Promise { const mergedContext = this.mergeContexts(context, meta); - await this.writeToAllChannels('CRITICAL', message, mergedContext); + await this.writeToAllChannels('critical', message, mergedContext); } /** @@ -143,22 +153,39 @@ export class StructuredLoggerImpl implements StructuredLogger { context?: LogContext, error?: Error, ): Promise { - // Write to console if enabled - if (this.config.enableConsole !== false) { + // Write to console if enabled and level meets threshold + if (this.config.consoleChannel?.logChannelEnabled && this.shouldLogToChannel(level, this.config.consoleChannel.logLevel)) { await this.writeToConsole(level, message, context); } - // Write to file if enabled - if (this.config.enableFile !== false) { + // Write to file if enabled and level meets threshold + if (this.config.fileChannel?.logChannelEnabled && this.shouldLogToChannel(level, this.config.fileChannel.logLevel)) { await this.writeToFile(level, message, context); } - // Write to Sentry if enabled - if (this.config.enableSentry !== false && isSentryEnabled()) { + // Write to Sentry if enabled, level meets threshold, and Sentry is initialized + if (this.config.sentryChannel?.logChannelEnabled && this.shouldLogToChannel(level, this.config.sentryChannel.logLevel) && isSentryEnabled()) { await this.writeToSentry(level, message, context, error); } } + /** + * Check if a log level meets the threshold for a specific channel + */ + private shouldLogToChannel(messageLevel: LogLevel, channelLevel: LogLevel): boolean { + const messageLevelValue = this.getLogLevelValue(messageLevel); + const channelLevelValue = this.getLogLevelValue(channelLevel); + return messageLevelValue >= channelLevelValue; + } + + /** + * Get numeric value for log level comparison + */ + private getLogLevelValue(level: LogLevel): number { + const levels = { debug: 0, info: 1, warn: 2, error: 3, critical: 4 }; + return levels[level] ?? 1; + } + /** * Write log entry to console */ @@ -171,15 +198,15 @@ export class StructuredLoggerImpl implements StructuredLogger { const consoleFormatted = formatConsoleMessage(level, message, context); switch (level) { - case 'DEBUG': - case 'INFO': + case 'debug': + case 'info': console.log(consoleFormatted); break; - case 'WARN': + case 'warn': console.warn(consoleFormatted); break; - case 'ERROR': - case 'CRITICAL': + case 'error': + case 'critical': console.error(consoleFormatted); break; } @@ -227,7 +254,7 @@ export class StructuredLoggerImpl implements StructuredLogger { if (error) { // Error reporting reportErrorToSentry(error, level, context, message); - } else if (level === 'ERROR' || level === 'CRITICAL') { + } else if (level === 'error' || level === 'critical') { // Convert high-level log messages to Sentry messages reportMessageToSentry(message, level, context); } @@ -259,14 +286,14 @@ export class StructuredLoggerImpl implements StructuredLogger { /** * Get the current logger configuration */ - getConfig(): LoggerConfig { + getConfig(): LoggingConfig { return { ...this.config }; } /** * Update the logger configuration */ - updateConfig(config: Partial): void { + updateConfig(config: Partial): void { this.config = { ...this.config, ...config }; } } @@ -283,7 +310,7 @@ export class EnhancedStructuredLogger extends StructuredLoggerImpl { errorOrContext?: Error | LogContext, context?: LogContext, ): Promise { - if (!shouldLog('ERROR')) return; + if (!shouldLog('error')) return; let error: Error | undefined; let finalContext: LogContext | undefined; @@ -296,7 +323,7 @@ export class EnhancedStructuredLogger extends StructuredLoggerImpl { } const mergedContext = this.mergeContexts(finalContext); - await this.writeToAllChannels('ERROR', message, mergedContext, error); + await this.writeToAllChannels('error', message, mergedContext, error); } /** @@ -318,7 +345,7 @@ export class EnhancedStructuredLogger extends StructuredLoggerImpl { } const mergedContext = this.mergeContexts(finalContext); - await this.writeToAllChannels('CRITICAL', message, mergedContext, error); + await this.writeToAllChannels('critical', message, mergedContext, error); } } @@ -329,7 +356,7 @@ export class EnhancedStructuredLogger extends StructuredLoggerImpl { * @returns New StructuredLogger instance */ export function createLogger( - config?: LoggerConfig, + config?: LoggingConfig, baseContext?: LogContext, ): StructuredLogger { return new StructuredLoggerImpl(baseContext, config); @@ -337,12 +364,12 @@ export function createLogger( /** * Create an enhanced structured logger that supports Error objects - * @param config - Logger configuration + * @param config - Logger configuration * @param baseContext - Base context for all log entries * @returns New EnhancedStructuredLogger instance */ export function createEnhancedLogger( - config?: LoggerConfig, + config?: LoggingConfig, baseContext?: LogContext, ): EnhancedStructuredLogger { return new EnhancedStructuredLogger(baseContext, config); diff --git a/flow-service/src/config/config-types.ts b/flow-service/src/config/config-types.ts index 8f3d0c9..30e2604 100644 --- a/flow-service/src/config/config-types.ts +++ b/flow-service/src/config/config-types.ts @@ -21,7 +21,7 @@ export interface FlowServiceContext extends ContextDefinition { export interface LogChannelConfig extends NodeObject { readonly "@type": "fsvc:LogChannelConfig"; readonly "fsvc:logChannelEnabled": boolean; - readonly "fsvc:logLevel": "debug" | "info" | "warn" | "error"; + readonly "fsvc:logLevel": LogLevel; readonly "fsvc:logFormat"?: "json" | "pretty"; readonly "fsvc:logFilePath"?: string; readonly "fsvc:sentryDsn"?: string; diff --git a/flow-service/src/config/index.ts b/flow-service/src/config/index.ts index 108f0fc..dc20d27 100644 --- a/flow-service/src/config/index.ts +++ b/flow-service/src/config/index.ts @@ -25,7 +25,6 @@ export type { FlowServiceContext, LogChannelConfig, LoggingConfig, - LogLevel, MeshRootNodeConfig, MeshRootNodeConfigContext, MeshRootNodeConfigInput, diff --git a/flow-service/src/utils/service-logger.ts b/flow-service/src/utils/service-logger.ts index 74dae72..e4b56f6 100644 --- a/flow-service/src/utils/service-logger.ts +++ b/flow-service/src/utils/service-logger.ts @@ -6,7 +6,7 @@ import { createEnhancedLogger, - type LoggerConfig, + type LoggingConfig, type LogContext, type StructuredLogger, type EnhancedStructuredLogger, @@ -30,23 +30,27 @@ const SERVICE_CONTEXT = { /** * Service-specific logger configuration */ -export 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', +export const SERVICE_LOGGER_CONFIG: LoggingConfig = { + consoleChannel: { + logChannelEnabled: true, + logLevel: 'info', + logFormat: 'pretty', }, - 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'), + fileChannel: { + logChannelEnabled: Deno.env.get('FLOW_LOG_FILE_ENABLED') === 'true', + logLevel: 'info', + logFormat: 'json', + logFilePath: `${Deno.env.get('FLOW_LOG_DIR') || './logs'}/flow-service.log`, + logMaxFileSize: parseInt(Deno.env.get('FLOW_LOG_MAX_FILE_SIZE') || '10485760'), // 10MB + logMaxFiles: parseInt(Deno.env.get('FLOW_LOG_MAX_FILES') || '5'), + logRotationInterval: Deno.env.get('FLOW_LOG_ROTATE_DAILY') === 'true' ? 'daily' : 'size-based', + }, + + sentryChannel: { + logChannelEnabled: Deno.env.get('FLOW_SENTRY_ENABLED') === 'true', + logLevel: 'error', + sentryDsn: Deno.env.get('FLOW_SENTRY_DSN'), }, serviceContext: SERVICE_CONTEXT, From 6decf8ee8d1b65e9ecefc006d34fd540839b1d37 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Wed, 6 Aug 2025 16:56:36 -0700 Subject: [PATCH 06/10] Refactor logging configuration to use FLOW_SERVICE_VERSION consistently; add logging configuration extractor for service context --- .../error-handling-usage.md | 0 flow-core/src/utils/logger/formatters.ts | 2 +- flow-core/src/utils/logger/sentry-logger.ts | 2 +- flow-service/main.ts | 25 ++- .../src/config/logging-config-extractor.ts | 159 ++++++++++++++++++ flow-service/src/service-constants.ts | 5 + flow-service/src/utils/service-logger.ts | 6 +- 7 files changed, 191 insertions(+), 8 deletions(-) rename flow-core/{docs => documentation}/error-handling-usage.md (100%) create mode 100644 flow-service/src/config/logging-config-extractor.ts diff --git a/flow-core/docs/error-handling-usage.md b/flow-core/documentation/error-handling-usage.md similarity index 100% rename from flow-core/docs/error-handling-usage.md rename to flow-core/documentation/error-handling-usage.md diff --git a/flow-core/src/utils/logger/formatters.ts b/flow-core/src/utils/logger/formatters.ts index 5b4a51c..9aff0ab 100644 --- a/flow-core/src/utils/logger/formatters.ts +++ b/flow-core/src/utils/logger/formatters.ts @@ -88,7 +88,7 @@ export function formatStructuredMessage( level: level.toLowerCase(), message, service: serviceContext?.serviceName || 'flow-service', - version: serviceContext?.serviceVersion || Deno.env.get('FLOW_VERSION'), + version: serviceContext?.serviceVersion || Deno.env.get('FLOW_SERVICE_VERSION'), environment: serviceContext?.environment || Deno.env.get('FLOW_ENV') || 'development', instanceId: serviceContext?.instanceId, ...context, diff --git a/flow-core/src/utils/logger/sentry-logger.ts b/flow-core/src/utils/logger/sentry-logger.ts index fdab2cb..b7f7e85 100644 --- a/flow-core/src/utils/logger/sentry-logger.ts +++ b/flow-core/src/utils/logger/sentry-logger.ts @@ -50,7 +50,7 @@ export function initSentry(config?: SentryConfig, dsn?: string): void { 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'), + release: config?.release || Deno.env.get('FLOW_SERVICE_VERSION'), // Enable logs to be sent to Sentry (experimental feature) _experiments: { enableLogs: config?.enableLogs ?? true diff --git a/flow-service/main.ts b/flow-service/main.ts index 1fc8aa8..66f80e4 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -12,11 +12,11 @@ import type { LogContext } from '../flow-core/src/utils/logger/logger-types.ts'; import { createServiceLogContext } from './src/utils/service-log-context.ts'; import { MESH } from '../flow-core/src/mesh-constants.ts'; import { setGlobalLoggerConfig } from "../flow-core/src/utils/logger/component-logger.ts"; -import { SERVICE_LOGGER_CONFIG } from "./src/utils/service-logger.ts"; +import { SERVICE_LOGGER_DEFAULT_CONFIG } from "./src/utils/service-logger.ts"; import { getComponentLogger } from '../flow-core/src/utils/logger/component-logger.ts'; // initialize a logger with default config until we can process the service config -setGlobalLoggerConfig(SERVICE_LOGGER_CONFIG); +setGlobalLoggerConfig(SERVICE_LOGGER_DEFAULT_CONFIG); let logger = getComponentLogger(import.meta); logger.info('Starting Flow Service with initial logger configuration'); @@ -29,12 +29,31 @@ try { component: 'service-config-init', }); await handleCaughtError(error, 'Failed to initialize service configuration', context); - console.error( + logger.error( '❌ Service startup failed due to configuration error. Exiting...', ); Deno.exit(1); } +// now that config is loaded, reinitialize logger with service context +try { + const { extractLoggingConfigFromService } = await import('./src/config/logging-config-extractor.ts'); + const loggingConfig = await extractLoggingConfigFromService(); + setGlobalLoggerConfig(loggingConfig); + + // Get a new logger instance with the updated configuration + logger = getComponentLogger(import.meta); + logger.info('Logger reinitialized with service configuration'); +} catch (error) { + const context: LogContext = createServiceLogContext({ + operation: 'startup', + component: 'logger-reinit', + }); + await handleCaughtError(error, 'Failed to reinitialize logger with service configuration', context); + console.warn('⚠️ Logger reinitialization failed, continuing with default configuration...'); +} + + // Log service startup with configuration info try { logStartupConfiguration(); diff --git a/flow-service/src/config/logging-config-extractor.ts b/flow-service/src/config/logging-config-extractor.ts new file mode 100644 index 0000000..54d96ca --- /dev/null +++ b/flow-service/src/config/logging-config-extractor.ts @@ -0,0 +1,159 @@ +/** + * @fileoverview Logging configuration extractor for converting ontology-based + * configuration to LoggingConfig format for logger reinitialization. + */ + +import type { LoggingConfig, LogLevel } from '../../../flow-core/src/utils/logger/logger-types.ts'; +import { singletonServiceConfigAccessor } from './resolution/service-config-accessor.ts'; +import { FLOW_SERVICE_VERSION, FLOW_SERVICE_NAME, FLOW_SERVICE_INSTANCE_ID } from '../service-constants.ts'; +import { querySingleValue } from '../../../flow-core/src/utils/sparql-utils.ts'; +import { defaultQuadstoreBundle } from '../quadstore-default-bundle.ts'; +import { getCurrentServiceUri } from '../utils/service-uri-builder.ts'; +import { CONFIG_GRAPH_NAMES } from './index.ts'; + +/** + * Extracts and converts ontology-based logging configuration from the merged + * service config to LoggingConfig interface format. + * + * @returns Promise The converted logging configuration + */ +export async function extractLoggingConfigFromService(): Promise { + // Get service context information + const serviceContext = { + serviceName: FLOW_SERVICE_NAME, + serviceVersion: Deno.env.get('FLOW_SERVICE_VERSION') || FLOW_SERVICE_VERSION, + environment: Deno.env.get('FLOW_ENV') || 'development', + instanceId: FLOW_SERVICE_INSTANCE_ID, + }; + + // Extract console channel configuration + const consoleConfig = await extractConsoleChannelConfig(); + + // Extract file channel configuration + const fileConfig = await extractFileChannelConfig(); + + // Extract sentry channel configuration + const sentryConfig = await extractSentryChannelConfig(); + + return { + consoleChannel: consoleConfig, + fileChannel: fileConfig, + sentryChannel: sentryConfig, + serviceContext, + }; +} + +/** + * Extracts console channel configuration from the merged service config + */ +async function extractConsoleChannelConfig() { + try { + const consoleLoggingConfig = await singletonServiceConfigAccessor.getConsoleLoggingConfig(); + + // Get additional console-specific properties + const logFormat = await getChannelProperty('fsvc:hasConsoleChannel', 'fsvc:logFormat') as 'json' | 'pretty' | undefined; + + return { + logChannelEnabled: consoleLoggingConfig.enabled, + logLevel: (consoleLoggingConfig.level as LogLevel) || 'info', + logFormat: logFormat || 'pretty', + }; + } catch (error) { + // Provide sensible defaults if extraction fails + console.warn('Failed to extract console logging config, using defaults:', error); + return { + logChannelEnabled: true, + logLevel: 'info' as LogLevel, + logFormat: 'pretty' as const, + }; + } +} + +/** + * Extracts file channel configuration from the merged service config + */ +async function extractFileChannelConfig() { + try { + const fileLoggingConfig = await singletonServiceConfigAccessor.getFileLoggingConfig(); + + // Get additional file-specific properties + const logFormat = await getChannelProperty('fsvc:hasFileChannel', 'fsvc:logFormat') as 'json' | 'pretty' | undefined; + const logFilePath = await getChannelProperty('fsvc:hasFileChannel', 'fsvc:logFilePath'); + const logRetentionDays = await getChannelProperty('fsvc:hasFileChannel', 'fsvc:logRetentionDays'); + const logMaxFiles = await getChannelProperty('fsvc:hasFileChannel', 'fsvc:logMaxFiles'); + const logMaxFileSize = await getChannelProperty('fsvc:hasFileChannel', 'fsvc:logMaxFileSize'); + const logRotationInterval = await getChannelProperty('fsvc:hasFileChannel', 'fsvc:logRotationInterval') as 'daily' | 'weekly' | 'monthly' | 'size-based' | undefined; + + return { + logChannelEnabled: fileLoggingConfig.enabled, + logLevel: (fileLoggingConfig.level as LogLevel) || 'warn', + logFormat: logFormat || 'json', + logFilePath: logFilePath || './logs/flow-service.log', + logRetentionDays: logRetentionDays ? parseInt(logRetentionDays) : 30, + logMaxFiles: logMaxFiles ? parseInt(logMaxFiles) : 10, + logMaxFileSize: logMaxFileSize ? parseInt(logMaxFileSize) : 10485760, // 10MB + logRotationInterval: logRotationInterval || 'daily', + }; + } catch (error) { + // Provide sensible defaults if extraction fails + console.warn('Failed to extract file logging config, using defaults:', error); + return { + logChannelEnabled: false, + logLevel: 'warn' as LogLevel, + logFormat: 'json' as const, + logFilePath: './logs/flow-service.log', + logRetentionDays: 30, + logMaxFiles: 10, + logMaxFileSize: 10485760, + logRotationInterval: 'daily' as const, + }; + } +} + +/** + * Extracts sentry channel configuration from the merged service config + */ +async function extractSentryChannelConfig() { + try { + const sentryLoggingConfig = await singletonServiceConfigAccessor.getSentryLoggingConfig(); + + // Get additional sentry-specific properties + const sentryDsn = await getChannelProperty('fsvc:hasSentryChannel', 'fsvc:sentryDsn'); + + return { + logChannelEnabled: sentryLoggingConfig.enabled, + logLevel: (sentryLoggingConfig.level as LogLevel) || 'error', + sentryDsn: sentryDsn || Deno.env.get('FLOW_SENTRY_DSN'), + }; + } catch (error) { + // Provide sensible defaults if extraction fails + console.warn('Failed to extract sentry logging config, using defaults:', error); + return { + logChannelEnabled: false, + logLevel: 'error' as LogLevel, + sentryDsn: Deno.env.get('FLOW_SENTRY_DSN'), + }; + } +} + +/** + * Helper function to get channel-specific properties using SPARQL + */ +async function getChannelProperty(channelProperty: string, property: string): Promise { + const sparql = ` + PREFIX fsvc: + SELECT ?value WHERE { + GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { + ?s ${channelProperty} ?channel . + ?channel ${property} ?value . + } + } + `; + + try { + return await querySingleValue(defaultQuadstoreBundle, sparql); + } catch (error) { + console.warn(`Failed to query ${property} for ${channelProperty}:`, error); + return undefined; + } +} diff --git a/flow-service/src/service-constants.ts b/flow-service/src/service-constants.ts index 888b57a..0770fd8 100644 --- a/flow-service/src/service-constants.ts +++ b/flow-service/src/service-constants.ts @@ -1,3 +1,8 @@ export const FLOW_SERVICE_VERSION = '0.1.0'; export const FLOW_SERVICE_NAME = 'flow-service'; + +/** + * Service instance ID - generated once per service invocation + */ +export const FLOW_SERVICE_INSTANCE_ID = Deno.env.get('FLOW_INSTANCE_ID') || crypto.randomUUID(); diff --git a/flow-service/src/utils/service-logger.ts b/flow-service/src/utils/service-logger.ts index e4b56f6..7653f71 100644 --- a/flow-service/src/utils/service-logger.ts +++ b/flow-service/src/utils/service-logger.ts @@ -22,7 +22,7 @@ export { handleCaughtError } from '../../../flow-core/src/utils/logger/error-han */ const SERVICE_CONTEXT = { serviceName: 'flow-service', - serviceVersion: Deno.env.get('FLOW_VERSION') || FLOW_SERVICE_VERSION, + serviceVersion: Deno.env.get('FLOW_SERVICE_VERSION') || FLOW_SERVICE_VERSION, environment: Deno.env.get('FLOW_ENV') || 'development', instanceId: Deno.env.get('FLOW_INSTANCE_ID') || crypto.randomUUID(), } as const; @@ -30,7 +30,7 @@ const SERVICE_CONTEXT = { /** * Service-specific logger configuration */ -export const SERVICE_LOGGER_CONFIG: LoggingConfig = { +export const SERVICE_LOGGER_DEFAULT_CONFIG: LoggingConfig = { consoleChannel: { logChannelEnabled: true, logLevel: 'info', @@ -59,7 +59,7 @@ export const SERVICE_LOGGER_CONFIG: LoggingConfig = { /** * Configured logger instance for flow-service with service-specific context */ -export const logger: EnhancedStructuredLogger = createEnhancedLogger(SERVICE_LOGGER_CONFIG); +export const logger: EnhancedStructuredLogger = createEnhancedLogger(SERVICE_LOGGER_DEFAULT_CONFIG); /** * Create a logger with additional service operation context From ded24994b3f15aab8ffd18fe5d641fbc4c4283d2 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Wed, 6 Aug 2025 21:01:53 -0700 Subject: [PATCH 07/10] Enhance logging and error handling across the service - Updated logging configuration to include SPARQL context support and standardized log levels. - Refactored logger methods to improve performance by removing unnecessary async/await where applicable. - Introduced new utility functions for relativizing and expanding RDF quads, with corresponding unit tests. - Enhanced error handling with the `handleCaughtError` function, providing detailed logging for various error scenarios. - Removed outdated documentation on error handling and improved logging messages for service startup and configuration. - Updated dependencies in `deno.lock` to ensure compatibility with the latest Deno standard library. --- deno.lock | 12 +- flow-core/src/deps.ts | 2 + .../src/utils/logger/structured-logger.ts | 14 +- flow-core/src/utils/rdfjs-utils.ts | 63 +++++++-- flow-core/src/utils/sparql-utils.ts | 12 +- flow-core/tests/unit/rdfjs-utils.test.ts | 74 ++++++++++ flow-service/deno.lock | 3 + .../documentation/error-handling-usage.md | 117 ---------------- flow-service/main.ts | 132 ++++++++++-------- .../src/config/loaders/quadstore-loader.ts | 41 +++--- .../resolution/service-config-resolver.ts | 5 +- flow-service/src/utils/startup-logger.ts | 45 +++--- 12 files changed, 272 insertions(+), 248 deletions(-) create mode 100644 flow-core/tests/unit/rdfjs-utils.test.ts delete mode 100644 flow-service/documentation/error-handling-usage.md diff --git a/deno.lock b/deno.lock index c6ef000..178e968 100644 --- a/deno.lock +++ b/deno.lock @@ -1,6 +1,13 @@ { "version": "5", + "redirects": { + "https://deno.land/std/assert/mod.ts": "https://deno.land/std@0.224.0/assert/mod.ts", + "https://deno.land/std/testing/asserts.ts": "https://deno.land/std@0.224.0/testing/asserts.ts", + "https://deno.land/std/testing/bdd.ts": "https://deno.land/std@0.224.0/testing/bdd.ts" + }, "remote": { + "https://deno.land/std@0.177.0/testing/_test_suite.ts": "30f018feeb3835f12ab198d8a518f9089b1bcb2e8c838a8b615ab10d5005465c", + "https://deno.land/std@0.177.0/testing/bdd.ts": "c5ca6d85940dbcc19b4d2bc3608d49ab65d81470aa91306d5efa4b0d5c945731", "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", @@ -127,7 +134,10 @@ "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/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", + "https://deno.land/std@0.224.0/testing/_test_suite.ts": "f10a8a6338b60c403f07a76f3f46bdc9f1e1a820c0a1decddeb2949f7a8a0546", + "https://deno.land/std@0.224.0/testing/asserts.ts": "d0cdbabadc49cc4247a50732ee0df1403fdcd0f95360294ad448ae8c240f3f5c", + "https://deno.land/std@0.224.0/testing/bdd.ts": "3e4de4ff6d8f348b5574661cef9501b442046a59079e201b849d0e74120d476b" }, "workspace": { "dependencies": [ diff --git a/flow-core/src/deps.ts b/flow-core/src/deps.ts index 098869f..8ec02d9 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -1,4 +1,5 @@ export { normalize } from 'https://deno.land/std@0.224.0/path/mod.ts'; +export { describe, it } from "https://deno.land/std/testing/bdd.ts"; export { assertEquals, assertNotStrictEquals, @@ -38,3 +39,4 @@ 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/structured-logger.ts b/flow-core/src/utils/logger/structured-logger.ts index efe1267..32995bf 100644 --- a/flow-core/src/utils/logger/structured-logger.ts +++ b/flow-core/src/utils/logger/structured-logger.ts @@ -10,7 +10,7 @@ import { shouldLog, mergeLogContext, } from './formatters.ts'; -import { getGlobalFileLogger, type FileLogger } from './file-logger.ts'; +import { getGlobalFileLogger } from './file-logger.ts'; import { isSentryEnabled, reportErrorToSentry, @@ -155,7 +155,7 @@ export class StructuredLoggerImpl implements StructuredLogger { ): Promise { // Write to console if enabled and level meets threshold if (this.config.consoleChannel?.logChannelEnabled && this.shouldLogToChannel(level, this.config.consoleChannel.logLevel)) { - await this.writeToConsole(level, message, context); + this.writeToConsole(level, message, context); } // Write to file if enabled and level meets threshold @@ -165,7 +165,7 @@ export class StructuredLoggerImpl implements StructuredLogger { // Write to Sentry if enabled, level meets threshold, and Sentry is initialized if (this.config.sentryChannel?.logChannelEnabled && this.shouldLogToChannel(level, this.config.sentryChannel.logLevel) && isSentryEnabled()) { - await this.writeToSentry(level, message, context, error); + this.writeToSentry(level, message, context, error); } } @@ -189,11 +189,11 @@ export class StructuredLoggerImpl implements StructuredLogger { /** * Write log entry to console */ - private async writeToConsole( + private writeToConsole( level: LogLevel, message: string, context?: LogContext, - ): Promise { + ): void { try { const consoleFormatted = formatConsoleMessage(level, message, context); @@ -244,12 +244,12 @@ export class StructuredLoggerImpl implements StructuredLogger { /** * Write log entry to Sentry */ - private async writeToSentry( + private writeToSentry( level: LogLevel, message: string, context?: LogContext, error?: Error, - ): Promise { + ): void { try { if (error) { // Error reporting diff --git a/flow-core/src/utils/rdfjs-utils.ts b/flow-core/src/utils/rdfjs-utils.ts index 6834b98..1889cdf 100644 --- a/flow-core/src/utils/rdfjs-utils.ts +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -26,7 +26,39 @@ export async function jsonldToQuads( ); } -export function relativeizeIds(inputQuads: RDF.Quad[], baseIRI: string): RDF.Quad[] { + +export function relativizeQuads(inputQuads: RDF.Quad[], baseIRI: string): RDF.Quad[] { + // 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}`); + } + + return inputQuads.map(quad => { + const subject = quad.subject.termType === 'NamedNode' ? quad.subject.value.replace(baseIRI, '') : quad.subject.value; + const predicate = quad.predicate.termType === 'NamedNode' ? quad.predicate.value.replace(baseIRI, '') : quad.predicate.value; + let object; + if (quad.object.termType === 'NamedNode') { + object = df.namedNode(quad.object.value.replace(baseIRI, '')); + } else if (quad.object.termType === 'Literal') { + object = df.literal(quad.object.value, quad.object.datatype); + } else { + object = quad.object; + } + const graph = quad.graph && quad.graph.termType === 'NamedNode' ? df.namedNode(quad.graph.value.replace(baseIRI, '')) : quad.graph.termType === 'DefaultGraph' ? df.defaultGraph() : undefined; + return df.quad( + quad.subject.termType === 'NamedNode' ? df.namedNode(subject) : quad.subject, + quad.predicate.termType === 'NamedNode' ? df.namedNode(predicate) : quad.predicate, + object, + graph + ); + }); +} + + +export function expandRelativeQuads(inputQuads: RDF.Quad[], baseIRI: string): RDF.Quad[] { // Validate baseIRI format if (!baseIRI.startsWith("http://") && !baseIRI.startsWith("https://")) { throw new Error(`Invalid baseIRI: must start with http:// or https://, got: ${baseIRI}`); @@ -36,20 +68,27 @@ export function relativeizeIds(inputQuads: RDF.Quad[], baseIRI: string): RDF.Qua } return inputQuads.map(quad => { - const subject = quad.subject.value.replace(baseIRI, ''); - const predicate = quad.predicate.value.replace(baseIRI, ''); - const object = quad.object.value.replace(baseIRI, ''); - const graph = quad.graph ? quad.graph.value.replace(baseIRI, '') : undefined; - - return { - '@id': subject, - [predicate]: object, - '@graph': graph, - }; + const subject = quad.subject.termType === 'NamedNode' ? (quad.subject.value.startsWith("http") ? quad.subject.value : baseIRI + quad.subject.value) : quad.subject.value; + const predicate = quad.predicate.termType === 'NamedNode' ? (quad.predicate.value.startsWith("http") ? quad.predicate.value : baseIRI + quad.predicate.value) : quad.predicate.value; + let object; + if (quad.object.termType === 'NamedNode') { + object = df.namedNode(quad.object.value.startsWith("http") ? quad.object.value : baseIRI + quad.object.value); + } else if (quad.object.termType === 'Literal') { + object = df.literal(quad.object.value, quad.object.datatype); + } else { + object = quad.object; + } + const graph = quad.graph && quad.graph.termType === 'NamedNode' ? df.namedNode(quad.graph.value.startsWith("http") ? quad.graph.value : baseIRI + quad.graph.value) : quad.graph.termType === 'DefaultGraph' ? df.defaultGraph() : undefined; + return df.quad( + quad.subject.termType === 'NamedNode' ? df.namedNode(subject) : quad.subject, + quad.predicate.termType === 'NamedNode' ? df.namedNode(predicate) : quad.predicate, + object, + graph + ); }); } -export function expandRelativeIds(inputJsonLd: NodeObject, baseIRI: string): any { +export function expandRelativeJsonLd(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}`); diff --git a/flow-core/src/utils/sparql-utils.ts b/flow-core/src/utils/sparql-utils.ts index 50d023e..1c34788 100644 --- a/flow-core/src/utils/sparql-utils.ts +++ b/flow-core/src/utils/sparql-utils.ts @@ -1,6 +1,9 @@ // Utility functions for SPARQL queries used across the codebase - import type { QuadstoreBundle } from '../types.ts'; +import { getComponentLogger } from '../utils/logger/component-logger.ts'; +import { handleCaughtError } from "./logger/error-handlers.ts"; + +const logger = getComponentLogger(import.meta); export async function querySingleValue( bundle: QuadstoreBundle, @@ -19,10 +22,11 @@ export async function querySingleValue( } } } catch (error) { - throw new Error(`Failed to execute SPARQL query: ${error instanceof Error ? error.message : String(error)}`); + handleCaughtError(error, `Failed to execute SPARQL query ${sparql}`); } if (values.length > 1) { - throw new Error('Expected single result but multiple values were returned'); + logger.warn(`Expected single result but got ${values.length} values for query: ${sparql}`); + //throw new Error('Expected single result but multiple values were returned'); } return values.length === 1 ? values[0] : undefined; } @@ -44,7 +48,7 @@ export async function queryMultipleValues( } } } catch (error) { - throw new Error(`Failed to execute SPARQL query: ${error instanceof Error ? error.message : String(error)}`); + handleCaughtError(error, `Failed to execute SPARQL query ${sparql}`); } return values; } diff --git a/flow-core/tests/unit/rdfjs-utils.test.ts b/flow-core/tests/unit/rdfjs-utils.test.ts new file mode 100644 index 0000000..fc5cecd --- /dev/null +++ b/flow-core/tests/unit/rdfjs-utils.test.ts @@ -0,0 +1,74 @@ +import { DataFactory } from 'npm:rdf-data-factory'; +import { relativizeQuads, expandRelativeQuads } from '../../src/utils/rdfjs-utils.ts'; +import type { RDF } from '../../src/deps.ts'; +import { assertEquals, describe, it } from '../../src/deps.ts'; + +const df = new DataFactory(); + +describe('relativizeQuads', () => { + const baseIRI = 'http://example.org/'; + + it('should relativize namedNode components correctly', () => { + const quads: RDF.Quad[] = [ + df.quad( + df.namedNode('http://example.org/subject'), + df.namedNode('http://example.org/predicate'), + df.namedNode('http://example.org/object'), + df.namedNode('http://example.org/graph') + ) + ]; + const result = relativizeQuads(quads, baseIRI); + assertEquals(result[0].subject.value, 'subject'); + assertEquals(result[0].predicate.value, 'predicate'); + assertEquals(result[0].object.value, 'object'); + assertEquals(result[0].graph?.value, 'graph'); + }); + + it('should preserve literals in object', () => { + const quads: RDF.Quad[] = [ + df.quad( + df.namedNode('http://example.org/subject'), + df.namedNode('http://example.org/predicate'), + df.literal('literal value', df.namedNode('http://www.w3.org/2001/XMLSchema#string')), + df.defaultGraph() + ) + ]; + const result = relativizeQuads(quads, baseIRI); + assertEquals(result[0].object.termType, 'Literal'); + assertEquals(result[0].object.value, 'literal value'); + }); +}); + +describe('expandRelativeQuads', () => { + const baseIRI = 'http://example.org/'; + + it('should expand namedNode components correctly', () => { + const quads: RDF.Quad[] = [ + df.quad( + df.namedNode('subject'), + df.namedNode('predicate'), + df.namedNode('object'), + df.namedNode('graph') + ) + ]; + const result = expandRelativeQuads(quads, baseIRI); + assertEquals(result[0].subject.value, 'http://example.org/subject'); + assertEquals(result[0].predicate.value, 'http://example.org/predicate'); + assertEquals(result[0].object.value, 'http://example.org/object'); + assertEquals(result[0].graph?.value, 'http://example.org/graph'); + }); + + it('should preserve literals in object', () => { + const quads: RDF.Quad[] = [ + df.quad( + df.namedNode('subject'), + df.namedNode('predicate'), + df.literal('literal value', df.namedNode('http://www.w3.org/2001/XMLSchema#string')), + df.defaultGraph() + ) + ]; + const result = expandRelativeQuads(quads, baseIRI); + assertEquals(result[0].object.termType, 'Literal'); + assertEquals(result[0].object.value, 'literal value'); + }); +}); diff --git a/flow-service/deno.lock b/flow-service/deno.lock index 4ea1718..98c2df3 100644 --- a/flow-service/deno.lock +++ b/flow-service/deno.lock @@ -2027,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/std/testing/bdd.ts": "https://deno.land/std@0.224.0/testing/bdd.ts", "https://deno.land/x/sentry/index.mjs": "https://deno.land/x/sentry@8.55.0/index.mjs" }, "remote": { @@ -2362,7 +2363,9 @@ "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/_test_suite.ts": "f10a8a6338b60c403f07a76f3f46bdc9f1e1a820c0a1decddeb2949f7a8a0546", "https://deno.land/std@0.224.0/testing/asserts.ts": "d0cdbabadc49cc4247a50732ee0df1403fdcd0f95360294ad448ae8c240f3f5c", + "https://deno.land/std@0.224.0/testing/bdd.ts": "3e4de4ff6d8f348b5574661cef9501b442046a59079e201b849d0e74120d476b", "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/documentation/error-handling-usage.md b/flow-service/documentation/error-handling-usage.md deleted file mode 100644 index da68d7e..0000000 --- a/flow-service/documentation/error-handling-usage.md +++ /dev/null @@ -1,117 +0,0 @@ -# Enhanced Error Handling with handleCaughtError - -The `handleCaughtError` function provides comprehensive error handling that -integrates with the structured logging system. It handles different error types -appropriately. - -## Usage - -```typescript -import { handleCaughtError } from './src/utils/logger.ts'; -``` - -## Basic Usage - -```typescript -try { - // Some operation that might fail - await riskyOperation(); -} catch (e) { - await handleCaughtError(e, 'During risky operation'); -} -``` - -## Examples by Error Type - -### Custom Flow Service Errors - -```typescript -import { - ConfigurationError, - FlowServiceError, - ValidationError, -} from './src/utils/errors.ts'; - -try { - throw new FlowServiceError( - 'Service initialization failed', - 'INIT_ERROR', - { component: 'mesh-scanner', retryCount: 3 }, - ); -} catch (e) { - await handleCaughtError(e, 'During service startup'); -} - -try { - throw new ValidationError( - 'Invalid configuration field', - 'port', - { providedValue: 'invalid', expectedType: 'number' }, - ); -} catch (e) { - await handleCaughtError(e, 'Config validation failed'); -} -``` - -### Standard JavaScript Errors - -```typescript -try { - throw new TypeError('Cannot read property of undefined'); -} catch (e) { - await handleCaughtError(e, 'Property access error'); -} -``` - -### Non-Error Values - -```typescript -try { - throw 'Something went wrong!'; -} catch (e) { - await handleCaughtError(e, 'String error thrown'); -} - -try { - throw 404; -} catch (e) { - await handleCaughtError(e, 'HTTP status error'); -} -``` - -## Features - -- **Comprehensive Error Type Handling**: Handles Error objects, custom error - classes, strings, numbers, null, undefined, and complex objects -- **Detailed Logging**: Provides stack traces, error causes, and comprehensive - context -- **Structured Logging Integration**: Uses the existing LogContext interface for - rich contextual information -- **Custom Message Support**: Optional custom message to provide additional - context -- **Fallback Handling**: Graceful degradation if logging itself fails - -## Log Output - -The function produces structured logs with different levels: - -- **ERROR**: Main error message with context -- **DEBUG**: Stack traces, error causes, and detailed error inspection - -Example output: - -``` -[2025-07-15T01:26:03.238Z] ERROR During service startup FlowServiceError: Service initialization failed -[2025-07-15T01:26:03.241Z] DEBUG FlowServiceError stack trace: (op=error-handling) -[2025-07-15T01:26:03.243Z] DEBUG FlowServiceError context: (op=error-handling) -``` - -## Integration with Existing Systems - -The function integrates seamlessly with: - -- **File Logging**: Writes structured JSON logs to files when enabled -- **Console Logging**: Provides colorized console output in development -- **Sentry Integration**: Automatically reports errors to Sentry when configured -- **Custom Error Classes**: Special handling for FlowServiceError, - ValidationError, ConfigurationError, etc. diff --git a/flow-service/main.ts b/flow-service/main.ts index 66f80e4..60d5b57 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -1,103 +1,112 @@ -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, singletonServiceConfigAccessor } from './src/config/index.ts'; +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, + singletonServiceConfigAccessor, +} from "./src/config/index.ts"; import { logStartupConfiguration, logStartupUrls, -} from './src/utils/startup-logger.ts'; -import { handleCaughtError } from '../flow-core/src/utils/logger/error-handlers.ts'; -import type { LogContext } from '../flow-core/src/utils/logger/logger-types.ts'; -import { createServiceLogContext } from './src/utils/service-log-context.ts'; -import { MESH } from '../flow-core/src/mesh-constants.ts'; +} from "./src/utils/startup-logger.ts"; +import { handleCaughtError } from "../flow-core/src/utils/logger/error-handlers.ts"; +import type { LogContext } from "../flow-core/src/utils/logger/logger-types.ts"; +import { createServiceLogContext } from "./src/utils/service-log-context.ts"; +import { MESH } from "../flow-core/src/mesh-constants.ts"; import { setGlobalLoggerConfig } from "../flow-core/src/utils/logger/component-logger.ts"; import { SERVICE_LOGGER_DEFAULT_CONFIG } from "./src/utils/service-logger.ts"; -import { getComponentLogger } from '../flow-core/src/utils/logger/component-logger.ts'; +import { getComponentLogger } from "../flow-core/src/utils/logger/component-logger.ts"; // initialize a logger with default config until we can process the service config setGlobalLoggerConfig(SERVICE_LOGGER_DEFAULT_CONFIG); let logger = getComponentLogger(import.meta); -logger.info('Starting Flow Service with initial logger configuration'); +logger.info("Starting Flow Service with initial logger configuration"); // Initialize configuration system try { await createServiceConfig(); } catch (error) { const context: LogContext = createServiceLogContext({ - operation: 'startup', - component: 'service-config-init', + operation: "createServiceConfig", }); - await handleCaughtError(error, 'Failed to initialize service configuration', context); - logger.error( - '❌ Service startup failed due to configuration error. Exiting...', + await handleCaughtError( + error, + "Failed to initialize service configuration", + context, ); Deno.exit(1); } // now that config is loaded, reinitialize logger with service context try { - const { extractLoggingConfigFromService } = await import('./src/config/logging-config-extractor.ts'); + const { extractLoggingConfigFromService } = await import( + "./src/config/logging-config-extractor.ts" + ); const loggingConfig = await extractLoggingConfigFromService(); setGlobalLoggerConfig(loggingConfig); // Get a new logger instance with the updated configuration logger = getComponentLogger(import.meta); - logger.info('Logger reinitialized with service configuration'); + logger.info("Logger reinitialized with service configuration"); } catch (error) { const context: LogContext = createServiceLogContext({ - operation: 'startup', - component: 'logger-reinit', + operation: "reinitialize-logger", }); - await handleCaughtError(error, 'Failed to reinitialize logger with service configuration', context); - console.warn('⚠️ Logger reinitialization failed, continuing with default configuration...'); + await handleCaughtError( + error, + "Failed to reinitialize logger with service configuration", + context, + ); } - // Log service startup with configuration info try { logStartupConfiguration(); } catch (error) { const context: LogContext = createServiceLogContext({ - operation: 'startup', - component: 'startup-config-logging', + operation: "logStartupConfiguration", }); - await handleCaughtError(error, 'Failed to log startup configuration', context); - console.error('⚠️ Configuration logging failed, but continuing startup...'); + await handleCaughtError( + error, + "Failed to log startup configuration", + context, + ); } const app = new OpenAPIHono(); -app.get('/', (c) => { - return c.text('Flow Service - Semantic Mesh Management API'); +app.get("/", (c) => { + return c.text("Flow Service - Semantic Mesh Management API"); }); // OpenAPI documentation const content = { - openapi: '3.1.0', + openapi: "3.1.0", info: { - version: '0.1.0', - title: 'Flow Service API', - description: 'REST API for semantic mesh management and weave processes', + version: "0.1.0", + title: "Flow Service API", + description: "REST API for semantic mesh management and weave processes", }, servers: [ { - url: `http://${await singletonServiceConfigAccessor.getHost()}:${await singletonServiceConfigAccessor.getPort()}`, - description: 'Configured server', + url: `http://${await singletonServiceConfigAccessor + .getHost()}:${await singletonServiceConfigAccessor.getPort()}`, + description: "Configured server", }, ], }; -app.doc('/openapi.json', content); +app.doc("/openapi.json", content); // Scalar API documentation app.get( MESH.API_PORTAL_ROUTE, apiReference({ - spec: { url: '/openapi.json' }, - pageTitle: 'Semantic Flow Service API Docs', - theme: 'default', - layout: 'classic', + spec: { url: "/openapi.json" }, + pageTitle: "Semantic Flow Service API Docs", + theme: "default", + layout: "classic", }), ); @@ -106,39 +115,39 @@ try { markdown = await createMarkdownFromOpenApi(JSON.stringify(content)); } catch (error) { const context: LogContext = createServiceLogContext({ - operation: 'startup', - component: 'markdown-generation', + operation: "startup", }); - await handleCaughtError(error, 'Failed to generate markdown documentation', context); - console.error('⚠️ Markdown generation failed, but continuing startup...'); - markdown = '# API Documentation\n\nDocumentation generation failed.'; + await handleCaughtError( + error, + "Failed to generate markdown documentation", + context, + ); + markdown = "# API Documentation\n\nDocumentation generation failed."; } -app.get('/llms.txt', (c) => { +app.get("/llms.txt", (c) => { return c.text(markdown); }); // Mount health routes -app.route('/api', health); -import { createMeshesRoutes } from './src/routes/meshes.ts'; -import { createWeaveRoutes } from './src/routes/weave.ts'; +app.route("/api", health); +import { createMeshesRoutes } from "./src/routes/meshes.ts"; +import { createWeaveRoutes } from "./src/routes/weave.ts"; const meshes = createMeshesRoutes(); const weave = createWeaveRoutes(); -app.route('/api', meshes); -app.route('/api', weave); +app.route("/api", meshes); +app.route("/api", weave); // Startup logging try { logStartupUrls(); } catch (error) { const context: LogContext = createServiceLogContext({ - operation: 'startup', - component: 'startup-url-logging', + operation: "startup", }); - await handleCaughtError(error, 'Failed to log startup URLs', context); - console.error('⚠️ URL logging failed, but continuing startup...'); + await handleCaughtError(error, "Failed to log startup URLs", context); } try { @@ -148,13 +157,12 @@ try { }, app.fetch); } catch (error) { const context: LogContext = createServiceLogContext({ - operation: 'startup', - component: 'http-server-start', + operation: "startup", metadata: { - serverEndpoint: `http://${await singletonServiceConfigAccessor.getHost()}:${await singletonServiceConfigAccessor.getPort()}` - } + serverEndpoint: `http://${await singletonServiceConfigAccessor + .getHost()}:${await singletonServiceConfigAccessor.getPort()}`, + }, }); - await handleCaughtError(error, 'Failed to start HTTP server', context); - console.error('❌ Server startup failed. Exiting...'); + await handleCaughtError(error, "Failed to start HTTP server", context); Deno.exit(1); } diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index aacabb4..8f6fb52 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -4,8 +4,12 @@ import { PLATFORM_SERVICE_DEFAULTS, PLATFORM_NODE_DEFAULTS } from '../defaults.t import { clearGraph, copyGraph, createNewGraphFromJsonLd } from '../../../../flow-core/src/utils/quadstore/quadstore-utils.ts'; import { handleCaughtError } from '../../../../flow-core/src/utils/logger/error-handlers.ts'; import { CONFIG_GRAPH_NAMES } from '../index.ts'; -import { expandRelativeIds } from "../../../../flow-core/src/utils/rdfjs-utils.ts"; +import { expandRelativeQuads, relativizeQuads, expandRelativeJsonLd } from "../../../../flow-core/src/utils/rdfjs-utils.ts"; import { getCurrentServiceUri } from "../../utils/service-uri-builder.ts"; +import { getComponentLogger } from "../../../../flow-core/src/utils/logger/component-logger.ts"; + +const logger = getComponentLogger(import.meta); + /** * Load platform defaults into Quadstore graphs */ @@ -13,8 +17,8 @@ export async function loadPlatformServiceDefaults(): Promise { try { const uri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.platformServiceDefaults); - const expandedPlatformServiceDefaults = expandRelativeIds(PLATFORM_SERVICE_DEFAULTS, uri); - //console.log(`Loading platform service defaults into graph:\n ${JSON.stringify(expandedPlatformServiceDefaults)}`); + const expandedPlatformServiceDefaults = expandRelativeJsonLd(PLATFORM_SERVICE_DEFAULTS, uri); + //logger.log(`Loading platform service defaults into graph:\n ${JSON.stringify(expandedPlatformServiceDefaults)}`); // use the Service URI for the graph name await createNewGraphFromJsonLd(expandedPlatformServiceDefaults, { graphName: uri }); } catch (error) { @@ -35,7 +39,7 @@ export async function loadPlatformServiceDefaults(): Promise { export async function loadPlatformImplicitMeshRootNodeConfig(): Promise { const uri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.platformImplicitMeshRootNodeConfig); - const expandedPlatformNodeDefaults = expandRelativeIds(PLATFORM_NODE_DEFAULTS, uri); + const expandedPlatformNodeDefaults = expandRelativeJsonLd(PLATFORM_NODE_DEFAULTS, uri); await createNewGraphFromJsonLd(expandedPlatformNodeDefaults, { graphName: uri }); } @@ -43,15 +47,15 @@ export async function loadPlatformImplicitMeshRootNodeConfig(): Promise { * Load input service config into Quadstore graphs (both into inputServiceConfig and as a base for mergedServiceConfig) */ export async function loadInputServiceConfig(inputConfig: ServiceConfigInput): Promise { - //console.log(inputConfig); + //logger.log(inputConfig); const inputUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.inputServiceConfig); const mergedUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig); - const expandedInputServiceConfig = expandRelativeIds(inputConfig, inputUri); - //console.log(expandedInputServiceConfig) + const expandedInputServiceConfig = expandRelativeJsonLd(inputConfig, inputUri); + //logger.log(expandedInputServiceConfig) await createNewGraphFromJsonLd(expandedInputServiceConfig, { graphName: inputUri }); - const expandedMergedServiceConfig = expandRelativeIds(inputConfig, mergedUri); + const expandedMergedServiceConfig = expandRelativeJsonLd(inputConfig, mergedUri); await createNewGraphFromJsonLd(expandedMergedServiceConfig, { graphName: mergedUri }); } @@ -60,7 +64,7 @@ export async function loadInputServiceConfig(inputConfig: ServiceConfigInput): P */ export async function loadInputMeshRootNodeConfig(inputMeshRootNodeConfig: MeshRootNodeConfigInput): Promise { const uri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.inputMeshRootNodeConfig); - const expandedInputMeshRootNodeConfig = expandRelativeIds(inputMeshRootNodeConfig, uri); + const expandedInputMeshRootNodeConfig = expandRelativeJsonLd(inputMeshRootNodeConfig, uri); await createNewGraphFromJsonLd(expandedInputMeshRootNodeConfig, { graphName: uri }); } @@ -78,22 +82,27 @@ export async function mergeServiceConfigGraphs( const defaultUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.platformServiceDefaults); // Copy platform service defaults quads if not overridden - const platformQuads = store.match(undefined, undefined, undefined, df.namedNode(defaultUri)); + const originalPlatformQuads = await store.get({ subject: undefined, predicate: undefined, object: undefined, graph: df.namedNode(defaultUri) }); + const relativeizedPlatformQuads = relativizeQuads(originalPlatformQuads.items, defaultUri); + const expandedForMergedPlatformQuads = expandRelativeQuads(relativeizedPlatformQuads, mergedUri); let platformQuadsCopied = 0; - for await (const q of platformQuads) { + for await (const q of expandedForMergedPlatformQuads) { + logger.debug(`platform subject: ${q.subject.value}`); const existingQuads = await store.get({ subject: q.subject, predicate: q.predicate, object: undefined, graph: df.namedNode(mergedUri) }); - //console.log(`${existingQuads.items}\n`) - + logger.debug(`existing subject: ${existingQuads.items[0]?.subject.value}`); + logger.debug(`${q.subject.value.split('/').pop()}-${q.predicate.value.split('/').pop()}: ${existingQuads.items.length}`); if (existingQuads.items.length === 0) { - //console.log(`platform quad copied: ${q.subject.value} ${q.predicate.value} ${q.object.value} in graph ${mergedUri}`); + logger.info(`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))); platformQuadsCopied++; + } else if (existingQuads.items.length === 1) { + logger.debug(`Quad already exists in merged graph: ${q.subject.value} ${q.predicate.value} ${q.object.value}`); } else if (existingQuads.items.length > 1) { - console.error(`Multiple quads found for ${q.subject.value} ${q.predicate.value} in graph ${mergedUri}. This may indicate a configuration error.`); + logger.error(`Multiple quads found for ${q.subject.value} ${q.predicate.value} in graph ${mergedUri}. This may indicate a configuration error.`); } } - //console.log(`platform quads copied: ${platformQuadsCopied}`); + logger.info(`platform quads copied: ${platformQuadsCopied}`); } diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index f518c80..17cee0b 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -12,6 +12,9 @@ import { LogContext } from '../../../../flow-core/src/utils/logger/logger-types. 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'; +import { getComponentLogger } from "../../../../flow-core/src/utils/logger/component-logger.ts"; + +const logger = getComponentLogger(import.meta); /** * Asynchronously resolves the service configuration by merging CLI options, environment variables, configuration files, and environment-specific defaults in a defined precedence order. @@ -32,7 +35,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) { diff --git a/flow-service/src/utils/startup-logger.ts b/flow-service/src/utils/startup-logger.ts index 8215251..02bd825 100644 --- a/flow-service/src/utils/startup-logger.ts +++ b/flow-service/src/utils/startup-logger.ts @@ -7,7 +7,9 @@ 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'; +import { getComponentLogger } from "../../../flow-core/src/utils/logger/component-logger.ts"; +const logger = getComponentLogger(import.meta); /** * Logs the service startup configuration details with a timestamp in US locale. @@ -16,57 +18,44 @@ import { resolve } from '../../../flow-core/src/deps.ts'; */ export async function logStartupConfiguration(): Promise { if (!config.isInitialized()) { - console.error('⚠️ Service configuration is not initialized. Skipping startup logging.'); - Deno.exit(0); + logger.error('Service configuration is not initialized.'); + Deno.exit(1); } - const now = new Date(); - const timestamp = now.toLocaleDateString('en-US', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - }) + ' ' + now.toLocaleTimeString('en-US', { - hour: '2-digit', - minute: '2-digit', - hour12: true, - }).toLowerCase(); - - console.log( - `🔧 Flow Service initializing at ${timestamp} with configuration:`, - ); - const meshPaths = await config.getMeshPaths(); if (meshPaths.length > 0) { + let message = `Configured mesh paths:`; for (const meshPath of meshPaths) { const absolutePath = resolve(Deno.cwd(), meshPath); - console.log(` Configured mesh path: ${absolutePath}`); + message += `\n - ${absolutePath}`; } + logger.info(message); } else { - console.log(` Mesh Paths: none configured`); + logger.info(` Mesh Paths: none configured`); } // Use custom config accessors for logging channels try { const consoleConfig = await config.getConsoleLoggingConfig(); - console.log(` Console Logging: ${consoleConfig.enabled ? (consoleConfig.level ?? 'enabled') : 'disabled'}`); + logger.info(` Console Logging: ${consoleConfig.enabled ? (consoleConfig.level ?? 'enabled') : 'disabled'}`); } catch { - console.log(` Console Logging: error fetching config`); + logger.info(` Console Logging: error fetching config`); } try { const fileConfig = await config.getFileLoggingConfig(); - console.log(` File Logging: ${fileConfig.enabled ? (fileConfig.level ?? 'enabled') : 'disabled'}`); + logger.info(` File Logging: ${fileConfig.enabled ? (fileConfig.level ?? 'enabled') : 'disabled'}`); } catch { - console.log(` File Logging: error fetching config`); + logger.info(` File Logging: error fetching config`); } try { const sentryConfig = await config.getSentryLoggingConfig(); - console.log(` Sentry Logging: ${sentryConfig.enabled ? (sentryConfig.level ?? 'enabled') : 'disabled'}`); + logger.info(` Sentry Logging: ${sentryConfig.enabled ? (sentryConfig.level ?? 'enabled') : 'disabled'}`); } catch { - console.log(` Sentry Logging: error fetching config`); + logger.info(` Sentry Logging: error fetching config`); } const enabledServices: string[] = []; @@ -75,7 +64,7 @@ export async function logStartupConfiguration(): Promise { // if (await config.sparqlEnabled()) enabledServices.push('SPARQL Endpoint'); // if (await config.queryWidgetEnabled()) enabledServices.push('SPARQL GUI'); - console.log( + logger.info( ` Services: ${enabledServices.length > 0 ? enabledServices.join(', ') : 'none'}`, ); } @@ -90,6 +79,6 @@ export async function logStartupUrls(): Promise { const scheme = await config.getScheme() || 'http'; const baseUrl = `${scheme}://${host}:${port}`; - console.log(`📍 Root: ${baseUrl}/`); - console.log(`📍 API documentation: ${baseUrl}${MESH.API_PORTAL_ROUTE}`); + logger.info(`📍 Root: ${baseUrl}/`); + logger.info(`📍 API documentation: ${baseUrl}${MESH.API_PORTAL_ROUTE}`); } From 75a5b9072cab223918d067190fc01a53e7b49ec7 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Wed, 6 Aug 2025 21:21:28 -0700 Subject: [PATCH 08/10] address or comment-out linting problems --- flow-core/src/utils/logger/error-handlers.ts | 8 +++++--- flow-core/src/utils/rdfjs-utils.ts | 4 ++-- flow-core/src/utils/sparql-utils.ts | 2 ++ flow-service/src/config/loaders/quadstore-loader.ts | 6 +++--- .../src/config/resolution/service-config-accessor.ts | 7 ++++--- .../src/config/resolution/service-config-resolver.ts | 10 ++++------ 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/flow-core/src/utils/logger/error-handlers.ts b/flow-core/src/utils/logger/error-handlers.ts index 8611296..bd997c7 100644 --- a/flow-core/src/utils/logger/error-handlers.ts +++ b/flow-core/src/utils/logger/error-handlers.ts @@ -36,6 +36,7 @@ export async function handleError( // Extract error information for structured context const errorContext = error instanceof Error ? { errorType: error.constructor.name, + // deno-lint-ignore no-explicit-any errorCode: (error as any).code, stackTrace: error.stack, originalError: error @@ -123,6 +124,7 @@ export async function handleCaughtError( const logDetailedError = async (error: Error, type = 'Error') => { const errorContext = { errorType: error.constructor.name, + // deno-lint-ignore no-explicit-any errorCode: (error as any).code, stackTrace: error.stack, originalError: error @@ -432,7 +434,7 @@ export async function withErrorHandling( */ export function createContextualErrorHandler(baseContext: LogContext) { return { - handleError: async ( + handleError: ( error: unknown, context?: string, additionalContext?: LogContext, @@ -443,7 +445,7 @@ export function createContextualErrorHandler(baseContext: LogContext) { return handleError(error, context, mergedContext, meta, options); }, - handleCaughtError: async ( + handleCaughtError: ( error: unknown, customMessage?: string, additionalContext?: LogContext, @@ -453,7 +455,7 @@ export function createContextualErrorHandler(baseContext: LogContext) { return handleCaughtError(error, customMessage, mergedContext, options); }, - withErrorHandling: async ( + withErrorHandling: ( operation: () => Promise, context?: string, additionalContext?: LogContext, diff --git a/flow-core/src/utils/rdfjs-utils.ts b/flow-core/src/utils/rdfjs-utils.ts index 1889cdf..a1682a7 100644 --- a/flow-core/src/utils/rdfjs-utils.ts +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -88,7 +88,7 @@ export function expandRelativeQuads(inputQuads: RDF.Quad[], baseIRI: string): RD }); } -export function expandRelativeJsonLd(inputJsonLd: NodeObject, baseIRI: string): any { +export function expandRelativeJsonLd(inputJsonLd: NodeObject, baseIRI: string): NodeObject { // Validate baseIRI format if (!baseIRI.startsWith("http://") && !baseIRI.startsWith("https://")) { throw new Error(`Invalid baseIRI: must start with http:// or https://, got: ${baseIRI}`); @@ -98,7 +98,7 @@ export function expandRelativeJsonLd(inputJsonLd: NodeObject, baseIRI: string): } const expanded = structuredClone(inputJsonLd); - + // deno-lint-ignore no-explicit-any function rewrite(obj: any) { if (obj && typeof obj === "object") { if (typeof obj["@id"] === "string" && !obj["@id"].startsWith("http")) { diff --git a/flow-core/src/utils/sparql-utils.ts b/flow-core/src/utils/sparql-utils.ts index 1c34788..2893fba 100644 --- a/flow-core/src/utils/sparql-utils.ts +++ b/flow-core/src/utils/sparql-utils.ts @@ -15,6 +15,7 @@ export async function querySingleValue( const values: string[] = []; try { const bindingsStream = await bundle.engine.queryBindings(sparql, { sources: [bundle.store] }); + // deno-lint-ignore no-explicit-any for await (const binding of bindingsStream as any) { const value = binding.get('value'); if (value) { @@ -41,6 +42,7 @@ export async function queryMultipleValues( const values: string[] = []; try { const bindingsStream = await bundle.engine.queryBindings(sparql, { sources: [bundle.store] }); + // deno-lint-ignore no-explicit-any for await (const binding of bindingsStream as any) { const value = binding.get('value'); if (value) { diff --git a/flow-service/src/config/loaders/quadstore-loader.ts b/flow-service/src/config/loaders/quadstore-loader.ts index 8f6fb52..a16ad5c 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/quadstore-utils.ts'; +import { 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 { expandRelativeQuads, relativizeQuads, expandRelativeJsonLd } from "../../../../flow-core/src/utils/rdfjs-utils.ts"; @@ -92,7 +92,7 @@ export async function mergeServiceConfigGraphs( logger.debug(`existing subject: ${existingQuads.items[0]?.subject.value}`); logger.debug(`${q.subject.value.split('/').pop()}-${q.predicate.value.split('/').pop()}: ${existingQuads.items.length}`); if (existingQuads.items.length === 0) { - logger.info(`platform quad copied: ${q.subject.value} ${q.predicate.value} ${q.object.value} in graph ${mergedUri}`); + logger.debug(`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))); platformQuadsCopied++; @@ -102,7 +102,7 @@ export async function mergeServiceConfigGraphs( logger.error(`Multiple quads found for ${q.subject.value} ${q.predicate.value} in graph ${mergedUri}. This may indicate a configuration error.`); } } - logger.info(`platform quads copied: ${platformQuadsCopied}`); + logger.debug(`platform quads copied: ${platformQuadsCopied}`); } diff --git a/flow-service/src/config/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts index 7a9a515..0b4b327 100644 --- a/flow-service/src/config/resolution/service-config-accessor.ts +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -52,15 +52,15 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { } async getHost(): Promise { - return this.getConfigValue('fsvc:host'); + return await this.getConfigValue('fsvc:host'); } async getScheme(): Promise { - return this.getConfigValue('fsvc:scheme'); + return await this.getConfigValue('fsvc:scheme'); } async getMeshPaths(): Promise { - return this.getMultipleConfigValues('fsvc:meshPaths'); + return await this.getMultipleConfigValues('fsvc:meshPaths'); } // Custom accessors for logging channels @@ -119,6 +119,7 @@ export const singletonServiceConfigAccessor = new (class ServiceConfigAccessor { } try { const bindingsStream = await this.bundle.engine.queryBindings(sparql, { sources: [this.bundle.store] }); + // deno-lint-ignore no-explicit-any for await (const binding of bindingsStream as any) { const value = binding.get(variable); if (value) { diff --git a/flow-service/src/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index 17cee0b..e6bdecf 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 @@ const logger = getComponentLogger(import.meta); * @throws ConfigError if configuration resolution fails or an unexpected error occurs */ -import { loadPlatformServiceDefaults, loadInputServiceConfig, loadInputMeshRootNodeConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.ts'; +import { loadPlatformServiceDefaults, loadInputServiceConfig, mergeServiceConfigGraphs } from '../loaders/quadstore-loader.ts'; import { singletonServiceConfigAccessor } from "./service-config-accessor.ts"; export async function resolveServiceConfig( @@ -50,7 +50,7 @@ export async function resolveServiceConfig( const mergedInputConfig = mergeConfigs(mergeConfigs(envConfig, fileConfig ?? {}), cliConfig); // Extract service URI configuration from merged config, using platform defaults as fallback - // TODO: + // This is used for for providing URL expansion before configuration merging has finished const serviceUriConfig: ServiceUriConfig = { scheme: mergedInputConfig['fsvc:scheme'] ?? PLATFORM_SERVICE_DEFAULTS['fsvc:scheme'], host: mergedInputConfig['fsvc:host'] ?? PLATFORM_SERVICE_DEFAULTS['fsvc:host'], @@ -66,11 +66,9 @@ export async function resolveServiceConfig( // Load merged input config into Quadstore graph await loadInputServiceConfig(mergedInputConfig); - // TODO: Load input mesh node config if applicable - // await loadInputMeshRootNodeConfig(...); - - // Merge all graphs into mergedServiceConfig graph + // Merge service config graphs into mergedServiceConfig graph await mergeServiceConfigGraphs(); + logger.info(`Service configuration loaded from: ${serviceConfigPath || 'default environment'}`); } catch (error) { const context: LogContext = createServiceLogContext({ From d525a357abbb69e25acabac302c667c53908fd9a Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Wed, 6 Aug 2025 21:48:55 -0700 Subject: [PATCH 09/10] Fix import paths and enhance logging in various modules; update service constants for consistency --- flow-core/src/deps.ts | 2 +- flow-core/src/utils/logger/index.ts | 2 +- flow-core/src/utils/logger/logger-types.ts | 11 ----------- flow-core/src/utils/quadstore/quadstore-utils.ts | 10 ++++++++-- flow-core/src/utils/rdfjs-utils.ts | 12 ++++++++---- flow-core/src/utils/sparql-utils.ts | 5 +++-- flow-service/src/service-constants.ts | 2 +- flow-service/src/utils/service-logger.ts | 4 ++-- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/flow-core/src/deps.ts b/flow-core/src/deps.ts index 8ec02d9..9686ad3 100644 --- a/flow-core/src/deps.ts +++ b/flow-core/src/deps.ts @@ -1,5 +1,5 @@ export { normalize } from 'https://deno.land/std@0.224.0/path/mod.ts'; -export { describe, it } from "https://deno.land/std/testing/bdd.ts"; +export { describe, it } from "https://deno.land/std@0.224.0/testing/bdd.ts"; export { assertEquals, assertNotStrictEquals, diff --git a/flow-core/src/utils/logger/index.ts b/flow-core/src/utils/logger/index.ts index 2999d44..2bc2520 100644 --- a/flow-core/src/utils/logger/index.ts +++ b/flow-core/src/utils/logger/index.ts @@ -20,7 +20,7 @@ export type { } from './logger-types.ts'; export type { LogLevel } from './logger-types.ts'; -export { validLogLevels, LogLevelValues } from './logger-types.ts'; +export { validLogLevels } from './logger-types.ts'; // Export formatting utilities (LogLevel type is now imported from logger-types.ts) export { diff --git a/flow-core/src/utils/logger/logger-types.ts b/flow-core/src/utils/logger/logger-types.ts index 2fa4a85..5c3e6ce 100644 --- a/flow-core/src/utils/logger/logger-types.ts +++ b/flow-core/src/utils/logger/logger-types.ts @@ -160,17 +160,6 @@ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'critical'; */ export const validLogLevels = ['debug', 'info', 'warn', 'error', 'critical'] as const; -/** - * Log level numeric values for comparison and filtering - */ -export const LogLevelValues = { - debug: 0, - info: 1, - warn: 2, - error: 3, - critical: 4, -} as const; - /** * Base log channel configuration interface aligned with ontology fsvc:LogChannelConfig */ diff --git a/flow-core/src/utils/quadstore/quadstore-utils.ts b/flow-core/src/utils/quadstore/quadstore-utils.ts index 638faad..608ace3 100644 --- a/flow-core/src/utils/quadstore/quadstore-utils.ts +++ b/flow-core/src/utils/quadstore/quadstore-utils.ts @@ -3,6 +3,9 @@ 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 { getComponentLogger } from '../logger/component-logger.ts'; + +const logger = getComponentLogger(import.meta); export function countQuadsInStream(stream: RDF.Stream): Promise { return new Promise((resolve, reject) => { @@ -41,6 +44,7 @@ export async function clearGraph( 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); + // deno-lint-ignore no-explicit-any await store.delStream(matchStream2 as any); /*const matchStream3 = store.match(undefined, undefined, undefined, graph); const count2 = await countQuadsInStream(matchStream3); @@ -62,9 +66,11 @@ export async function copyGraph( ): Promise { const stream = store.match(undefined, undefined, undefined, sourceGraph); const quads = []; - console.log("COPIED QUADS:"); + + logger.info("COPIED QUADS:"); + // deno-lint-ignore no-explicit-any for await (const q of stream as any) { - console.log(`${q.subject.value} ${q.predicate.value} ${q.object.value} to graph ${targetGraph.value}`); + logger.info(`${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); } diff --git a/flow-core/src/utils/rdfjs-utils.ts b/flow-core/src/utils/rdfjs-utils.ts index a1682a7..4331241 100644 --- a/flow-core/src/utils/rdfjs-utils.ts +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -68,17 +68,21 @@ export function expandRelativeQuads(inputQuads: RDF.Quad[], baseIRI: string): RD } return inputQuads.map(quad => { - const subject = quad.subject.termType === 'NamedNode' ? (quad.subject.value.startsWith("http") ? quad.subject.value : baseIRI + quad.subject.value) : quad.subject.value; - const predicate = quad.predicate.termType === 'NamedNode' ? (quad.predicate.value.startsWith("http") ? quad.predicate.value : baseIRI + quad.predicate.value) : quad.predicate.value; + const isAbsoluteURI = (uri: string) => /^[a-z][a-z0-9+.-]*:/i.test(uri); + const subject = quad.subject.termType === 'NamedNode' ? (isAbsoluteURI(quad.subject.value) ? quad.subject.value : baseIRI + quad.subject.value) : quad.subject.value; + const predicate = quad.predicate.termType === 'NamedNode' ? (isAbsoluteURI(quad.predicate.value) ? quad.predicate.value : baseIRI + quad.predicate.value) : quad.predicate.value; + + let object; if (quad.object.termType === 'NamedNode') { - object = df.namedNode(quad.object.value.startsWith("http") ? quad.object.value : baseIRI + quad.object.value); + object = df.namedNode(isAbsoluteURI(quad.object.value) ? quad.object.value : baseIRI + quad.object.value); } else if (quad.object.termType === 'Literal') { object = df.literal(quad.object.value, quad.object.datatype); } else { object = quad.object; } - const graph = quad.graph && quad.graph.termType === 'NamedNode' ? df.namedNode(quad.graph.value.startsWith("http") ? quad.graph.value : baseIRI + quad.graph.value) : quad.graph.termType === 'DefaultGraph' ? df.defaultGraph() : undefined; + const graph = quad.graph && quad.graph.termType === 'NamedNode' ? df.namedNode(isAbsoluteURI(quad.graph.value) ? quad.graph.value : baseIRI + quad.graph.value) : quad.graph.termType === 'DefaultGraph' ? df.defaultGraph() : undefined; + return df.quad( quad.subject.termType === 'NamedNode' ? df.namedNode(subject) : quad.subject, quad.predicate.termType === 'NamedNode' ? df.namedNode(predicate) : quad.predicate, diff --git a/flow-core/src/utils/sparql-utils.ts b/flow-core/src/utils/sparql-utils.ts index 2893fba..8f76496 100644 --- a/flow-core/src/utils/sparql-utils.ts +++ b/flow-core/src/utils/sparql-utils.ts @@ -12,6 +12,7 @@ export async function querySingleValue( if (!bundle.engine) { throw new Error('SPARQL engine not initialized in Quadstore bundle'); } + logger.debug(`Executing SPARQL query: ${sparql}`); const values: string[] = []; try { const bindingsStream = await bundle.engine.queryBindings(sparql, { sources: [bundle.store] }); @@ -26,8 +27,7 @@ export async function querySingleValue( handleCaughtError(error, `Failed to execute SPARQL query ${sparql}`); } if (values.length > 1) { - logger.warn(`Expected single result but got ${values.length} values for query: ${sparql}`); - //throw new Error('Expected single result but multiple values were returned'); + throw new Error('Expected single result but multiple values were returned'); } return values.length === 1 ? values[0] : undefined; } @@ -39,6 +39,7 @@ export async function queryMultipleValues( if (!bundle.engine) { throw new Error('SPARQL engine not initialized in Quadstore bundle'); } + logger.debug(`Executing SPARQL query: ${sparql}`); const values: string[] = []; try { const bindingsStream = await bundle.engine.queryBindings(sparql, { sources: [bundle.store] }); diff --git a/flow-service/src/service-constants.ts b/flow-service/src/service-constants.ts index 0770fd8..d18b74a 100644 --- a/flow-service/src/service-constants.ts +++ b/flow-service/src/service-constants.ts @@ -5,4 +5,4 @@ export const FLOW_SERVICE_NAME = 'flow-service'; /** * Service instance ID - generated once per service invocation */ -export const FLOW_SERVICE_INSTANCE_ID = Deno.env.get('FLOW_INSTANCE_ID') || crypto.randomUUID(); +export const FLOW_SERVICE_INSTANCE_ID = Deno.env.get('FLOW_SERVICE_INSTANCE_ID') || crypto.randomUUID(); diff --git a/flow-service/src/utils/service-logger.ts b/flow-service/src/utils/service-logger.ts index 7653f71..1973f7e 100644 --- a/flow-service/src/utils/service-logger.ts +++ b/flow-service/src/utils/service-logger.ts @@ -11,7 +11,7 @@ import { type StructuredLogger, type EnhancedStructuredLogger, } from '../../../flow-core/src/utils/logger/index.ts'; -import { FLOW_SERVICE_VERSION } from "../service-constants.ts"; +import { FLOW_SERVICE_VERSION, FLOW_SERVICE_INSTANCE_ID } from "../service-constants.ts"; // Re-export formatters and error handlers for test access export { formatConsoleMessage } from '../../../flow-core/src/utils/logger/formatters.ts'; @@ -24,7 +24,7 @@ const SERVICE_CONTEXT = { serviceName: 'flow-service', serviceVersion: Deno.env.get('FLOW_SERVICE_VERSION') || FLOW_SERVICE_VERSION, environment: Deno.env.get('FLOW_ENV') || 'development', - instanceId: Deno.env.get('FLOW_INSTANCE_ID') || crypto.randomUUID(), + instanceId: Deno.env.get('FLOW_SERVICE_INSTANCE_ID') || FLOW_SERVICE_INSTANCE_ID, } as const; /** From 8e24c10f7fea87c79918f758c437ec58bc5e1423 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Wed, 6 Aug 2025 22:39:27 -0700 Subject: [PATCH 10/10] Refactor logging configuration to use LoggingConfig consistently; update related tests for base URL setup --- .../src/utils/logger/component-logger.ts | 22 +++++++++------- flow-core/src/utils/logger/index.ts | 2 -- flow-core/src/utils/logger/logger-types.ts | 5 ---- flow-service/logs/test-pretty-direct.log | 8 +++--- flow-service/main.ts | 6 ++--- .../meshes-routes.integration.test.ts | 26 ++++++++++++++++++- 6 files changed, 44 insertions(+), 25 deletions(-) diff --git a/flow-core/src/utils/logger/component-logger.ts b/flow-core/src/utils/logger/component-logger.ts index 91eb3ff..c860b9e 100644 --- a/flow-core/src/utils/logger/component-logger.ts +++ b/flow-core/src/utils/logger/component-logger.ts @@ -1,18 +1,18 @@ import { EnhancedStructuredLogger, - LoggerConfig, + LoggingConfig, createEnhancedLogger, } from "./index.ts"; -let injectedLoggerConfig: LoggerConfig | undefined; +let injectedLoggingConfig: LoggingConfig | undefined; let globalLogger: EnhancedStructuredLogger | undefined; /** - * Inject a LoggerConfig to be used globally by flow-core. + * Inject a LoggingConfig to be used globally by flow-core. * Should be called once by flow-service at startup. */ -export function setGlobalLoggerConfig(config: LoggerConfig): void { - injectedLoggerConfig = config; +export function setGlobalLoggingConfig(config: LoggingConfig): void { + injectedLoggingConfig = config; globalLogger = undefined; // force re-creation with new config } @@ -20,7 +20,7 @@ export function setGlobalLoggerConfig(config: LoggerConfig): void { * Resets logger for testing or reconfiguration */ export function resetGlobalLogger(): void { - injectedLoggerConfig = undefined; + injectedLoggingConfig = undefined; globalLogger = undefined; } @@ -29,10 +29,12 @@ export function resetGlobalLogger(): void { */ function getGlobalLogger(): EnhancedStructuredLogger { if (!globalLogger) { - globalLogger = createEnhancedLogger(injectedLoggerConfig ?? { - enableConsole: true, - enableFile: false, - enableSentry: false, + globalLogger = createEnhancedLogger(injectedLoggingConfig ?? { + consoleChannel: { + logChannelEnabled: true, + logLevel: 'info', + logFormat: 'pretty', + } }); } return globalLogger; diff --git a/flow-core/src/utils/logger/index.ts b/flow-core/src/utils/logger/index.ts index 2bc2520..350d006 100644 --- a/flow-core/src/utils/logger/index.ts +++ b/flow-core/src/utils/logger/index.ts @@ -15,8 +15,6 @@ export type { SentryConfig, ErrorSeverity, ErrorHandlingOptions, - // Deprecated - use LoggingConfig instead - LoggerConfig, } from './logger-types.ts'; export type { LogLevel } from './logger-types.ts'; diff --git a/flow-core/src/utils/logger/logger-types.ts b/flow-core/src/utils/logger/logger-types.ts index 5c3e6ce..1c66042 100644 --- a/flow-core/src/utils/logger/logger-types.ts +++ b/flow-core/src/utils/logger/logger-types.ts @@ -252,11 +252,6 @@ export interface SentryConfig { debug?: boolean; } -/** - * @deprecated Use LoggingConfig instead. Will be removed in next major version. - */ -export type LoggerConfig = LoggingConfig; - /** * Error severity levels for error handling */ diff --git a/flow-service/logs/test-pretty-direct.log b/flow-service/logs/test-pretty-direct.log index b47f4da..b60cfc4 100644 --- a/flow-service/logs/test-pretty-direct.log +++ b/flow-service/logs/test-pretty-direct.log @@ -1,4 +1,4 @@ -[2025-08-03T15:27:54.395Z] INFO File logging test started (op=startup, [config-resolver]) -[2025-08-03T15:27:54.396Z] WARN Configuration override detected (op=config-resolve) -[2025-08-03T15:27:54.397Z] ERROR Database connection failed (op=startup) -[2025-08-03T15:27:54.398Z] DEBUG Processing mesh nodes (op=scan) +[2025-08-07T05:00:58.484Z] INFO File logging test started (op=startup, [config-resolver]) +[2025-08-07T05:00:58.486Z] WARN Configuration override detected (op=config-resolve) +[2025-08-07T05:00:58.486Z] ERROR Database connection failed (op=startup) +[2025-08-07T05:00:58.487Z] DEBUG Processing mesh nodes (op=scan) diff --git a/flow-service/main.ts b/flow-service/main.ts index 60d5b57..d5d76fa 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -14,12 +14,12 @@ import { handleCaughtError } from "../flow-core/src/utils/logger/error-handlers. import type { LogContext } from "../flow-core/src/utils/logger/logger-types.ts"; import { createServiceLogContext } from "./src/utils/service-log-context.ts"; import { MESH } from "../flow-core/src/mesh-constants.ts"; -import { setGlobalLoggerConfig } from "../flow-core/src/utils/logger/component-logger.ts"; +import { setGlobalLoggingConfig } from "../flow-core/src/utils/logger/component-logger.ts"; import { SERVICE_LOGGER_DEFAULT_CONFIG } from "./src/utils/service-logger.ts"; import { getComponentLogger } from "../flow-core/src/utils/logger/component-logger.ts"; // initialize a logger with default config until we can process the service config -setGlobalLoggerConfig(SERVICE_LOGGER_DEFAULT_CONFIG); +setGlobalLoggingConfig(SERVICE_LOGGER_DEFAULT_CONFIG); let logger = getComponentLogger(import.meta); logger.info("Starting Flow Service with initial logger configuration"); @@ -44,7 +44,7 @@ try { "./src/config/logging-config-extractor.ts" ); const loggingConfig = await extractLoggingConfigFromService(); - setGlobalLoggerConfig(loggingConfig); + setGlobalLoggingConfig(loggingConfig); // Get a new logger instance with the updated configuration logger = getComponentLogger(import.meta); diff --git a/flow-service/tests/integration/meshes-routes.integration.test.ts b/flow-service/tests/integration/meshes-routes.integration.test.ts index 06e6eea..57cf7be 100644 --- a/flow-service/tests/integration/meshes-routes.integration.test.ts +++ b/flow-service/tests/integration/meshes-routes.integration.test.ts @@ -1,7 +1,31 @@ import { assertEquals } from '../../../flow-core/src/deps.ts'; +import { singletonServiceConfigAccessor } from '../../src/config/resolution/service-config-accessor.ts'; +import { createServiceConfig } from '../../src/config/index.ts'; +import { buildServiceBaseUri } from '../../src/utils/service-uri-builder.ts'; + +let baseUrl: string; + +Deno.test({ + name: 'Setup service base URL', + fn: async () => { + await createServiceConfig(); + const host = await singletonServiceConfigAccessor.getHost(); + const port = await singletonServiceConfigAccessor.getPort(); + const scheme = await singletonServiceConfigAccessor.getScheme() ?? 'http'; + if (!host || !port) { + throw new Error('Service config could not be resolved'); + } + baseUrl = buildServiceBaseUri({ scheme, host, port }); + }, + sanitizeResources: false, + sanitizeOps: false, +}); Deno.test('Health endpoint is reachable', async () => { - const response = await fetch('http://localhost:8080/api/health'); + if (!baseUrl) { + throw new Error('Base URL not initialized'); + } + const response = await fetch(`${baseUrl}api/health`); assertEquals(response.status, 200); await response.text(); // consume the body to avoid leaks });