diff --git a/deno.lock b/deno.lock index 8703db5..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": [ @@ -138,8 +148,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/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/deps.ts b/flow-core/src/deps.ts index 098869f..9686ad3 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@0.224.0/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/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..c860b9e --- /dev/null +++ b/flow-core/src/utils/logger/component-logger.ts @@ -0,0 +1,55 @@ +import { + EnhancedStructuredLogger, + LoggingConfig, + createEnhancedLogger, +} from "./index.ts"; + +let injectedLoggingConfig: LoggingConfig | undefined; +let globalLogger: EnhancedStructuredLogger | undefined; + +/** + * Inject a LoggingConfig to be used globally by flow-core. + * Should be called once by flow-service at startup. + */ +export function setGlobalLoggingConfig(config: LoggingConfig): void { + injectedLoggingConfig = config; + globalLogger = undefined; // force re-creation with new config +} + +/** + * Resets logger for testing or reconfiguration + */ +export function resetGlobalLogger(): void { + injectedLoggingConfig = undefined; + globalLogger = undefined; +} + +/** + * Get the global enhanced logger, initializing if necessary. + */ +function getGlobalLogger(): EnhancedStructuredLogger { + if (!globalLogger) { + globalLogger = createEnhancedLogger(injectedLoggingConfig ?? { + consoleChannel: { + logChannelEnabled: true, + logLevel: 'info', + logFormat: 'pretty', + } + }); + } + 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/error-handlers.ts b/flow-core/src/utils/logger/error-handlers.ts index 2a3362a..bd997c7 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'; @@ -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/logger/formatters.ts b/flow-core/src/utils/logger/formatters.ts index 3abd4f5..9aff0ab 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 './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; } @@ -93,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, @@ -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 ce91a22..350d006 100644 --- a/flow-core/src/utils/logger/index.ts +++ b/flow-core/src/utils/logger/index.ts @@ -7,15 +7,20 @@ export type { LogContext, StructuredLogger, - LoggerConfig, + LoggingConfig, + LogChannelConfig, + ConsoleChannelConfig, + FileChannelConfig, + SentryChannelConfig, + SentryConfig, ErrorSeverity, ErrorHandlingOptions, -} from './types.ts'; +} from './logger-types.ts'; -export { LogLevel } from './types.ts'; +export type { LogLevel } from './logger-types.ts'; +export { validLogLevels } 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, @@ -60,7 +65,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'; @@ -76,9 +81,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 +124,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/types.ts b/flow-core/src/utils/logger/logger-types.ts similarity index 66% rename from flow-core/src/utils/logger/types.ts rename to flow-core/src/utils/logger/logger-types.ts index e28d319..1c66042 100644 --- a/flow-core/src/utils/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,48 +151,76 @@ export interface StructuredLogger { } /** - * Log level enumeration for controlling log output + * Log level type using lowercase string literals for controlling log output + */ +export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'critical'; + +/** + * Valid log levels array for validation and iteration */ -export enum LogLevel { - DEBUG = 0, - INFO = 1, - WARN = 2, - ERROR = 3, - CRITICAL = 4, - OFF = 5, +export const validLogLevels = ['debug', 'info', 'warn', 'error', 'critical'] as const; + +/** + * Base log channel configuration interface aligned with ontology fsvc:LogChannelConfig + */ +export interface LogChannelConfig { + /** Whether this logging channel is enabled (fsvc:logChannelEnabled) */ + logChannelEnabled: boolean; + + /** Minimum log level for this channel (fsvc:logLevel) */ + logLevel: LogLevel; + + /** Log format for this channel (fsvc:logFormat) */ + logFormat?: 'json' | 'pretty'; } /** - * Logger configuration interface + * Console channel configuration interface */ -export interface LoggerConfig { - /** Minimum log level to output */ - level?: LogLevel; +export interface ConsoleChannelConfig extends LogChannelConfig { + // Console channels only need the base properties +} - /** Whether to enable console output */ - enableConsole?: boolean; +/** + * File channel configuration interface + */ +export interface FileChannelConfig extends LogChannelConfig { + /** Log file path (fsvc:logFilePath) */ + logFilePath?: string; - /** Whether to enable file output */ - enableFile?: boolean; + /** Log retention days (fsvc:logRetentionDays) */ + logRetentionDays?: number; - /** Whether to enable Sentry integration */ - enableSentry?: boolean; + /** Maximum number of log files (fsvc:logMaxFiles) */ + logMaxFiles?: number; - /** File logging configuration */ - fileConfig?: { - logDir?: string; - maxFileSize?: number; - maxFiles?: number; - rotateDaily?: boolean; - }; + /** Maximum log file size in bytes (fsvc:logMaxFileSize) */ + logMaxFileSize?: number; - /** Sentry configuration */ - sentryConfig?: { - dsn?: string; - environment?: string; - release?: string; - sampleRate?: 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?: { @@ -198,6 +231,27 @@ 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; +} + /** * Error severity levels for error handling */ diff --git a/flow-core/src/utils/logger/sentry-logger.ts b/flow-core/src/utils/logger/sentry-logger.ts index 228bd7a..b7f7e85 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 './types.ts'; -import type { LogLevel } from './formatters.ts'; +import type { LogContext, LogLevel } from './logger-types.ts'; import { extractErrorContext } from './formatters.ts'; /** @@ -51,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 @@ -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-core/src/utils/logger/structured-logger.ts b/flow-core/src/utils/logger/structured-logger.ts index df70804..32995bf 100644 --- a/flow-core/src/utils/logger/structured-logger.ts +++ b/flow-core/src/utils/logger/structured-logger.ts @@ -3,15 +3,14 @@ * Integrates console output, file logging, and Sentry error reporting with structured context. */ -import type { LogContext, StructuredLogger, LoggerConfig } from './types.ts'; -import type { LogLevel } from './formatters.ts'; +import type { LogContext, StructuredLogger, LoggingConfig, LogLevel } from './logger-types.ts'; import { formatConsoleMessage, formatStructuredMessage, shouldLog, mergeLogContext, } from './formatters.ts'; -import { getGlobalFileLogger, type FileLogger } from './file-logger.ts'; +import { getGlobalFileLogger } from './file-logger.ts'; import { isSentryEnabled, reportErrorToSentry, @@ -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,43 +153,60 @@ export class StructuredLoggerImpl implements StructuredLogger { context?: LogContext, error?: Error, ): Promise { - // Write to console if enabled - if (this.config.enableConsole !== false) { - await this.writeToConsole(level, message, context); + // Write to console if enabled and level meets threshold + if (this.config.consoleChannel?.logChannelEnabled && this.shouldLogToChannel(level, this.config.consoleChannel.logLevel)) { + 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()) { - await this.writeToSentry(level, message, context, error); + // 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()) { + 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 */ - private async writeToConsole( + private writeToConsole( level: LogLevel, message: string, context?: LogContext, - ): Promise { + ): void { try { 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; } @@ -217,17 +244,17 @@ 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 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-core/src/utils/quadstore/quadstore-utils.ts b/flow-core/src/utils/quadstore/quadstore-utils.ts index 9415559..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,7 +44,8 @@ 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); + // 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); console.log(`Number of quads in graph ${graph.value}: ${count2}`); @@ -62,8 +66,11 @@ 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}`); + + logger.info("COPIED QUADS:"); + // deno-lint-ignore no-explicit-any + for await (const q of stream as any) { + 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 674d3fd..4331241 100644 --- a/flow-core/src/utils/rdfjs-utils.ts +++ b/flow-core/src/utils/rdfjs-utils.ts @@ -26,7 +26,8 @@ export async function jsonldToQuads( ); } -export function expandRelativeIds(inputJsonLd: NodeObject, baseIRI: string): any { + +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}`); @@ -35,8 +36,73 @@ export function expandRelativeIds(inputJsonLd: NodeObject, baseIRI: string): any throw new Error(`Invalid baseIRI: must end with "/", got: ${baseIRI}`); } - const expanded = structuredClone(inputJsonLd); + 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}`); + } + if (!baseIRI.endsWith("/")) { + throw new Error(`Invalid baseIRI: must end with "/", got: ${baseIRI}`); + } + + return inputQuads.map(quad => { + 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(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(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, + object, + graph + ); + }); +} + +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}`); + } + if (!baseIRI.endsWith("/")) { + throw new Error(`Invalid baseIRI: must end with "/", got: ${baseIRI}`); + } + + 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 new file mode 100644 index 0000000..8f76496 --- /dev/null +++ b/flow-core/src/utils/sparql-utils.ts @@ -0,0 +1,57 @@ +// 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, + sparql: string, +): Promise { + 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] }); + // deno-lint-ignore no-explicit-any + for await (const binding of bindingsStream as any) { + const value = binding.get('value'); + if (value) { + values.push(value.value); + } + } + } catch (error) { + handleCaughtError(error, `Failed to execute SPARQL query ${sparql}`); + } + 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'); + } + logger.debug(`Executing SPARQL query: ${sparql}`); + 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) { + values.push(value.value); + } + } + } catch (error) { + handleCaughtError(error, `Failed to execute SPARQL query ${sparql}`); + } + return values; +} 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-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/flow-service-config.jsonld b/flow-service/flow-service-config.jsonld index 383e635..57d2012 100644 --- a/flow-service/flow-service-config.jsonld +++ b/flow-service/flow-service-config.jsonld @@ -8,9 +8,9 @@ "@type": "fsvc:ServiceConfig", "fsvc:scheme": "http", "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/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 ce28b43..d5d76fa 100644 --- a/flow-service/main.ts +++ b/flow-service/main.ts @@ -1,76 +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/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 { 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 +setGlobalLoggingConfig(SERVICE_LOGGER_DEFAULT_CONFIG); +let logger = getComponentLogger(import.meta); +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); - console.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 loggingConfig = await extractLoggingConfigFromService(); + setGlobalLoggingConfig(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: "reinitialize-logger", + }); + 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", }), ); @@ -79,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 { @@ -121,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/config-types.ts b/flow-service/src/config/config-types.ts index 2bc4855..30e2604 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 { @@ -20,11 +21,10 @@ 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; - readonly "fsvc:sentryLoggingEnabled"?: boolean; readonly "fsvc:logRetentionDays"?: number; readonly "fsvc:logMaxFiles"?: number; readonly "fsvc:logMaxFileSize"?: number; @@ -83,6 +83,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 @@ -187,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/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/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/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..a16ad5c 100644 --- a/flow-service/src/config/loaders/quadstore-loader.ts +++ b/flow-service/src/config/loaders/quadstore-loader.ts @@ -1,11 +1,15 @@ 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 { 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,7 +17,8 @@ export async function loadPlatformServiceDefaults(): Promise { try { const uri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.platformServiceDefaults); - const expandedPlatformServiceDefaults = expandRelativeIds(PLATFORM_SERVICE_DEFAULTS, uri); + 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) { @@ -34,19 +39,24 @@ 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 }); } /** - * 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); - //console.log(expandedInputServiceConfig) - await createNewGraphFromJsonLd(expandedInputServiceConfig, { graphName: uri }); + //logger.log(inputConfig); + const inputUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.inputServiceConfig); + const mergedUri = getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig); + + const expandedInputServiceConfig = expandRelativeJsonLd(inputConfig, inputUri); + //logger.log(expandedInputServiceConfig) + await createNewGraphFromJsonLd(expandedInputServiceConfig, { graphName: inputUri }); + + const expandedMergedServiceConfig = expandRelativeJsonLd(inputConfig, mergedUri); + await createNewGraphFromJsonLd(expandedMergedServiceConfig, { graphName: mergedUri }); } /** @@ -54,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 }); } @@ -68,27 +78,31 @@ 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)); - 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 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 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) }); + 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.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++; + } 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) { + logger.error(`Multiple quads found for ${q.subject.value} ${q.predicate.value} in graph ${mergedUri}. This may indicate a configuration error.`); } } - - + logger.debug(`platform quads copied: ${platformQuadsCopied}`); } 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/config/resolution/service-config-accessor.ts b/flow-service/src/config/resolution/service-config-accessor.ts index 790edce..0b4b327 100644 --- a/flow-service/src/config/resolution/service-config-accessor.ts +++ b/flow-service/src/config/resolution/service-config-accessor.ts @@ -1,92 +1,136 @@ + 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; + private initialized: boolean = false; constructor(bundle: QuadstoreBundle = defaultQuadstoreBundle) { this.bundle = bundle; - this.df = bundle.df; } + isInitialized(): boolean { + return this.initialized; + } - 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; + 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 ${property} ?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; + `; + return await querySingleValue(this.bundle, sparql); } - async getPort(): Promise { + async getMultipleConfigValues(property: string): Promise { const sparql = ` PREFIX fsvc: SELECT ?value WHERE { GRAPH <${getCurrentServiceUri(CONFIG_GRAPH_NAMES.mergedServiceConfig)}> { - ?s fsvc:port ?value . + ?s ${property} ?value . } - } LIMIT 1 + } `; - const result = await this.querySingleValue(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 await this.getConfigValue('fsvc:host'); + } + + async getScheme(): Promise { + return await this.getConfigValue('fsvc:scheme'); + } + + async getMeshPaths(): Promise { + return await 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:host ?value . + ?s fsvc:hasConsoleChannel ?channel . + ?channel fsvc:logChannelEnabled ?enabled . + OPTIONAL { ?channel fsvc:logLevel ?level . } } - } LIMIT 1 + } `; - return this.querySingleValue(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 . } } } `; + 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'); } - 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); + 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) { + return value.value; + } } + } catch (error) { + throw new Error(`Failed to execute SPARQL query: ${error instanceof Error ? error.message : String(error)}`); } - return values; + return undefined; } - - // 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/config/resolution/service-config-resolver.ts b/flow-service/src/config/resolution/service-config-resolver.ts index 4f80fe6..e6bdecf 100644 --- a/flow-service/src/config/resolution/service-config-resolver.ts +++ b/flow-service/src/config/resolution/service-config-resolver.ts @@ -8,10 +8,13 @@ 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'; +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. @@ -20,7 +23,8 @@ import { serviceUriConfigManager, type ServiceUriConfig } from '../../utils/serv * @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( cliOptions?: ServiceOptions, @@ -31,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) { @@ -46,6 +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 + // 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'], @@ -61,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({ @@ -80,16 +83,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/service-constants.ts b/flow-service/src/service-constants.ts index 888b57a..d18b74a 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_SERVICE_INSTANCE_ID') || crypto.randomUUID(); 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'; /** diff --git a/flow-service/src/utils/service-logger.ts b/flow-service/src/utils/service-logger.ts index bbfec4e..1973f7e 100644 --- a/flow-service/src/utils/service-logger.ts +++ b/flow-service/src/utils/service-logger.ts @@ -6,11 +6,12 @@ import { createEnhancedLogger, - type LoggerConfig, + type LoggingConfig, type LogContext, type StructuredLogger, type EnhancedStructuredLogger, } from '../../../flow-core/src/utils/logger/index.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'; @@ -21,31 +22,35 @@ 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_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; /** * Service-specific logger configuration */ -const SERVICE_LOGGER_CONFIG: LoggerConfig = { - enableConsole: true, - enableFile: Deno.env.get('FLOW_LOG_FILE_ENABLED') === 'true', - enableSentry: Deno.env.get('FLOW_SENTRY_ENABLED') === 'true', - - fileConfig: { - logDir: Deno.env.get('FLOW_LOG_DIR') || './logs', - maxFileSize: parseInt(Deno.env.get('FLOW_LOG_MAX_FILE_SIZE') || '10485760'), // 10MB - maxFiles: parseInt(Deno.env.get('FLOW_LOG_MAX_FILES') || '5'), - rotateDaily: Deno.env.get('FLOW_LOG_ROTATE_DAILY') === 'true', +export const SERVICE_LOGGER_DEFAULT_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, @@ -54,7 +59,7 @@ const SERVICE_LOGGER_CONFIG: LoggerConfig = { /** * 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 @@ -134,7 +139,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..02bd825 100644 --- a/flow-service/src/utils/startup-logger.ts +++ b/flow-service/src/utils/startup-logger.ts @@ -7,6 +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. @@ -14,35 +17,46 @@ import { resolve } from '../../../flow-core/src/deps.ts'; * Outputs mesh paths, logging levels, and feature enablement statuses to the console for the provided configuration. */ export async function logStartupConfiguration(): Promise { - 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(); + if (!config.isInitialized()) { + logger.error('Service configuration is not initialized.'); + Deno.exit(1); + } - 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`); } - // 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(); + logger.info(` Console Logging: ${consoleConfig.enabled ? (consoleConfig.level ?? 'enabled') : 'disabled'}`); + } catch { + logger.info(` Console Logging: error fetching config`); + } + + try { + const fileConfig = await config.getFileLoggingConfig(); + logger.info(` File Logging: ${fileConfig.enabled ? (fileConfig.level ?? 'enabled') : 'disabled'}`); + } catch { + logger.info(` File Logging: error fetching config`); + } + + try { + const sentryConfig = await config.getSentryLoggingConfig(); + logger.info(` Sentry Logging: ${sentryConfig.enabled ? (sentryConfig.level ?? 'enabled') : 'disabled'}`); + } catch { + logger.info(` Sentry Logging: error fetching config`); + } const enabledServices: string[] = []; // TODO: Query service enablement flags from config @@ -50,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'}`, ); } @@ -62,8 +76,9 @@ 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}`); + logger.info(`📍 Root: ${baseUrl}/`); + logger.info(`📍 API documentation: ${baseUrl}${MESH.API_PORTAL_ROUTE}`); } 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, 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 }); 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" } }