@iworkforces/tracelattice 2.0.5 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/ConfigLoader.d.ts +3 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +6 -0
- package/dist/config/ConfigLoader.js.map +1 -1
- package/dist/metrics/metrics.impl.d.ts +2 -7
- package/dist/metrics/metrics.impl.d.ts.map +1 -1
- package/dist/metrics/metrics.impl.js +32 -34
- package/dist/metrics/metrics.impl.js.map +1 -1
- package/dist/pool/ConnectionPool.d.ts +7 -3
- package/dist/pool/ConnectionPool.d.ts.map +1 -1
- package/dist/pool/ConnectionPool.js +65 -40
- package/dist/pool/ConnectionPool.js.map +1 -1
- package/package.json +3 -3
|
@@ -93,6 +93,9 @@ export interface ConfigFileOptions {
|
|
|
93
93
|
* Persistence configuration for storing history and state.
|
|
94
94
|
*/
|
|
95
95
|
readonly persistence?: PersistenceConfig;
|
|
96
|
+
readonly persistenceBufferSize?: number;
|
|
97
|
+
readonly persistenceFlushInterval?: number;
|
|
98
|
+
readonly persistenceMaxRetries?: number;
|
|
96
99
|
/**
|
|
97
100
|
* Feature flag overrides. Each field can be set independently.
|
|
98
101
|
* Can be overridden by `TRACELATTICE_FEATURES_*` environment variables.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAE5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AAE5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAiC9D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAEjC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAEhC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAExD;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAE7B;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAE9B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAE7B;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE;QACzB;;;WAGG;QACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QACtB;;WAEG;QACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IAEF;;OAEG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAEzC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAExC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAE3C,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAExC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAE1C;;;OAGG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAEtC;;;OAGG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAExC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,qBAAa,YAAY;IACxB,+DAA+D;IAC/D,OAAO,CAAC,YAAY,CAAW;IAE/B;;;;;;;;;;;;;OAaG;gBACS,UAAU,CAAC,EAAE,MAAM;IAa/B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,IAAI,IAAI,iBAAiB,GAAG,IAAI;IAiBhC;;;;;;;;;;;;;;;OAeG;IACI,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB;IA6F9E;;;;;;;OAOG;IACH,OAAO,CAAC,yBAAyB;IAuCjC,OAAO,CAAC,uBAAuB;IAW/B;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;IAOpB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,WAAW;IASnB;;;;;;;;;;;;;;;;;;OAkBG;IACH,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,GAAG,mBAAmB;CAkBrE"}
|
|
@@ -22,6 +22,9 @@ const ConfigFileOptionsSchema = __rspack_external_valibot.looseObject({
|
|
|
22
22
|
maxSize: __rspack_external_valibot.optional(__rspack_external_valibot.number())
|
|
23
23
|
})),
|
|
24
24
|
persistence: __rspack_external_valibot.optional(__rspack_external_valibot.looseObject({})),
|
|
25
|
+
persistenceBufferSize: __rspack_external_valibot.optional(__rspack_external_valibot.number()),
|
|
26
|
+
persistenceFlushInterval: __rspack_external_valibot.optional(__rspack_external_valibot.number()),
|
|
27
|
+
persistenceMaxRetries: __rspack_external_valibot.optional(__rspack_external_valibot.number()),
|
|
25
28
|
features: __rspack_external_valibot.optional(__rspack_external_valibot.looseObject({})),
|
|
26
29
|
toolInterleaveTtlMs: __rspack_external_valibot.optional(__rspack_external_valibot.number()),
|
|
27
30
|
toolInterleaveSweepMs: __rspack_external_valibot.optional(__rspack_external_valibot.number()),
|
|
@@ -174,6 +177,9 @@ class ConfigLoader {
|
|
|
174
177
|
toolDirs: config.toolDirs,
|
|
175
178
|
discoveryCache: config.discoveryCache,
|
|
176
179
|
persistence: config.persistence,
|
|
180
|
+
persistenceBufferSize: config.persistenceBufferSize,
|
|
181
|
+
persistenceFlushInterval: config.persistenceFlushInterval,
|
|
182
|
+
persistenceMaxRetries: config.persistenceMaxRetries,
|
|
177
183
|
features: config.features,
|
|
178
184
|
toolInterleaveTtlMs: config.toolInterleaveTtlMs,
|
|
179
185
|
toolInterleaveSweepMs: config.toolInterleaveSweepMs,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config/ConfigLoader.js","sources":["../../src/config/ConfigLoader.ts"],"sourcesContent":["/**\n * Configuration file loading with environment variable override support.\n *\n * This module provides the `ConfigLoader` class which handles loading configuration\n * from YAML and JSON files in standard locations, with automatic environment variable\n * overrides for all settings.\n *\n * @module config\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport * as v from 'valibot';\nimport type { PersistenceConfig } from '../contracts/PersistenceBackend.js';\nimport { ConfigurationError, getErrorMessage } from '../errors.js';\nimport type { FeatureFlags } from '../contracts/features.js';\nimport type { ServerConfigOptions } from '../ServerConfig.js';\n\n/**\n * Lenient runtime schema for config files. Only validates top-level shape;\n * unknown extra fields are preserved by Valibot's default object behavior\n * being strict is avoided by using `v.looseObject`. All fields are optional.\n */\nconst ConfigFileOptionsSchema = v.looseObject({\n\tmaxHistorySize: v.optional(v.number()),\n\tmaxBranches: v.optional(v.number()),\n\tmaxBranchSize: v.optional(v.number()),\n\tlogLevel: v.optional(v.picklist(['debug', 'info', 'warn', 'error'])),\n\tprettyLog: v.optional(v.boolean()),\n\tskillDirs: v.optional(v.array(v.string())),\n\ttoolDirs: v.optional(v.array(v.string())),\n\tdiscoveryCache: v.optional(\n\t\tv.looseObject({\n\t\t\tttl: v.optional(v.number()),\n\t\t\tmaxSize: v.optional(v.number()),\n\t\t})\n\t),\n\tpersistence: v.optional(v.looseObject({})),\n\tfeatures: v.optional(v.looseObject({})),\n\ttoolInterleaveTtlMs: v.optional(v.number()),\n\ttoolInterleaveSweepMs: v.optional(v.number()),\n\tmaxSessionsPerOwner: v.optional(v.number()),\n});\n\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] };\n\n/**\n * Configuration options loaded from config files.\n *\n * These options represent the structure of configuration files (JSON or YAML)\n * that can be loaded from standard locations. All values can be overridden\n * by environment variables.\n *\n * @example\n * ```yaml\n * # .claude/config.yaml\n * maxHistorySize: 500\n * maxBranches: 25\n * logLevel: debug\n * prettyLog: true\n * skillDirs:\n * - ./custom-skills\n * toolDirs:\n * - ./custom-tools\n * discoveryCache:\n * ttl: 600000\n * maxSize: 200\n * persistence:\n * enabled: true\n * backend: sqlite\n * options:\n * dbPath: ./data/history.db\n * ```\n */\nexport interface ConfigFileOptions {\n\t/**\n\t * Maximum number of thoughts to keep in history.\n\t * Can be overridden by `MAX_HISTORY_SIZE` environment variable.\n\t */\n\treadonly maxHistorySize?: number;\n\n\t/**\n\t * Maximum number of branches to maintain.\n\t * Can be overridden by `MAX_BRANCHES` environment variable.\n\t */\n\treadonly maxBranches?: number;\n\n\t/**\n\t * Maximum size of each branch.\n\t * Can be overridden by `MAX_BRANCH_SIZE` environment variable.\n\t */\n\treadonly maxBranchSize?: number;\n\n\t/**\n\t * Logging level for the application.\n\t * Can be overridden by `LOG_LEVEL` environment variable.\n\t */\n\treadonly logLevel?: 'debug' | 'info' | 'warn' | 'error';\n\n\t/**\n\t * Whether to enable pretty (formatted) logging output.\n\t * Can be overridden by `PRETTY_LOG` environment variable (set to \"false\" to disable).\n\t */\n\treadonly prettyLog?: boolean;\n\n\t/**\n\t * Directory paths to search for skills.\n\t * Can be overridden by `SKILL_DIRS` environment variable (colon-separated).\n\t */\n\treadonly skillDirs?: string[];\n\n\t/**\n\t * Directory paths to search for tools.\n\t * Can be overridden by `TOOL_DIRS` environment variable (colon-separated).\n\t */\n\treadonly toolDirs?: string[];\n\n\t/**\n\t * Discovery cache configuration.\n\t * Can be overridden by `DISCOVERY_CACHE_TTL` and `DISCOVERY_CACHE_MAX_SIZE` environment variables.\n\t */\n\treadonly discoveryCache?: {\n\t\t/**\n\t\t * Time-to-live for cache entries in milliseconds.\n\t\t * Environment variable `DISCOVERY_CACHE_TTL` accepts seconds.\n\t\t */\n\t\treadonly ttl?: number;\n\t\t/**\n\t\t * Maximum number of entries in the cache.\n\t\t */\n\t\treadonly maxSize?: number;\n\t};\n\n\t/**\n\t * Persistence configuration for storing history and state.\n\t */\n\treadonly persistence?: PersistenceConfig;\n\n\t/**\n\t * Feature flag overrides. Each field can be set independently.\n\t * Can be overridden by `TRACELATTICE_FEATURES_*` environment variables.\n\t */\n\treadonly features?: Partial<FeatureFlags>;\n\n\t/**\n\t * TTL in milliseconds for suspended tool-interleave entries.\n\t * Can be overridden by `TRACELATTICE_TOOL_INTERLEAVE_TTL_MS` environment variable.\n\t */\n\treadonly toolInterleaveTtlMs?: number;\n\n\t/**\n\t * Sweep interval in milliseconds for SuspensionStore expiration cleanup.\n\t * Can be overridden by `TRACELATTICE_TOOL_INTERLEAVE_SWEEP_MS` environment variable.\n\t */\n\treadonly toolInterleaveSweepMs?: number;\n\n\t/**\n\t * Maximum sessions per owner. Per-owner LRU bucket prevents one user from\n\t * consuming all session slots.\n\t * Can be overridden by `SESSION_MAX_PER_OWNER` environment variable.\n\t */\n\treadonly maxSessionsPerOwner?: number;\n}\n\n/**\n * Loads configuration from files with environment variable overrides.\n *\n * This class searches for configuration files in standard locations and applies\n * environment variable overrides. Files are searched in priority order, with the\n * first match being used. Environment variables always take precedence over file values.\n *\n * @remarks\n * **Config File Search Order (priority):**\n * 1. Custom path (if provided to constructor)\n * 2. `.claude/config.json` (project-local)\n * 3. `.claude/config.yaml` (project-local)\n * 4. `.claude/config.yml` (project-local)\n * 5. `~/.claude/config.json` (user-global)\n * 6. `~/.claude/config.yaml` (user-global)\n * 7. `~/.claude/config.yml` (user-global)\n *\n * **Environment Variable Overrides:**\n * | Variable | Type | Description |\n * |----------|------|-------------|\n * | `MAX_HISTORY_SIZE` | number | Max thoughts in history |\n * | `MAX_BRANCHES` | number | Max number of branches |\n * | `MAX_BRANCH_SIZE` | number | Max size of each branch |\n * | `LOG_LEVEL` | string | Logging level (debug/info/warn/error) |\n * | `PRETTY_LOG` | string | \"false\" to disable pretty logging |\n * | `SKILL_DIRS` | string | Colon-separated directory paths |\n * | `TOOL_DIRS` | string | Colon-separated directory paths |\n * | `DISCOVERY_CACHE_TTL` | number | TTL in seconds (converted to ms) |\n * | `DISCOVERY_CACHE_MAX_SIZE` | number | Max cache entries |\n *\n * @example\n * ```typescript\n * // Use default search paths\n * const loader1 = new ConfigLoader();\n * const config1 = loader1.load();\n *\n * // Use custom config path\n * const loader2 = new ConfigLoader('./my-config.yaml');\n * const config2 = loader2.load();\n *\n * // Convert to ServerConfig options\n * const serverOptions = loader2.toServerConfigOptions(config2);\n * ```\n */\nexport class ConfigLoader {\n\t/** Array of config file paths to search, in priority order. */\n\tprivate _configPaths: string[];\n\n\t/**\n\t * Creates a new ConfigLoader instance.\n\t *\n\t * @param customPath - Optional custom config file path. If provided, only this path will be checked.\n\t *\n\t * @example\n\t * ```typescript\n\t * // Use default search paths\n\t * const loader1 = new ConfigLoader();\n\t *\n\t * // Use a specific config file\n\t * const loader2 = new ConfigLoader('./custom-config.json');\n\t * ```\n\t */\n\tconstructor(customPath?: string) {\n\t\tthis._configPaths = customPath\n\t\t\t? [customPath]\n\t\t\t: [\n\t\t\t\t\t'.claude/config.json',\n\t\t\t\t\t'.claude/config.yaml',\n\t\t\t\t\t'.claude/config.yml',\n\t\t\t\t\tjoin(homedir(), '.claude/config.json'),\n\t\t\t\t\tjoin(homedir(), '.claude/config.yaml'),\n\t\t\t\t\tjoin(homedir(), '.claude/config.yml'),\n\t\t\t\t];\n\t}\n\n\t/**\n\t * Loads configuration from files and applies environment overrides.\n\t *\n\t * Searches for config files in the configured paths (in priority order),\n\t * parses the first match, and applies environment variable overrides.\n\t * Returns null if no config file is found and no environment overrides are set.\n\t *\n\t * @returns The loaded configuration with environment overrides applied, or null if no config found\n\t *\n\t * @example\n\t * ```typescript\n\t * const loader = new ConfigLoader();\n\t * const config = loader.load();\n\t *\n\t * if (config) {\n\t * console.log('Max history size:', config.maxHistorySize);\n\t * console.log('Log level:', config.logLevel);\n\t * }\n\t * ```\n\t */\n\tload(): ConfigFileOptions | null {\n\t\tlet config: ConfigFileOptions | null = null;\n\n\t\tfor (const configPath of this._configPaths) {\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\ttry {\n\t\t\t\t\tconfig = this.parseConfig(configPath);\n\t\t\t\t\tbreak;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(`Failed to load config from ${configPath}:`, getErrorMessage(error));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.applyEnvironmentOverrides(config || {});\n\t}\n\n\t/**\n\t * Applies environment variable overrides to the configuration.\n\t *\n\t * Environment variables take precedence over file-based configuration.\n\t * Supported environment variables:\n\t * - `MAX_HISTORY_SIZE`, `MAX_BRANCHES`, `MAX_BRANCH_SIZE` (numbers)\n\t * - `LOG_LEVEL` (debug/info/warn/error)\n\t * - `PRETTY_LOG` (\"false\" to disable)\n\t * - `SKILL_DIRS` (colon-separated paths)\n\t * - `TOOL_DIRS` (colon-separated paths)\n\t * - `DISCOVERY_CACHE_TTL` (in seconds, converted to ms)\n\t * - `DISCOVERY_CACHE_MAX_SIZE` (number)\n\t *\n\t * @param config - The configuration to apply overrides to\n\t * @returns A new configuration object with environment overrides applied\n\t */\n\tpublic applyEnvironmentOverrides(config: ConfigFileOptions): ConfigFileOptions {\n\t\tconst result: Mutable<ConfigFileOptions> = {\n\t\t\t...config,\n\t\t\t...(config.skillDirs === undefined ? {} : { skillDirs: [...config.skillDirs] }),\n\t\t\t...(config.toolDirs === undefined ? {} : { toolDirs: [...config.toolDirs] }),\n\t\t\t...(config.discoveryCache === undefined\n\t\t\t\t? {}\n\t\t\t\t: { discoveryCache: { ...config.discoveryCache } }),\n\t\t\t...(config.persistence === undefined\n\t\t\t\t? {}\n\t\t\t\t: {\n\t\t\t\t\t\tpersistence: {\n\t\t\t\t\t\t\t...config.persistence,\n\t\t\t\t\t\t\t...(config.persistence.options === undefined\n\t\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t\t: { options: { ...config.persistence.options } }),\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t...(config.features === undefined ? {} : { features: { ...config.features } }),\n\t\t};\n\n\t\tif (process.env.MAX_HISTORY_SIZE !== undefined) {\n\t\t\tresult.maxHistorySize = this.parseEnvironmentInteger(\n\t\t\t\t'MAX_HISTORY_SIZE',\n\t\t\t\tprocess.env.MAX_HISTORY_SIZE\n\t\t\t);\n\t\t}\n\t\tif (process.env.MAX_BRANCHES !== undefined) {\n\t\t\tresult.maxBranches = this.parseEnvironmentInteger('MAX_BRANCHES', process.env.MAX_BRANCHES);\n\t\t}\n\t\tif (process.env.MAX_BRANCH_SIZE !== undefined) {\n\t\t\tresult.maxBranchSize = this.parseEnvironmentInteger(\n\t\t\t\t'MAX_BRANCH_SIZE',\n\t\t\t\tprocess.env.MAX_BRANCH_SIZE\n\t\t\t);\n\t\t}\n\t\tif (\n\t\t\tprocess.env.LOG_LEVEL &&\n\t\t\t['debug', 'info', 'warn', 'error'].includes(process.env.LOG_LEVEL)\n\t\t) {\n\t\t\tresult.logLevel = process.env.LOG_LEVEL as 'debug' | 'info' | 'warn' | 'error';\n\t\t}\n\t\tif (process.env.PRETTY_LOG === 'false') {\n\t\t\tresult.prettyLog = false;\n\t\t}\n\t\tif (process.env.SKILL_DIRS !== undefined) {\n\t\t\tresult.skillDirs = process.env.SKILL_DIRS === '' ? [] : process.env.SKILL_DIRS.split(':');\n\t\t}\n\t\tif (process.env.TOOL_DIRS !== undefined) {\n\t\t\tresult.toolDirs = process.env.TOOL_DIRS === '' ? [] : process.env.TOOL_DIRS.split(':');\n\t\t}\n\t\tif (process.env.DISCOVERY_CACHE_TTL !== undefined) {\n\t\t\tconst seconds = this.parseEnvironmentInteger(\n\t\t\t\t'DISCOVERY_CACHE_TTL',\n\t\t\t\tprocess.env.DISCOVERY_CACHE_TTL\n\t\t\t);\n\t\t\tconst ttl = seconds * 1000;\n\t\t\tif (!Number.isSafeInteger(ttl)) {\n\t\t\t\tthrow new ConfigurationError('DISCOVERY_CACHE_TTL exceeds the safe integer range');\n\t\t\t}\n\t\t\tresult.discoveryCache = { ...result.discoveryCache, ttl };\n\t\t}\n\t\tif (process.env.DISCOVERY_CACHE_MAX_SIZE !== undefined) {\n\t\t\tconst maxSize = this.parseEnvironmentInteger(\n\t\t\t\t'DISCOVERY_CACHE_MAX_SIZE',\n\t\t\t\tprocess.env.DISCOVERY_CACHE_MAX_SIZE\n\t\t\t);\n\t\t\tresult.discoveryCache = { ...result.discoveryCache, maxSize };\n\t\t}\n\t\tif (process.env.TRACELATTICE_TOOL_INTERLEAVE_TTL_MS !== undefined) {\n\t\t\tresult.toolInterleaveTtlMs = this.parseEnvironmentInteger(\n\t\t\t\t'TRACELATTICE_TOOL_INTERLEAVE_TTL_MS',\n\t\t\t\tprocess.env.TRACELATTICE_TOOL_INTERLEAVE_TTL_MS\n\t\t\t);\n\t\t}\n\t\tif (process.env.TRACELATTICE_TOOL_INTERLEAVE_SWEEP_MS !== undefined) {\n\t\t\tresult.toolInterleaveSweepMs = this.parseEnvironmentInteger(\n\t\t\t\t'TRACELATTICE_TOOL_INTERLEAVE_SWEEP_MS',\n\t\t\t\tprocess.env.TRACELATTICE_TOOL_INTERLEAVE_SWEEP_MS\n\t\t\t);\n\t\t}\n\t\tif (process.env.SESSION_MAX_PER_OWNER !== undefined) {\n\t\t\tresult.maxSessionsPerOwner = this.parseEnvironmentInteger(\n\t\t\t\t'SESSION_MAX_PER_OWNER',\n\t\t\t\tprocess.env.SESSION_MAX_PER_OWNER\n\t\t\t);\n\t\t}\n\n\t\tthis.applyFeatureFlagOverrides(result);\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Applies TRACELATTICE_FEATURES_* environment variable overrides for feature flags.\n\t * Booleans accept 'true'/'false'/'1'/'0' (case-insensitive).\n\t * Invalid reasoningStrategy values are rejected.\n\t *\n\t * @param result - Configuration object to mutate with feature flag overrides\n\t * @private\n\t */\n\tprivate applyFeatureFlagOverrides(result: Mutable<ConfigFileOptions>): void {\n\t\tconst boolMap: Record<string, Exclude<keyof FeatureFlags, 'reasoningStrategy'>> = {\n\t\t\tTRACELATTICE_FEATURES_DAG_EDGES: 'dagEdges',\n\t\t\tTRACELATTICE_FEATURES_CALIBRATION: 'calibration',\n\t\t\tTRACELATTICE_FEATURES_COMPRESSION: 'compression',\n\t\t\tTRACELATTICE_FEATURES_TOOL_INTERLEAVE: 'toolInterleave',\n\t\t\tTRACELATTICE_FEATURES_NEW_THOUGHT_TYPES: 'newThoughtTypes',\n\t\t\tTRACELATTICE_FEATURES_OUTCOME_RECORDING: 'outcomeRecording',\n\t\t};\n\t\tfor (const [envVar, key] of Object.entries(boolMap)) {\n\t\t\tconst raw = process.env[envVar];\n\t\t\tif (raw === undefined) continue;\n\t\t\tconst parsed = this.parseBoolean(raw);\n\t\t\tif (parsed === undefined) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Invalid boolean value for ${envVar}: \"${raw}\" (expected true/false/1/0). Ignoring.`\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst features: { -readonly [K in keyof FeatureFlags]?: FeatureFlags[K] } =\n\t\t\t\tresult.features ?? {};\n\t\t\tfeatures[key] = parsed;\n\t\t\tresult.features = features;\n\t\t}\n\n\t\tconst strategyRaw = process.env.TRACELATTICE_FEATURES_REASONING_STRATEGY;\n\t\tif (strategyRaw !== undefined) {\n\t\t\tif (strategyRaw !== 'sequential' && strategyRaw !== 'tot') {\n\t\t\t\tthrow new ConfigurationError(\n\t\t\t\t\t`TRACELATTICE_FEATURES_REASONING_STRATEGY must be one of sequential, tot, got ${strategyRaw}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst features: { -readonly [K in keyof FeatureFlags]?: FeatureFlags[K] } =\n\t\t\t\tresult.features ?? {};\n\t\t\tfeatures.reasoningStrategy = strategyRaw;\n\t\t\tresult.features = features;\n\t\t}\n\t}\n\n\tprivate parseEnvironmentInteger(environmentName: string, raw: string): number {\n\t\tif (!/^(0|[1-9]\\d*)$/.test(raw)) {\n\t\t\tthrow new ConfigurationError(`${environmentName} must be an integer, got ${raw}`);\n\t\t}\n\t\tconst parsed = Number(raw);\n\t\tif (!Number.isSafeInteger(parsed)) {\n\t\t\tthrow new ConfigurationError(`${environmentName} must be a safe integer, got ${raw}`);\n\t\t}\n\t\treturn parsed;\n\t}\n\n\t/**\n\t * Parses a boolean from an environment variable string.\n\t * Accepts 'true'/'false'/'1'/'0' case-insensitively.\n\t *\n\t * @param raw - Raw environment variable string\n\t * @returns Parsed boolean, or undefined if the value is invalid\n\t * @private\n\t */\n\tprivate parseBoolean(raw: string): boolean | undefined {\n\t\tconst v = raw.trim().toLowerCase();\n\t\tif (v === 'true' || v === '1') return true;\n\t\tif (v === 'false' || v === '0') return false;\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Parses a configuration file (JSON or YAML).\n\t *\n\t * Detects the file type by extension and uses the appropriate parser.\n\t * Supports `.json`, `.yaml`, and `.yml` file extensions.\n\t *\n\t * @param filePath - Path to the configuration file to parse\n\t * @returns The parsed configuration object\n\t * @throws {Error} If the file cannot be parsed\n\t * @private\n\t */\n\tprivate parseConfig(filePath: string): ConfigFileOptions {\n\t\tconst content = readFileSync(filePath, 'utf-8');\n\t\tconst ext = filePath.split('.').pop()?.toLowerCase();\n\n\t\tconst raw: unknown = ext === 'yaml' || ext === 'yml' ? parseYaml(content) : JSON.parse(content);\n\n\t\treturn v.parse(ConfigFileOptionsSchema, raw) as ConfigFileOptions;\n\t}\n\n\t/**\n\t * Converts file-based configuration to ServerConfig options.\n\t *\n\t * This is a convenience method for extracting the ServerConfig-relevant\n\t * options from a file-based configuration.\n\t *\n\t * @param config - The configuration to convert\n\t * @returns An object with ServerConfig-compatible options\n\t *\n\t * @example\n\t * ```typescript\n\t * const loader = new ConfigLoader();\n\t * const config = loader.load();\n\t * if (config) {\n\t * const serverOpts = loader.toServerConfigOptions(config);\n\t * const serverConfig = new ServerConfig(serverOpts);\n\t * }\n\t * ```\n\t */\n\ttoServerConfigOptions(config: ConfigFileOptions): ServerConfigOptions {\n\t\treturn {\n\t\t\tmaxHistorySize: config.maxHistorySize,\n\t\t\tmaxBranches: config.maxBranches,\n\t\t\tmaxBranchSize: config.maxBranchSize,\n\t\t\tskillDirs: config.skillDirs,\n\t\t\ttoolDirs: config.toolDirs,\n\t\t\tdiscoveryCache: config.discoveryCache,\n\t\t\tpersistence: config.persistence,\n\t\t\tfeatures: config.features,\n\t\t\ttoolInterleaveTtlMs: config.toolInterleaveTtlMs,\n\t\t\ttoolInterleaveSweepMs: config.toolInterleaveSweepMs,\n\t\t\tmaxSessionsPerOwner: config.maxSessionsPerOwner,\n\t\t};\n\t}\n}\n"],"names":["ConfigFileOptionsSchema","v","ConfigLoader","customPath","join","homedir","config","configPath","existsSync","error","console","getErrorMessage","result","undefined","process","seconds","ttl","Number","ConfigurationError","maxSize","boolMap","envVar","key","Object","raw","parsed","features","strategyRaw","environmentName","filePath","content","readFileSync","ext","parseYaml","JSON"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config/ConfigLoader.js","sources":["../../src/config/ConfigLoader.ts"],"sourcesContent":["/**\n * Configuration file loading with environment variable override support.\n *\n * This module provides the `ConfigLoader` class which handles loading configuration\n * from YAML and JSON files in standard locations, with automatic environment variable\n * overrides for all settings.\n *\n * @module config\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport * as v from 'valibot';\nimport type { PersistenceConfig } from '../contracts/PersistenceBackend.js';\nimport { ConfigurationError, getErrorMessage } from '../errors.js';\nimport type { FeatureFlags } from '../contracts/features.js';\nimport type { ServerConfigOptions } from '../ServerConfig.js';\n\n/**\n * Lenient runtime schema for config files. Only validates top-level shape;\n * unknown extra fields are preserved by Valibot's default object behavior\n * being strict is avoided by using `v.looseObject`. All fields are optional.\n */\nconst ConfigFileOptionsSchema = v.looseObject({\n\tmaxHistorySize: v.optional(v.number()),\n\tmaxBranches: v.optional(v.number()),\n\tmaxBranchSize: v.optional(v.number()),\n\tlogLevel: v.optional(v.picklist(['debug', 'info', 'warn', 'error'])),\n\tprettyLog: v.optional(v.boolean()),\n\tskillDirs: v.optional(v.array(v.string())),\n\ttoolDirs: v.optional(v.array(v.string())),\n\tdiscoveryCache: v.optional(\n\t\tv.looseObject({\n\t\t\tttl: v.optional(v.number()),\n\t\t\tmaxSize: v.optional(v.number()),\n\t\t})\n\t),\n\tpersistence: v.optional(v.looseObject({})),\n\tpersistenceBufferSize: v.optional(v.number()),\n\tpersistenceFlushInterval: v.optional(v.number()),\n\tpersistenceMaxRetries: v.optional(v.number()),\n\tfeatures: v.optional(v.looseObject({})),\n\ttoolInterleaveTtlMs: v.optional(v.number()),\n\ttoolInterleaveSweepMs: v.optional(v.number()),\n\tmaxSessionsPerOwner: v.optional(v.number()),\n});\n\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] };\n\n/**\n * Configuration options loaded from config files.\n *\n * These options represent the structure of configuration files (JSON or YAML)\n * that can be loaded from standard locations. All values can be overridden\n * by environment variables.\n *\n * @example\n * ```yaml\n * # .claude/config.yaml\n * maxHistorySize: 500\n * maxBranches: 25\n * logLevel: debug\n * prettyLog: true\n * skillDirs:\n * - ./custom-skills\n * toolDirs:\n * - ./custom-tools\n * discoveryCache:\n * ttl: 600000\n * maxSize: 200\n * persistence:\n * enabled: true\n * backend: sqlite\n * options:\n * dbPath: ./data/history.db\n * ```\n */\nexport interface ConfigFileOptions {\n\t/**\n\t * Maximum number of thoughts to keep in history.\n\t * Can be overridden by `MAX_HISTORY_SIZE` environment variable.\n\t */\n\treadonly maxHistorySize?: number;\n\n\t/**\n\t * Maximum number of branches to maintain.\n\t * Can be overridden by `MAX_BRANCHES` environment variable.\n\t */\n\treadonly maxBranches?: number;\n\n\t/**\n\t * Maximum size of each branch.\n\t * Can be overridden by `MAX_BRANCH_SIZE` environment variable.\n\t */\n\treadonly maxBranchSize?: number;\n\n\t/**\n\t * Logging level for the application.\n\t * Can be overridden by `LOG_LEVEL` environment variable.\n\t */\n\treadonly logLevel?: 'debug' | 'info' | 'warn' | 'error';\n\n\t/**\n\t * Whether to enable pretty (formatted) logging output.\n\t * Can be overridden by `PRETTY_LOG` environment variable (set to \"false\" to disable).\n\t */\n\treadonly prettyLog?: boolean;\n\n\t/**\n\t * Directory paths to search for skills.\n\t * Can be overridden by `SKILL_DIRS` environment variable (colon-separated).\n\t */\n\treadonly skillDirs?: string[];\n\n\t/**\n\t * Directory paths to search for tools.\n\t * Can be overridden by `TOOL_DIRS` environment variable (colon-separated).\n\t */\n\treadonly toolDirs?: string[];\n\n\t/**\n\t * Discovery cache configuration.\n\t * Can be overridden by `DISCOVERY_CACHE_TTL` and `DISCOVERY_CACHE_MAX_SIZE` environment variables.\n\t */\n\treadonly discoveryCache?: {\n\t\t/**\n\t\t * Time-to-live for cache entries in milliseconds.\n\t\t * Environment variable `DISCOVERY_CACHE_TTL` accepts seconds.\n\t\t */\n\t\treadonly ttl?: number;\n\t\t/**\n\t\t * Maximum number of entries in the cache.\n\t\t */\n\t\treadonly maxSize?: number;\n\t};\n\n\t/**\n\t * Persistence configuration for storing history and state.\n\t */\n\treadonly persistence?: PersistenceConfig;\n\n\treadonly persistenceBufferSize?: number;\n\n\treadonly persistenceFlushInterval?: number;\n\n\treadonly persistenceMaxRetries?: number;\n\n\t/**\n\t * Feature flag overrides. Each field can be set independently.\n\t * Can be overridden by `TRACELATTICE_FEATURES_*` environment variables.\n\t */\n\treadonly features?: Partial<FeatureFlags>;\n\n\t/**\n\t * TTL in milliseconds for suspended tool-interleave entries.\n\t * Can be overridden by `TRACELATTICE_TOOL_INTERLEAVE_TTL_MS` environment variable.\n\t */\n\treadonly toolInterleaveTtlMs?: number;\n\n\t/**\n\t * Sweep interval in milliseconds for SuspensionStore expiration cleanup.\n\t * Can be overridden by `TRACELATTICE_TOOL_INTERLEAVE_SWEEP_MS` environment variable.\n\t */\n\treadonly toolInterleaveSweepMs?: number;\n\n\t/**\n\t * Maximum sessions per owner. Per-owner LRU bucket prevents one user from\n\t * consuming all session slots.\n\t * Can be overridden by `SESSION_MAX_PER_OWNER` environment variable.\n\t */\n\treadonly maxSessionsPerOwner?: number;\n}\n\n/**\n * Loads configuration from files with environment variable overrides.\n *\n * This class searches for configuration files in standard locations and applies\n * environment variable overrides. Files are searched in priority order, with the\n * first match being used. Environment variables always take precedence over file values.\n *\n * @remarks\n * **Config File Search Order (priority):**\n * 1. Custom path (if provided to constructor)\n * 2. `.claude/config.json` (project-local)\n * 3. `.claude/config.yaml` (project-local)\n * 4. `.claude/config.yml` (project-local)\n * 5. `~/.claude/config.json` (user-global)\n * 6. `~/.claude/config.yaml` (user-global)\n * 7. `~/.claude/config.yml` (user-global)\n *\n * **Environment Variable Overrides:**\n * | Variable | Type | Description |\n * |----------|------|-------------|\n * | `MAX_HISTORY_SIZE` | number | Max thoughts in history |\n * | `MAX_BRANCHES` | number | Max number of branches |\n * | `MAX_BRANCH_SIZE` | number | Max size of each branch |\n * | `LOG_LEVEL` | string | Logging level (debug/info/warn/error) |\n * | `PRETTY_LOG` | string | \"false\" to disable pretty logging |\n * | `SKILL_DIRS` | string | Colon-separated directory paths |\n * | `TOOL_DIRS` | string | Colon-separated directory paths |\n * | `DISCOVERY_CACHE_TTL` | number | TTL in seconds (converted to ms) |\n * | `DISCOVERY_CACHE_MAX_SIZE` | number | Max cache entries |\n *\n * @example\n * ```typescript\n * // Use default search paths\n * const loader1 = new ConfigLoader();\n * const config1 = loader1.load();\n *\n * // Use custom config path\n * const loader2 = new ConfigLoader('./my-config.yaml');\n * const config2 = loader2.load();\n *\n * // Convert to ServerConfig options\n * const serverOptions = loader2.toServerConfigOptions(config2);\n * ```\n */\nexport class ConfigLoader {\n\t/** Array of config file paths to search, in priority order. */\n\tprivate _configPaths: string[];\n\n\t/**\n\t * Creates a new ConfigLoader instance.\n\t *\n\t * @param customPath - Optional custom config file path. If provided, only this path will be checked.\n\t *\n\t * @example\n\t * ```typescript\n\t * // Use default search paths\n\t * const loader1 = new ConfigLoader();\n\t *\n\t * // Use a specific config file\n\t * const loader2 = new ConfigLoader('./custom-config.json');\n\t * ```\n\t */\n\tconstructor(customPath?: string) {\n\t\tthis._configPaths = customPath\n\t\t\t? [customPath]\n\t\t\t: [\n\t\t\t\t\t'.claude/config.json',\n\t\t\t\t\t'.claude/config.yaml',\n\t\t\t\t\t'.claude/config.yml',\n\t\t\t\t\tjoin(homedir(), '.claude/config.json'),\n\t\t\t\t\tjoin(homedir(), '.claude/config.yaml'),\n\t\t\t\t\tjoin(homedir(), '.claude/config.yml'),\n\t\t\t\t];\n\t}\n\n\t/**\n\t * Loads configuration from files and applies environment overrides.\n\t *\n\t * Searches for config files in the configured paths (in priority order),\n\t * parses the first match, and applies environment variable overrides.\n\t * Returns null if no config file is found and no environment overrides are set.\n\t *\n\t * @returns The loaded configuration with environment overrides applied, or null if no config found\n\t *\n\t * @example\n\t * ```typescript\n\t * const loader = new ConfigLoader();\n\t * const config = loader.load();\n\t *\n\t * if (config) {\n\t * console.log('Max history size:', config.maxHistorySize);\n\t * console.log('Log level:', config.logLevel);\n\t * }\n\t * ```\n\t */\n\tload(): ConfigFileOptions | null {\n\t\tlet config: ConfigFileOptions | null = null;\n\n\t\tfor (const configPath of this._configPaths) {\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\ttry {\n\t\t\t\t\tconfig = this.parseConfig(configPath);\n\t\t\t\t\tbreak;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(`Failed to load config from ${configPath}:`, getErrorMessage(error));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.applyEnvironmentOverrides(config || {});\n\t}\n\n\t/**\n\t * Applies environment variable overrides to the configuration.\n\t *\n\t * Environment variables take precedence over file-based configuration.\n\t * Supported environment variables:\n\t * - `MAX_HISTORY_SIZE`, `MAX_BRANCHES`, `MAX_BRANCH_SIZE` (numbers)\n\t * - `LOG_LEVEL` (debug/info/warn/error)\n\t * - `PRETTY_LOG` (\"false\" to disable)\n\t * - `SKILL_DIRS` (colon-separated paths)\n\t * - `TOOL_DIRS` (colon-separated paths)\n\t * - `DISCOVERY_CACHE_TTL` (in seconds, converted to ms)\n\t * - `DISCOVERY_CACHE_MAX_SIZE` (number)\n\t *\n\t * @param config - The configuration to apply overrides to\n\t * @returns A new configuration object with environment overrides applied\n\t */\n\tpublic applyEnvironmentOverrides(config: ConfigFileOptions): ConfigFileOptions {\n\t\tconst result: Mutable<ConfigFileOptions> = {\n\t\t\t...config,\n\t\t\t...(config.skillDirs === undefined ? {} : { skillDirs: [...config.skillDirs] }),\n\t\t\t...(config.toolDirs === undefined ? {} : { toolDirs: [...config.toolDirs] }),\n\t\t\t...(config.discoveryCache === undefined\n\t\t\t\t? {}\n\t\t\t\t: { discoveryCache: { ...config.discoveryCache } }),\n\t\t\t...(config.persistence === undefined\n\t\t\t\t? {}\n\t\t\t\t: {\n\t\t\t\t\t\tpersistence: {\n\t\t\t\t\t\t\t...config.persistence,\n\t\t\t\t\t\t\t...(config.persistence.options === undefined\n\t\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t\t: { options: { ...config.persistence.options } }),\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t...(config.features === undefined ? {} : { features: { ...config.features } }),\n\t\t};\n\n\t\tif (process.env.MAX_HISTORY_SIZE !== undefined) {\n\t\t\tresult.maxHistorySize = this.parseEnvironmentInteger(\n\t\t\t\t'MAX_HISTORY_SIZE',\n\t\t\t\tprocess.env.MAX_HISTORY_SIZE\n\t\t\t);\n\t\t}\n\t\tif (process.env.MAX_BRANCHES !== undefined) {\n\t\t\tresult.maxBranches = this.parseEnvironmentInteger('MAX_BRANCHES', process.env.MAX_BRANCHES);\n\t\t}\n\t\tif (process.env.MAX_BRANCH_SIZE !== undefined) {\n\t\t\tresult.maxBranchSize = this.parseEnvironmentInteger(\n\t\t\t\t'MAX_BRANCH_SIZE',\n\t\t\t\tprocess.env.MAX_BRANCH_SIZE\n\t\t\t);\n\t\t}\n\t\tif (\n\t\t\tprocess.env.LOG_LEVEL &&\n\t\t\t['debug', 'info', 'warn', 'error'].includes(process.env.LOG_LEVEL)\n\t\t) {\n\t\t\tresult.logLevel = process.env.LOG_LEVEL as 'debug' | 'info' | 'warn' | 'error';\n\t\t}\n\t\tif (process.env.PRETTY_LOG === 'false') {\n\t\t\tresult.prettyLog = false;\n\t\t}\n\t\tif (process.env.SKILL_DIRS !== undefined) {\n\t\t\tresult.skillDirs = process.env.SKILL_DIRS === '' ? [] : process.env.SKILL_DIRS.split(':');\n\t\t}\n\t\tif (process.env.TOOL_DIRS !== undefined) {\n\t\t\tresult.toolDirs = process.env.TOOL_DIRS === '' ? [] : process.env.TOOL_DIRS.split(':');\n\t\t}\n\t\tif (process.env.DISCOVERY_CACHE_TTL !== undefined) {\n\t\t\tconst seconds = this.parseEnvironmentInteger(\n\t\t\t\t'DISCOVERY_CACHE_TTL',\n\t\t\t\tprocess.env.DISCOVERY_CACHE_TTL\n\t\t\t);\n\t\t\tconst ttl = seconds * 1000;\n\t\t\tif (!Number.isSafeInteger(ttl)) {\n\t\t\t\tthrow new ConfigurationError('DISCOVERY_CACHE_TTL exceeds the safe integer range');\n\t\t\t}\n\t\t\tresult.discoveryCache = { ...result.discoveryCache, ttl };\n\t\t}\n\t\tif (process.env.DISCOVERY_CACHE_MAX_SIZE !== undefined) {\n\t\t\tconst maxSize = this.parseEnvironmentInteger(\n\t\t\t\t'DISCOVERY_CACHE_MAX_SIZE',\n\t\t\t\tprocess.env.DISCOVERY_CACHE_MAX_SIZE\n\t\t\t);\n\t\t\tresult.discoveryCache = { ...result.discoveryCache, maxSize };\n\t\t}\n\t\tif (process.env.TRACELATTICE_TOOL_INTERLEAVE_TTL_MS !== undefined) {\n\t\t\tresult.toolInterleaveTtlMs = this.parseEnvironmentInteger(\n\t\t\t\t'TRACELATTICE_TOOL_INTERLEAVE_TTL_MS',\n\t\t\t\tprocess.env.TRACELATTICE_TOOL_INTERLEAVE_TTL_MS\n\t\t\t);\n\t\t}\n\t\tif (process.env.TRACELATTICE_TOOL_INTERLEAVE_SWEEP_MS !== undefined) {\n\t\t\tresult.toolInterleaveSweepMs = this.parseEnvironmentInteger(\n\t\t\t\t'TRACELATTICE_TOOL_INTERLEAVE_SWEEP_MS',\n\t\t\t\tprocess.env.TRACELATTICE_TOOL_INTERLEAVE_SWEEP_MS\n\t\t\t);\n\t\t}\n\t\tif (process.env.SESSION_MAX_PER_OWNER !== undefined) {\n\t\t\tresult.maxSessionsPerOwner = this.parseEnvironmentInteger(\n\t\t\t\t'SESSION_MAX_PER_OWNER',\n\t\t\t\tprocess.env.SESSION_MAX_PER_OWNER\n\t\t\t);\n\t\t}\n\n\t\tthis.applyFeatureFlagOverrides(result);\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Applies TRACELATTICE_FEATURES_* environment variable overrides for feature flags.\n\t * Booleans accept 'true'/'false'/'1'/'0' (case-insensitive).\n\t * Invalid reasoningStrategy values are rejected.\n\t *\n\t * @param result - Configuration object to mutate with feature flag overrides\n\t * @private\n\t */\n\tprivate applyFeatureFlagOverrides(result: Mutable<ConfigFileOptions>): void {\n\t\tconst boolMap: Record<string, Exclude<keyof FeatureFlags, 'reasoningStrategy'>> = {\n\t\t\tTRACELATTICE_FEATURES_DAG_EDGES: 'dagEdges',\n\t\t\tTRACELATTICE_FEATURES_CALIBRATION: 'calibration',\n\t\t\tTRACELATTICE_FEATURES_COMPRESSION: 'compression',\n\t\t\tTRACELATTICE_FEATURES_TOOL_INTERLEAVE: 'toolInterleave',\n\t\t\tTRACELATTICE_FEATURES_NEW_THOUGHT_TYPES: 'newThoughtTypes',\n\t\t\tTRACELATTICE_FEATURES_OUTCOME_RECORDING: 'outcomeRecording',\n\t\t};\n\t\tfor (const [envVar, key] of Object.entries(boolMap)) {\n\t\t\tconst raw = process.env[envVar];\n\t\t\tif (raw === undefined) continue;\n\t\t\tconst parsed = this.parseBoolean(raw);\n\t\t\tif (parsed === undefined) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Invalid boolean value for ${envVar}: \"${raw}\" (expected true/false/1/0). Ignoring.`\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst features: { -readonly [K in keyof FeatureFlags]?: FeatureFlags[K] } =\n\t\t\t\tresult.features ?? {};\n\t\t\tfeatures[key] = parsed;\n\t\t\tresult.features = features;\n\t\t}\n\n\t\tconst strategyRaw = process.env.TRACELATTICE_FEATURES_REASONING_STRATEGY;\n\t\tif (strategyRaw !== undefined) {\n\t\t\tif (strategyRaw !== 'sequential' && strategyRaw !== 'tot') {\n\t\t\t\tthrow new ConfigurationError(\n\t\t\t\t\t`TRACELATTICE_FEATURES_REASONING_STRATEGY must be one of sequential, tot, got ${strategyRaw}`\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst features: { -readonly [K in keyof FeatureFlags]?: FeatureFlags[K] } =\n\t\t\t\tresult.features ?? {};\n\t\t\tfeatures.reasoningStrategy = strategyRaw;\n\t\t\tresult.features = features;\n\t\t}\n\t}\n\n\tprivate parseEnvironmentInteger(environmentName: string, raw: string): number {\n\t\tif (!/^(0|[1-9]\\d*)$/.test(raw)) {\n\t\t\tthrow new ConfigurationError(`${environmentName} must be an integer, got ${raw}`);\n\t\t}\n\t\tconst parsed = Number(raw);\n\t\tif (!Number.isSafeInteger(parsed)) {\n\t\t\tthrow new ConfigurationError(`${environmentName} must be a safe integer, got ${raw}`);\n\t\t}\n\t\treturn parsed;\n\t}\n\n\t/**\n\t * Parses a boolean from an environment variable string.\n\t * Accepts 'true'/'false'/'1'/'0' case-insensitively.\n\t *\n\t * @param raw - Raw environment variable string\n\t * @returns Parsed boolean, or undefined if the value is invalid\n\t * @private\n\t */\n\tprivate parseBoolean(raw: string): boolean | undefined {\n\t\tconst v = raw.trim().toLowerCase();\n\t\tif (v === 'true' || v === '1') return true;\n\t\tif (v === 'false' || v === '0') return false;\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Parses a configuration file (JSON or YAML).\n\t *\n\t * Detects the file type by extension and uses the appropriate parser.\n\t * Supports `.json`, `.yaml`, and `.yml` file extensions.\n\t *\n\t * @param filePath - Path to the configuration file to parse\n\t * @returns The parsed configuration object\n\t * @throws {Error} If the file cannot be parsed\n\t * @private\n\t */\n\tprivate parseConfig(filePath: string): ConfigFileOptions {\n\t\tconst content = readFileSync(filePath, 'utf-8');\n\t\tconst ext = filePath.split('.').pop()?.toLowerCase();\n\n\t\tconst raw: unknown = ext === 'yaml' || ext === 'yml' ? parseYaml(content) : JSON.parse(content);\n\n\t\treturn v.parse(ConfigFileOptionsSchema, raw) as ConfigFileOptions;\n\t}\n\n\t/**\n\t * Converts file-based configuration to ServerConfig options.\n\t *\n\t * This is a convenience method for extracting the ServerConfig-relevant\n\t * options from a file-based configuration.\n\t *\n\t * @param config - The configuration to convert\n\t * @returns An object with ServerConfig-compatible options\n\t *\n\t * @example\n\t * ```typescript\n\t * const loader = new ConfigLoader();\n\t * const config = loader.load();\n\t * if (config) {\n\t * const serverOpts = loader.toServerConfigOptions(config);\n\t * const serverConfig = new ServerConfig(serverOpts);\n\t * }\n\t * ```\n\t */\n\ttoServerConfigOptions(config: ConfigFileOptions): ServerConfigOptions {\n\t\treturn {\n\t\t\tmaxHistorySize: config.maxHistorySize,\n\t\t\tmaxBranches: config.maxBranches,\n\t\t\tmaxBranchSize: config.maxBranchSize,\n\t\t\tskillDirs: config.skillDirs,\n\t\t\ttoolDirs: config.toolDirs,\n\t\t\tdiscoveryCache: config.discoveryCache,\n\t\t\tpersistence: config.persistence,\n\t\t\tpersistenceBufferSize: config.persistenceBufferSize,\n\t\t\tpersistenceFlushInterval: config.persistenceFlushInterval,\n\t\t\tpersistenceMaxRetries: config.persistenceMaxRetries,\n\t\t\tfeatures: config.features,\n\t\t\ttoolInterleaveTtlMs: config.toolInterleaveTtlMs,\n\t\t\ttoolInterleaveSweepMs: config.toolInterleaveSweepMs,\n\t\t\tmaxSessionsPerOwner: config.maxSessionsPerOwner,\n\t\t};\n\t}\n}\n"],"names":["ConfigFileOptionsSchema","v","ConfigLoader","customPath","join","homedir","config","configPath","existsSync","error","console","getErrorMessage","result","undefined","process","seconds","ttl","Number","ConfigurationError","maxSize","boolMap","envVar","key","Object","raw","parsed","features","strategyRaw","environmentName","filePath","content","readFileSync","ext","parseYaml","JSON"],"mappings":"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"}
|
|
@@ -241,12 +241,7 @@ export declare class Metrics {
|
|
|
241
241
|
* @private
|
|
242
242
|
*/
|
|
243
243
|
private _metricKey;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
* @param key - Metric key
|
|
247
|
-
* @returns Name and labels
|
|
248
|
-
* @private
|
|
249
|
-
*/
|
|
250
|
-
private _parseMetricKey;
|
|
244
|
+
private _formatLabels;
|
|
245
|
+
private _escapeLabelValue;
|
|
251
246
|
}
|
|
252
247
|
//# sourceMappingURL=metrics.impl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.impl.d.ts","sourceRoot":"","sources":["../../src/metrics/metrics.impl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,oBAAY,UAAU;IACrB,wDAAwD;IACxD,OAAO,YAAY;IAEnB,+CAA+C;IAC/C,KAAK,UAAU;IAEf,qDAAqD;IACrD,SAAS,cAAc;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACtB,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IAEb,iDAAiD;IACjD,IAAI,EAAE,UAAU,CAAC;IAEjB,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IAEd,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE/B,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"metrics.impl.d.ts","sourceRoot":"","sources":["../../src/metrics/metrics.impl.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,oBAAY,UAAU;IACrB,wDAAwD;IACxD,OAAO,YAAY;IAEnB,+CAA+C;IAC/C,KAAK,UAAU;IAEf,qDAAqD;IACrD,SAAS,cAAc;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACtB,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IAEb,iDAAiD;IACjD,IAAI,EAAE,UAAU,CAAC;IAEjB,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IAEd,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE/B,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,qBAAa,OAAO;IACnB,qDAAqD;IACrD,OAAO,CAAC,QAAQ,CAAsB;IAEtC,wBAAwB;IACxB,OAAO,CAAC,WAAW,CAA+B;IAElD,kCAAkC;IAClC,OAAO,CAAC,OAAO,CAAS;IAExB,6CAA6C;IAC7C,OAAO,CAAC,cAAc,CAAyB;IAE/C,+CAA+C;IAC/C,OAAO,CAAC,kBAAkB,CAAa;IAEvC;;;;;;;;;OASG;gBACS,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAO;IAOrF;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAI,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAuB1F;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAe5F;;;;;;;;;;;;;;OAcG;IACH,SAAS,CACR,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACnC,OAAO,WAAkB,GACvB,IAAI;IAkCP;;;;;;;;;;;OAWG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,MAAM,GAAG,SAAS;IAO1E;;;;;;;;;;OAUG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAIxD;;;;;;;;;;OAUG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAKxD;;;;;;;;OAQG;IACH,KAAK,IAAI,IAAI;IAMb;;;;;;;;;;OAUG;IACH,iBAAiB,IAAI,MAAM;IAI3B;;;;;;;;;;;;;;OAcG;IACH,MAAM,IAAI,MAAM;IA8DhB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IASjB;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,iBAAiB;CAGzB"}
|
|
@@ -27,7 +27,9 @@ class Metrics {
|
|
|
27
27
|
this._metrics = new Map();
|
|
28
28
|
this._histograms = new Map();
|
|
29
29
|
this._prefix = options.prefix ?? '';
|
|
30
|
-
this._defaultLabels =
|
|
30
|
+
this._defaultLabels = {
|
|
31
|
+
...options.defaultLabels
|
|
32
|
+
};
|
|
31
33
|
}
|
|
32
34
|
counter(name, value = 1, labels = {}, help) {
|
|
33
35
|
const fullName = this._fullName(name);
|
|
@@ -44,7 +46,9 @@ class Metrics {
|
|
|
44
46
|
name: fullName,
|
|
45
47
|
type: "counter",
|
|
46
48
|
value,
|
|
47
|
-
labels:
|
|
49
|
+
labels: {
|
|
50
|
+
...allLabels
|
|
51
|
+
},
|
|
48
52
|
help
|
|
49
53
|
});
|
|
50
54
|
this._operationsCounter++;
|
|
@@ -61,7 +65,9 @@ class Metrics {
|
|
|
61
65
|
name: fullName,
|
|
62
66
|
type: "gauge",
|
|
63
67
|
value,
|
|
64
|
-
labels:
|
|
68
|
+
labels: {
|
|
69
|
+
...allLabels
|
|
70
|
+
},
|
|
65
71
|
help: help ?? existing?.help
|
|
66
72
|
});
|
|
67
73
|
this._operationsCounter++;
|
|
@@ -82,6 +88,10 @@ class Metrics {
|
|
|
82
88
|
histogram.buckets.set(1 / 0, (histogram.buckets.get(1 / 0) ?? 0) + 1);
|
|
83
89
|
} else {
|
|
84
90
|
const histogramData = {
|
|
91
|
+
name: fullName,
|
|
92
|
+
labels: {
|
|
93
|
+
...allLabels
|
|
94
|
+
},
|
|
85
95
|
sum: value,
|
|
86
96
|
count: 1,
|
|
87
97
|
buckets: new Map()
|
|
@@ -119,7 +129,7 @@ class Metrics {
|
|
|
119
129
|
export() {
|
|
120
130
|
const lines = [];
|
|
121
131
|
const metrics = Array.from(this._metrics.values());
|
|
122
|
-
const histograms = Array.from(this._histograms.
|
|
132
|
+
const histograms = Array.from(this._histograms.values());
|
|
123
133
|
const helpEntries = new Map();
|
|
124
134
|
const typeEntries = new Map();
|
|
125
135
|
for (const metric of metrics){
|
|
@@ -132,17 +142,17 @@ class Metrics {
|
|
|
132
142
|
lines.push(`# TYPE ${metric.name} ${metric.type}`);
|
|
133
143
|
typeEntries.set(metric.name, metric.type);
|
|
134
144
|
}
|
|
135
|
-
const labelStr =
|
|
145
|
+
const labelStr = this._formatLabels(metric.labels);
|
|
136
146
|
lines.push(`${metric.name}{${labelStr}} ${metric.value}`);
|
|
137
147
|
}
|
|
138
|
-
for (const
|
|
139
|
-
const { name: fullName, labels } =
|
|
148
|
+
for (const histogram of histograms){
|
|
149
|
+
const { name: fullName, labels } = histogram;
|
|
140
150
|
if (histogram.count > 0) {
|
|
141
151
|
if (!typeEntries.has(fullName)) {
|
|
142
152
|
lines.push(`# TYPE ${fullName} histogram`);
|
|
143
153
|
typeEntries.set(fullName, "histogram");
|
|
144
154
|
}
|
|
145
|
-
const labelStr =
|
|
155
|
+
const labelStr = this._formatLabels(labels);
|
|
146
156
|
const formatMetricLine = (suffix, value)=>{
|
|
147
157
|
if (0 === labelStr.length) return `${fullName}${suffix} ${value}`;
|
|
148
158
|
return `${fullName}${suffix}{${labelStr}} ${value}`;
|
|
@@ -151,7 +161,10 @@ class Metrics {
|
|
|
151
161
|
lines.push(formatMetricLine('_count', histogram.count));
|
|
152
162
|
for (const [boundary, count] of histogram.buckets.entries()){
|
|
153
163
|
const boundaryLabel = 1 / 0 === boundary ? '+Inf' : Number.isFinite(boundary) ? String(boundary) : String(boundary);
|
|
154
|
-
const
|
|
164
|
+
const boundaryLabelStr = this._formatLabels({
|
|
165
|
+
le: boundaryLabel
|
|
166
|
+
});
|
|
167
|
+
const bucketLabels = 0 === labelStr.length ? boundaryLabelStr : `${labelStr},${boundaryLabelStr}`;
|
|
155
168
|
lines.push(`${fullName}_bucket{${bucketLabels}} ${count}`);
|
|
156
169
|
}
|
|
157
170
|
}
|
|
@@ -164,32 +177,17 @@ class Metrics {
|
|
|
164
177
|
return name.startsWith(prefix) ? name : `${prefix}${name}`;
|
|
165
178
|
}
|
|
166
179
|
_metricKey(name, labels) {
|
|
167
|
-
const sortedLabels = Object.entries(labels).sort(([a], [b])=>a
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
_parseMetricKey(key) {
|
|
171
|
-
const match = key.match(/^([^{}]+)\{([^{}]*)\}$/);
|
|
172
|
-
if (!match) return {
|
|
173
|
-
name: key,
|
|
174
|
-
labels: {}
|
|
175
|
-
};
|
|
176
|
-
const [, name, labelsPart] = match;
|
|
177
|
-
if (!labelsPart) return {
|
|
180
|
+
const sortedLabels = Object.entries(labels).sort(([a], [b])=>a < b ? -1 : a > b ? 1 : 0);
|
|
181
|
+
return JSON.stringify([
|
|
178
182
|
name,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
labels[labelKey] = labelValue;
|
|
188
|
-
}
|
|
189
|
-
return {
|
|
190
|
-
name,
|
|
191
|
-
labels
|
|
192
|
-
};
|
|
183
|
+
sortedLabels
|
|
184
|
+
]);
|
|
185
|
+
}
|
|
186
|
+
_formatLabels(labels) {
|
|
187
|
+
return Object.entries(labels).map(([key, value])=>`${key}="${this._escapeLabelValue(value)}"`).join(',');
|
|
188
|
+
}
|
|
189
|
+
_escapeLabelValue(value) {
|
|
190
|
+
return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('\n', '\\n');
|
|
193
191
|
}
|
|
194
192
|
}
|
|
195
193
|
export { Metrics, metrics_impl_MetricType as MetricType };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics/metrics.impl.js","sources":["../../src/metrics/metrics.impl.ts"],"sourcesContent":["/**\n * Prometheus-compatible metrics collection.\n *\n * This module provides metrics collection and export in Prometheus text format.\n * Supports counters, gauges, and histograms for observability.\n *\n * @module metrics\n */\n\n/**\n * Metric types supported by the metrics collector.\n */\nexport enum MetricType {\n\t/** Counter - a cumulative metric that only increases */\n\tCounter = 'counter',\n\n\t/** Gauge - a metric that can go up and down */\n\tGauge = 'gauge',\n\n\t/** Histogram - a metric for sampling observations */\n\tHistogram = 'histogram',\n}\n\n/**\n * A collected metric with its value and labels.\n */\nexport interface Metric {\n\t/** Name of the metric */\n\tname: string;\n\n\t/** Type of metric (counter, gauge, histogram) */\n\ttype: MetricType;\n\n\t/** Current value */\n\tvalue: number;\n\n\t/** Labels for the metric */\n\tlabels: Record<string, string>;\n\n\t/** Help text describing the metric */\n\thelp?: string;\n\n\t/** Timestamp when metric was recorded (Unix epoch) */\n\ttimestamp?: number;\n}\n\n/**\n * Histogram bucket boundaries for latency tracking.\n */\nconst DEFAULT_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\n/**\n * Prometheus-compatible metrics collector.\n *\n * Collects and exports metrics in Prometheus text format for scraping.\n * Thread-safe and supports counters, gauges, and histograms.\n *\n * @remarks\n * **Metric Types:**\n * - **Counter**: Cumulative value that only increases (e.g., request counts)\n * - **Gauge**: Value that can go up or down (e.g., active connections)\n * - **Histogram**: Sampling observations (e.g., request latency)\n *\n * **Labels:**\n * Metrics can have labels for dimensional data:\n * ```typescript\n * counter('http_requests_total', { method: 'GET', status: '200' });\n * ```\n *\n * **Export Format:**\n * Metrics are exported in Prometheus text format:\n * ```\n * # HELP http_requests_total Total HTTP requests\n * # TYPE http_requests_total counter\n * http_requests_total{method=\"GET\",status=\"200\"} 1234\n * ```\n *\n * @example\n * ```typescript\n * const metrics = new Metrics({ prefix: 'mcp_server' });\n *\n * // Count requests\n * metrics.counter('requests_total', { method: 'GET' }).inc();\n *\n * // Track active connections\n * const activeConnections = metrics.gauge('active_connections');\n * activeConnections.set(5);\n * activeConnections.dec();\n *\n * // Record latency\n * metrics.histogram('request_duration_seconds').observe(0.023);\n *\n * // Export for Prometheus\n * const prometheusText = metrics.export();\n * ```\n */\nexport class Metrics {\n\t/** Metrics storage indexed by name and label hash */\n\tprivate _metrics: Map<string, Metric>;\n\n\t/** Histogram samples */\n\tprivate _histograms: Map<string, { sum: number; count: number; buckets: Map<number, number> }>;\n\n\t/** Prefix for all metric names */\n\tprivate _prefix: string;\n\n\t/** Default labels to apply to all metrics */\n\tprivate _defaultLabels: Record<string, string>;\n\n\t/** Counter for metric collection operations */\n\tprivate _operationsCounter: number = 0;\n\n\t/**\n\t * Creates a new Metrics instance.\n\t *\n\t * @param options - Configuration options\n\t *\n\t * @example\n\t * ```typescript\n\t * const metrics = new Metrics({ prefix: 'mcp_server' });\n\t * ```\n\t */\n\tconstructor(options: { prefix?: string; defaultLabels?: Record<string, string> } = {}) {\n\t\tthis._metrics = new Map();\n\t\tthis._histograms = new Map();\n\t\tthis._prefix = options.prefix ?? '';\n\t\tthis._defaultLabels = options.defaultLabels ?? {};\n\t}\n\n\t/**\n\t * Creates a counter metric.\n\t *\n\t * @param name - Metric name\n\t * @param value - Increment value (default: 1)\n\t * @param labels - Optional labels\n\t * @param help - Help text\n\t *\n\t * @example\n\t * ```typescript\n\t * metrics.counter('requests_total', 1, { method: 'GET' });\n\t * metrics.counter('errors_total', 1, { type: 'timeout' });\n\t * ```\n\t */\n\tcounter(name: string, value = 1, labels: Record<string, string> = {}, help?: string): void {\n\t\tconst fullName = this._fullName(name);\n\t\tconst allLabels = { ...this._defaultLabels, ...labels };\n\t\tconst key = this._metricKey(fullName, allLabels);\n\t\tconst existing = this._metrics.get(key);\n\n\t\tif (existing) {\n\t\t\texisting.value += value;\n\t\t\tif (help && !existing.help) {\n\t\t\t\texisting.help = help;\n\t\t\t}\n\t\t} else {\n\t\t\tthis._metrics.set(key, {\n\t\t\t\tname: fullName,\n\t\t\t\ttype: MetricType.Counter,\n\t\t\t\tvalue,\n\t\t\t\tlabels: allLabels,\n\t\t\t\thelp,\n\t\t\t});\n\t\t}\n\t\tthis._operationsCounter++;\n\t}\n\n\t/**\n\t * Creates a gauge metric.\n\t *\n\t * @param name - Metric name\n\t * @param value - Current value\n\t * @param labels - Optional labels\n\t * @param help - Help text\n\t *\n\t * @example\n\t * ```typescript\n\t * const activeConnections = metrics.gauge('active_connections');\n\t * activeConnections.set(5);\n\t * activeConnections.dec();\n\t * ```\n\t */\n\tgauge(name: string, value: number, labels: Record<string, string> = {}, help?: string): void {\n\t\tconst fullName = this._fullName(name);\n\t\tconst allLabels = { ...this._defaultLabels, ...labels };\n\t\tconst key = this._metricKey(fullName, allLabels);\n\t\tconst existing = this._metrics.get(key);\n\t\tthis._metrics.set(key, {\n\t\t\tname: fullName,\n\t\t\ttype: MetricType.Gauge,\n\t\t\tvalue,\n\t\t\tlabels: allLabels,\n\t\t\thelp: help ?? existing?.help,\n\t\t});\n\t\tthis._operationsCounter++;\n\t}\n\n\t/**\n\t * Records a histogram observation.\n\t *\n\t * @param name - Histogram name\n\t * @param value - Observed value\n\t * @param labels - Optional labels\n\t * @param help - Help text\n\t * @param buckets - Custom bucket boundaries\n\t *\n\t * @example\n\t * ```typescript\n\t * metrics.histogram('request_duration_seconds').observe(0.023);\n\t * metrics.histogram('thought_processing_ms').observe(45);\n\t * ```\n\t */\n\thistogram(\n\t\tname: string,\n\t\tvalue: number,\n\t\tlabels: Record<string, string> = {},\n\t\tbuckets = DEFAULT_BUCKETS\n\t): void {\n\t\tconst fullName = this._fullName(name);\n\t\tconst allLabels = { ...this._defaultLabels, ...labels };\n\t\tconst key = this._metricKey(fullName, allLabels);\n\t\tconst histogram = this._histograms.get(key);\n\n\t\tif (histogram) {\n\t\t\thistogram.sum += value;\n\t\t\thistogram.count += 1;\n\t\t\tfor (const boundary of buckets) {\n\t\t\t\tif (value <= boundary) {\n\t\t\t\t\thistogram.buckets.set(boundary, (histogram.buckets.get(boundary) ?? 0) + 1);\n\t\t\t\t} else {\n\t\t\t\t\thistogram.buckets.set(boundary, histogram.buckets.get(boundary) ?? 0);\n\t\t\t\t}\n\t\t\t}\n\t\t\thistogram.buckets.set(Infinity, (histogram.buckets.get(Infinity) ?? 0) + 1);\n\t\t} else {\n\t\t\tconst histogramData = {\n\t\t\t\tsum: value,\n\t\t\t\tcount: 1,\n\t\t\t\tbuckets: new Map<number, number>(),\n\t\t\t};\n\t\t\tfor (const boundary of buckets) {\n\t\t\t\thistogramData.buckets.set(boundary, value <= boundary ? 1 : 0);\n\t\t\t}\n\t\t\thistogramData.buckets.set(Infinity, 1);\n\t\t\tthis._histograms.set(key, histogramData);\n\t\t}\n\t\tthis._operationsCounter++;\n\t}\n\n\t/**\n\t * Gets the current value of a metric.\n\t *\n\t * @param name - Metric name\n\t * @param labels - Labels\n\t * @returns Current value or undefined if not found\n\t *\n\t * @example\n\t * ```typescript\n\t * const count = metrics.get('requests_total', { method: 'GET' });\n\t * ```\n\t */\n\tget(name: string, labels: Record<string, string> = {}): number | undefined {\n\t\tconst fullName = this._fullName(name);\n\t\tconst allLabels = { ...this._defaultLabels, ...labels };\n\t\tconst key = this._metricKey(fullName, allLabels);\n\t\treturn this._metrics.get(key)?.value;\n\t}\n\n\t/**\n\t * Increments a counter by 1.\n\t *\n\t * @param name - Metric name\n\t * @param labels - Optional labels\n\t *\n\t * @example\n\t * ```typescript\n\t * metrics.inc('requests_total', { method: 'GET' });\n\t * ```\n\t */\n\tinc(name: string, labels?: Record<string, string>): void {\n\t\tthis.counter(name, 1, labels);\n\t}\n\n\t/**\n\t * Decrements a gauge by 1.\n\t *\n\t * @param name - Metric name\n\t * @param labels - Optional labels\n\t *\n\t * @example\n\t * ```typescript\n\t * metrics.dec('active_connections');\n\t * ```\n\t */\n\tdec(name: string, labels?: Record<string, string>): void {\n\t\tconst value = this.get(name, labels) ?? 0;\n\t\tthis.gauge(name, value - 1, labels);\n\t}\n\n\t/**\n\t * Resets all metrics to zero.\n\t * Useful for testing or on server restart.\n\t *\n\t * @example\n\t * ```typescript\n\t * metrics.reset();\n\t * ```\n\t */\n\treset(): void {\n\t\tthis._metrics.clear();\n\t\tthis._histograms.clear();\n\t\tthis._operationsCounter = 0;\n\t}\n\n\t/**\n\t * Gets the number of metric operations performed.\n\t * Useful for testing.\n\t *\n\t * @returns Operation count\n\t *\n\t * @example\n\t * ```typescript\n\t * const ops = metrics.getOperationCount();\n\t * ```\n\t */\n\tgetOperationCount(): number {\n\t\treturn this._operationsCounter;\n\t}\n\n\t/**\n\t * Exports metrics in Prometheus text format.\n\t *\n\t * @returns Prometheus text format string\n\t *\n\t * @example\n\t * ```typescript\n\t * const prometheusText = metrics.export();\n\t * console.log(prometheusText);\n\t * // Output:\n\t * // # HELP http_requests_total Total HTTP requests\n\t * // # TYPE http_requests_total counter\n\t * // http_requests_total{method=\"GET\",status=\"200\"} 1234\n\t * ```\n\t */\n\texport(): string {\n\t\tconst lines: string[] = [];\n\n\t\tconst metrics = Array.from(this._metrics.values());\n\t\tconst histograms = Array.from(this._histograms.entries());\n\n\t\tconst helpEntries = new Map<string, string>();\n\t\tconst typeEntries = new Map<string, MetricType>();\n\n\t\tfor (const metric of metrics) {\n\t\t\tif (!helpEntries.has(metric.name)) {\n\t\t\t\tconst helpText = metric.help ?? `${metric.name} metric`;\n\t\t\t\tlines.push(`# HELP ${metric.name} ${helpText}`);\n\t\t\t\thelpEntries.set(metric.name, helpText);\n\t\t\t}\n\t\t\tif (!typeEntries.has(metric.name)) {\n\t\t\t\tlines.push(`# TYPE ${metric.name} ${metric.type}`);\n\t\t\t\ttypeEntries.set(metric.name, metric.type);\n\t\t\t}\n\n\t\t\tconst labelStr = Object.entries(metric.labels)\n\t\t\t\t.map(([k, v]) => `${k}=\"${v}\"`)\n\t\t\t\t.join(',');\n\t\t\tlines.push(`${metric.name}{${labelStr}} ${metric.value}`);\n\t\t}\n\n\t\tfor (const [key, histogram] of histograms) {\n\t\t\tconst { name: fullName, labels } = this._parseMetricKey(key);\n\t\t\tif (histogram.count > 0) {\n\t\t\t\tif (!typeEntries.has(fullName)) {\n\t\t\t\t\tlines.push(`# TYPE ${fullName} histogram`);\n\t\t\t\t\ttypeEntries.set(fullName, MetricType.Histogram);\n\t\t\t\t}\n\n\t\t\t\tconst labelStr = Object.entries(labels)\n\t\t\t\t\t.map(([k, v]) => `${k}=\"${v}\"`)\n\t\t\t\t\t.join(',');\n\t\t\t\tconst formatMetricLine = (suffix: string, value: number): string => {\n\t\t\t\t\tif (labelStr.length === 0) {\n\t\t\t\t\t\treturn `${fullName}${suffix} ${value}`;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn `${fullName}${suffix}{${labelStr}} ${value}`;\n\t\t\t\t};\n\n\t\t\t\tlines.push(formatMetricLine('_sum', histogram.sum));\n\t\t\t\tlines.push(formatMetricLine('_count', histogram.count));\n\n\t\t\t\tfor (const [boundary, count] of histogram.buckets.entries()) {\n\t\t\t\t\tconst boundaryLabel =\n\t\t\t\t\t\tboundary === Infinity\n\t\t\t\t\t\t\t? '+Inf'\n\t\t\t\t\t\t\t: Number.isFinite(boundary)\n\t\t\t\t\t\t\t\t? String(boundary)\n\t\t\t\t\t\t\t\t: String(boundary);\n\t\t\t\t\tconst bucketLabels =\n\t\t\t\t\t\tlabelStr.length === 0 ? `le=\"${boundaryLabel}\"` : `${labelStr},le=\"${boundaryLabel}\"`;\n\t\t\t\t\tlines.push(`${fullName}_bucket{${bucketLabels}} ${count}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn lines.join('\\n');\n\t}\n\n\t/**\n\t * Gets metric name with prefix.\n\t * @param name - Metric name\n\t * @returns Full metric name\n\t * @private\n\t */\n\tprivate _fullName(name: string): string {\n\t\tif (!this._prefix) {\n\t\t\treturn name;\n\t\t}\n\n\t\tconst prefix = `${this._prefix}_`;\n\t\treturn name.startsWith(prefix) ? name : `${prefix}${name}`;\n\t}\n\n\t/**\n\t * Creates a unique key for a metric with labels.\n\t * @param name - Metric name\n\t * @param labels - Labels\n\t * @returns Unique key\n\t * @private\n\t */\n\tprivate _metricKey(name: string, labels: Record<string, string>): string {\n\t\tconst sortedLabels = Object.entries(labels)\n\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t.map(([k, v]) => `${k}=${v}`)\n\t\t\t.join(',');\n\t\treturn `${name}{${sortedLabels}}`;\n\t}\n\n\t/**\n\t * Parses a metric key back into name and labels.\n\t * @param key - Metric key\n\t * @returns Name and labels\n\t * @private\n\t */\n\tprivate _parseMetricKey(key: string): { name: string; labels: Record<string, string> } {\n\t\tconst match = key.match(/^([^{}]+)\\{([^{}]*)\\}$/);\n\t\tif (!match) {\n\t\t\treturn { name: key, labels: {} };\n\t\t}\n\n\t\tconst [, name, labelsPart] = match as [string, string, string | undefined];\n\t\tif (!labelsPart) {\n\t\t\treturn { name, labels: {} };\n\t\t}\n\n\t\tconst labels: Record<string, string> = {};\n\t\tfor (const label of labelsPart.split(',')) {\n\t\t\tconst separatorIndex = label.indexOf('=');\n\t\t\tif (separatorIndex <= 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst labelKey = label.slice(0, separatorIndex);\n\t\t\tconst labelValue = label.slice(separatorIndex + 1);\n\t\t\tlabels[labelKey] = labelValue;\n\t\t}\n\n\t\treturn { name, labels };\n\t}\n}\n"],"names":["MetricType","DEFAULT_BUCKETS","Metrics","options","Map","name","value","labels","help","fullName","allLabels","key","existing","buckets","histogram","boundary","Infinity","histogramData","lines","metrics","Array","histograms","helpEntries","typeEntries","metric","helpText","labelStr","Object","k","v","formatMetricLine","suffix","count","boundaryLabel","Number","String","bucketLabels","prefix","sortedLabels","a","b","match","labelsPart","label","separatorIndex","labelKey","labelValue"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"metrics/metrics.impl.js","sources":["../../src/metrics/metrics.impl.ts"],"sourcesContent":["/**\n * Prometheus-compatible metrics collection.\n *\n * This module provides metrics collection and export in Prometheus text format.\n * Supports counters, gauges, and histograms for observability.\n *\n * @module metrics\n */\n\n/**\n * Metric types supported by the metrics collector.\n */\nexport enum MetricType {\n\t/** Counter - a cumulative metric that only increases */\n\tCounter = 'counter',\n\n\t/** Gauge - a metric that can go up and down */\n\tGauge = 'gauge',\n\n\t/** Histogram - a metric for sampling observations */\n\tHistogram = 'histogram',\n}\n\n/**\n * A collected metric with its value and labels.\n */\nexport interface Metric {\n\t/** Name of the metric */\n\tname: string;\n\n\t/** Type of metric (counter, gauge, histogram) */\n\ttype: MetricType;\n\n\t/** Current value */\n\tvalue: number;\n\n\t/** Labels for the metric */\n\tlabels: Record<string, string>;\n\n\t/** Help text describing the metric */\n\thelp?: string;\n\n\t/** Timestamp when metric was recorded (Unix epoch) */\n\ttimestamp?: number;\n}\n\ninterface HistogramMetric {\n\tname: string;\n\tlabels: Record<string, string>;\n\tsum: number;\n\tcount: number;\n\tbuckets: Map<number, number>;\n}\n\n/**\n * Histogram bucket boundaries for latency tracking.\n */\nconst DEFAULT_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\n/**\n * Prometheus-compatible metrics collector.\n *\n * Collects and exports metrics in Prometheus text format for scraping.\n * Thread-safe and supports counters, gauges, and histograms.\n *\n * @remarks\n * **Metric Types:**\n * - **Counter**: Cumulative value that only increases (e.g., request counts)\n * - **Gauge**: Value that can go up or down (e.g., active connections)\n * - **Histogram**: Sampling observations (e.g., request latency)\n *\n * **Labels:**\n * Metrics can have labels for dimensional data:\n * ```typescript\n * counter('http_requests_total', { method: 'GET', status: '200' });\n * ```\n *\n * **Export Format:**\n * Metrics are exported in Prometheus text format:\n * ```\n * # HELP http_requests_total Total HTTP requests\n * # TYPE http_requests_total counter\n * http_requests_total{method=\"GET\",status=\"200\"} 1234\n * ```\n *\n * @example\n * ```typescript\n * const metrics = new Metrics({ prefix: 'mcp_server' });\n *\n * // Count requests\n * metrics.counter('requests_total', { method: 'GET' }).inc();\n *\n * // Track active connections\n * const activeConnections = metrics.gauge('active_connections');\n * activeConnections.set(5);\n * activeConnections.dec();\n *\n * // Record latency\n * metrics.histogram('request_duration_seconds').observe(0.023);\n *\n * // Export for Prometheus\n * const prometheusText = metrics.export();\n * ```\n */\nexport class Metrics {\n\t/** Metrics storage indexed by name and label hash */\n\tprivate _metrics: Map<string, Metric>;\n\n\t/** Histogram samples */\n\tprivate _histograms: Map<string, HistogramMetric>;\n\n\t/** Prefix for all metric names */\n\tprivate _prefix: string;\n\n\t/** Default labels to apply to all metrics */\n\tprivate _defaultLabels: Record<string, string>;\n\n\t/** Counter for metric collection operations */\n\tprivate _operationsCounter: number = 0;\n\n\t/**\n\t * Creates a new Metrics instance.\n\t *\n\t * @param options - Configuration options\n\t *\n\t * @example\n\t * ```typescript\n\t * const metrics = new Metrics({ prefix: 'mcp_server' });\n\t * ```\n\t */\n\tconstructor(options: { prefix?: string; defaultLabels?: Record<string, string> } = {}) {\n\t\tthis._metrics = new Map();\n\t\tthis._histograms = new Map();\n\t\tthis._prefix = options.prefix ?? '';\n\t\tthis._defaultLabels = { ...options.defaultLabels };\n\t}\n\n\t/**\n\t * Creates a counter metric.\n\t *\n\t * @param name - Metric name\n\t * @param value - Increment value (default: 1)\n\t * @param labels - Optional labels\n\t * @param help - Help text\n\t *\n\t * @example\n\t * ```typescript\n\t * metrics.counter('requests_total', 1, { method: 'GET' });\n\t * metrics.counter('errors_total', 1, { type: 'timeout' });\n\t * ```\n\t */\n\tcounter(name: string, value = 1, labels: Record<string, string> = {}, help?: string): void {\n\t\tconst fullName = this._fullName(name);\n\t\tconst allLabels = { ...this._defaultLabels, ...labels };\n\t\tconst key = this._metricKey(fullName, allLabels);\n\t\tconst existing = this._metrics.get(key);\n\n\t\tif (existing) {\n\t\t\texisting.value += value;\n\t\t\tif (help && !existing.help) {\n\t\t\t\texisting.help = help;\n\t\t\t}\n\t\t} else {\n\t\t\tthis._metrics.set(key, {\n\t\t\t\tname: fullName,\n\t\t\t\ttype: MetricType.Counter,\n\t\t\t\tvalue,\n\t\t\t\tlabels: { ...allLabels },\n\t\t\t\thelp,\n\t\t\t});\n\t\t}\n\t\tthis._operationsCounter++;\n\t}\n\n\t/**\n\t * Creates a gauge metric.\n\t *\n\t * @param name - Metric name\n\t * @param value - Current value\n\t * @param labels - Optional labels\n\t * @param help - Help text\n\t *\n\t * @example\n\t * ```typescript\n\t * const activeConnections = metrics.gauge('active_connections');\n\t * activeConnections.set(5);\n\t * activeConnections.dec();\n\t * ```\n\t */\n\tgauge(name: string, value: number, labels: Record<string, string> = {}, help?: string): void {\n\t\tconst fullName = this._fullName(name);\n\t\tconst allLabels = { ...this._defaultLabels, ...labels };\n\t\tconst key = this._metricKey(fullName, allLabels);\n\t\tconst existing = this._metrics.get(key);\n\t\tthis._metrics.set(key, {\n\t\t\tname: fullName,\n\t\t\ttype: MetricType.Gauge,\n\t\t\tvalue,\n\t\t\tlabels: { ...allLabels },\n\t\t\thelp: help ?? existing?.help,\n\t\t});\n\t\tthis._operationsCounter++;\n\t}\n\n\t/**\n\t * Records a histogram observation.\n\t *\n\t * @param name - Histogram name\n\t * @param value - Observed value\n\t * @param labels - Optional labels\n\t * @param help - Help text\n\t * @param buckets - Custom bucket boundaries\n\t *\n\t * @example\n\t * ```typescript\n\t * metrics.histogram('request_duration_seconds').observe(0.023);\n\t * metrics.histogram('thought_processing_ms').observe(45);\n\t * ```\n\t */\n\thistogram(\n\t\tname: string,\n\t\tvalue: number,\n\t\tlabels: Record<string, string> = {},\n\t\tbuckets = DEFAULT_BUCKETS\n\t): void {\n\t\tconst fullName = this._fullName(name);\n\t\tconst allLabels = { ...this._defaultLabels, ...labels };\n\t\tconst key = this._metricKey(fullName, allLabels);\n\t\tconst histogram = this._histograms.get(key);\n\n\t\tif (histogram) {\n\t\t\thistogram.sum += value;\n\t\t\thistogram.count += 1;\n\t\t\tfor (const boundary of buckets) {\n\t\t\t\tif (value <= boundary) {\n\t\t\t\t\thistogram.buckets.set(boundary, (histogram.buckets.get(boundary) ?? 0) + 1);\n\t\t\t\t} else {\n\t\t\t\t\thistogram.buckets.set(boundary, histogram.buckets.get(boundary) ?? 0);\n\t\t\t\t}\n\t\t\t}\n\t\t\thistogram.buckets.set(Infinity, (histogram.buckets.get(Infinity) ?? 0) + 1);\n\t\t} else {\n\t\t\tconst histogramData = {\n\t\t\t\tname: fullName,\n\t\t\t\tlabels: { ...allLabels },\n\t\t\t\tsum: value,\n\t\t\t\tcount: 1,\n\t\t\t\tbuckets: new Map<number, number>(),\n\t\t\t};\n\t\t\tfor (const boundary of buckets) {\n\t\t\t\thistogramData.buckets.set(boundary, value <= boundary ? 1 : 0);\n\t\t\t}\n\t\t\thistogramData.buckets.set(Infinity, 1);\n\t\t\tthis._histograms.set(key, histogramData);\n\t\t}\n\t\tthis._operationsCounter++;\n\t}\n\n\t/**\n\t * Gets the current value of a metric.\n\t *\n\t * @param name - Metric name\n\t * @param labels - Labels\n\t * @returns Current value or undefined if not found\n\t *\n\t * @example\n\t * ```typescript\n\t * const count = metrics.get('requests_total', { method: 'GET' });\n\t * ```\n\t */\n\tget(name: string, labels: Record<string, string> = {}): number | undefined {\n\t\tconst fullName = this._fullName(name);\n\t\tconst allLabels = { ...this._defaultLabels, ...labels };\n\t\tconst key = this._metricKey(fullName, allLabels);\n\t\treturn this._metrics.get(key)?.value;\n\t}\n\n\t/**\n\t * Increments a counter by 1.\n\t *\n\t * @param name - Metric name\n\t * @param labels - Optional labels\n\t *\n\t * @example\n\t * ```typescript\n\t * metrics.inc('requests_total', { method: 'GET' });\n\t * ```\n\t */\n\tinc(name: string, labels?: Record<string, string>): void {\n\t\tthis.counter(name, 1, labels);\n\t}\n\n\t/**\n\t * Decrements a gauge by 1.\n\t *\n\t * @param name - Metric name\n\t * @param labels - Optional labels\n\t *\n\t * @example\n\t * ```typescript\n\t * metrics.dec('active_connections');\n\t * ```\n\t */\n\tdec(name: string, labels?: Record<string, string>): void {\n\t\tconst value = this.get(name, labels) ?? 0;\n\t\tthis.gauge(name, value - 1, labels);\n\t}\n\n\t/**\n\t * Resets all metrics to zero.\n\t * Useful for testing or on server restart.\n\t *\n\t * @example\n\t * ```typescript\n\t * metrics.reset();\n\t * ```\n\t */\n\treset(): void {\n\t\tthis._metrics.clear();\n\t\tthis._histograms.clear();\n\t\tthis._operationsCounter = 0;\n\t}\n\n\t/**\n\t * Gets the number of metric operations performed.\n\t * Useful for testing.\n\t *\n\t * @returns Operation count\n\t *\n\t * @example\n\t * ```typescript\n\t * const ops = metrics.getOperationCount();\n\t * ```\n\t */\n\tgetOperationCount(): number {\n\t\treturn this._operationsCounter;\n\t}\n\n\t/**\n\t * Exports metrics in Prometheus text format.\n\t *\n\t * @returns Prometheus text format string\n\t *\n\t * @example\n\t * ```typescript\n\t * const prometheusText = metrics.export();\n\t * console.log(prometheusText);\n\t * // Output:\n\t * // # HELP http_requests_total Total HTTP requests\n\t * // # TYPE http_requests_total counter\n\t * // http_requests_total{method=\"GET\",status=\"200\"} 1234\n\t * ```\n\t */\n\texport(): string {\n\t\tconst lines: string[] = [];\n\n\t\tconst metrics = Array.from(this._metrics.values());\n\t\tconst histograms = Array.from(this._histograms.values());\n\n\t\tconst helpEntries = new Map<string, string>();\n\t\tconst typeEntries = new Map<string, MetricType>();\n\n\t\tfor (const metric of metrics) {\n\t\t\tif (!helpEntries.has(metric.name)) {\n\t\t\t\tconst helpText = metric.help ?? `${metric.name} metric`;\n\t\t\t\tlines.push(`# HELP ${metric.name} ${helpText}`);\n\t\t\t\thelpEntries.set(metric.name, helpText);\n\t\t\t}\n\t\t\tif (!typeEntries.has(metric.name)) {\n\t\t\t\tlines.push(`# TYPE ${metric.name} ${metric.type}`);\n\t\t\t\ttypeEntries.set(metric.name, metric.type);\n\t\t\t}\n\n\t\t\tconst labelStr = this._formatLabels(metric.labels);\n\t\t\tlines.push(`${metric.name}{${labelStr}} ${metric.value}`);\n\t\t}\n\n\t\tfor (const histogram of histograms) {\n\t\t\tconst { name: fullName, labels } = histogram;\n\t\t\tif (histogram.count > 0) {\n\t\t\t\tif (!typeEntries.has(fullName)) {\n\t\t\t\t\tlines.push(`# TYPE ${fullName} histogram`);\n\t\t\t\t\ttypeEntries.set(fullName, MetricType.Histogram);\n\t\t\t\t}\n\n\t\t\t\tconst labelStr = this._formatLabels(labels);\n\t\t\t\tconst formatMetricLine = (suffix: string, value: number): string => {\n\t\t\t\t\tif (labelStr.length === 0) {\n\t\t\t\t\t\treturn `${fullName}${suffix} ${value}`;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn `${fullName}${suffix}{${labelStr}} ${value}`;\n\t\t\t\t};\n\n\t\t\t\tlines.push(formatMetricLine('_sum', histogram.sum));\n\t\t\t\tlines.push(formatMetricLine('_count', histogram.count));\n\n\t\t\t\tfor (const [boundary, count] of histogram.buckets.entries()) {\n\t\t\t\t\tconst boundaryLabel =\n\t\t\t\t\t\tboundary === Infinity\n\t\t\t\t\t\t\t? '+Inf'\n\t\t\t\t\t\t\t: Number.isFinite(boundary)\n\t\t\t\t\t\t\t\t? String(boundary)\n\t\t\t\t\t\t\t\t: String(boundary);\n\t\t\t\t\tconst boundaryLabelStr = this._formatLabels({ le: boundaryLabel });\n\t\t\t\t\tconst bucketLabels =\n\t\t\t\t\t\tlabelStr.length === 0 ? boundaryLabelStr : `${labelStr},${boundaryLabelStr}`;\n\t\t\t\t\tlines.push(`${fullName}_bucket{${bucketLabels}} ${count}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn lines.join('\\n');\n\t}\n\n\t/**\n\t * Gets metric name with prefix.\n\t * @param name - Metric name\n\t * @returns Full metric name\n\t * @private\n\t */\n\tprivate _fullName(name: string): string {\n\t\tif (!this._prefix) {\n\t\t\treturn name;\n\t\t}\n\n\t\tconst prefix = `${this._prefix}_`;\n\t\treturn name.startsWith(prefix) ? name : `${prefix}${name}`;\n\t}\n\n\t/**\n\t * Creates a unique key for a metric with labels.\n\t * @param name - Metric name\n\t * @param labels - Labels\n\t * @returns Unique key\n\t * @private\n\t */\n\tprivate _metricKey(name: string, labels: Record<string, string>): string {\n\t\tconst sortedLabels = Object.entries(labels).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n\t\treturn JSON.stringify([name, sortedLabels]);\n\t}\n\n\tprivate _formatLabels(labels: Record<string, string>): string {\n\t\treturn Object.entries(labels)\n\t\t\t.map(([key, value]) => `${key}=\"${this._escapeLabelValue(value)}\"`)\n\t\t\t.join(',');\n\t}\n\n\tprivate _escapeLabelValue(value: string): string {\n\t\treturn value.replaceAll('\\\\', '\\\\\\\\').replaceAll('\"', '\\\\\"').replaceAll('\\n', '\\\\n');\n\t}\n}\n"],"names":["MetricType","DEFAULT_BUCKETS","Metrics","options","Map","name","value","labels","help","fullName","allLabels","key","existing","buckets","histogram","boundary","Infinity","histogramData","lines","metrics","Array","histograms","helpEntries","typeEntries","metric","helpText","labelStr","formatMetricLine","suffix","count","boundaryLabel","Number","String","boundaryLabelStr","bucketLabels","prefix","sortedLabels","Object","a","b","JSON"],"mappings":"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"}
|
|
@@ -61,7 +61,8 @@ export declare class Session {
|
|
|
61
61
|
private _closePromise;
|
|
62
62
|
private _activeOperationCount;
|
|
63
63
|
private _operationDrain;
|
|
64
|
-
|
|
64
|
+
private readonly _onTimeout;
|
|
65
|
+
constructor(id: SessionId, server: SessionServer, timeout: number, logger: Logger, onTimeout?: ((sessionId: SessionId) => void) | null);
|
|
65
66
|
/**
|
|
66
67
|
* Check if the session is active.
|
|
67
68
|
*/
|
|
@@ -102,6 +103,7 @@ export declare class Session {
|
|
|
102
103
|
*/
|
|
103
104
|
export declare class ConnectionPool implements IConnectionPool {
|
|
104
105
|
private _sessions;
|
|
106
|
+
private readonly _ownedSessions;
|
|
105
107
|
private readonly _closingSessions;
|
|
106
108
|
private _createSessionLock;
|
|
107
109
|
private _maxSessions;
|
|
@@ -112,9 +114,8 @@ export declare class ConnectionPool implements IConnectionPool {
|
|
|
112
114
|
private _terminated;
|
|
113
115
|
private _logger;
|
|
114
116
|
private _serverFactory;
|
|
115
|
-
private
|
|
117
|
+
private _terminationGeneration;
|
|
116
118
|
private readonly _admissionContext;
|
|
117
|
-
private readonly _pendingCreateStopFailures;
|
|
118
119
|
constructor(options?: SessionOptions);
|
|
119
120
|
/**
|
|
120
121
|
* Create a no-op logger when none is provided.
|
|
@@ -145,6 +146,8 @@ export declare class ConnectionPool implements IConnectionPool {
|
|
|
145
146
|
* @throws Error if session not found
|
|
146
147
|
*/
|
|
147
148
|
closeSession(sessionId: SessionId): Promise<void>;
|
|
149
|
+
private _coordinateCleanup;
|
|
150
|
+
private _handleSessionTimeout;
|
|
148
151
|
/**
|
|
149
152
|
* Get information about a session.
|
|
150
153
|
*
|
|
@@ -174,6 +177,7 @@ export declare class ConnectionPool implements IConnectionPool {
|
|
|
174
177
|
* Close all sessions and stop the cleanup timer.
|
|
175
178
|
*/
|
|
176
179
|
terminate(): Promise<void>;
|
|
180
|
+
private _completeTerminationGeneration;
|
|
177
181
|
/**
|
|
178
182
|
* Dispose of the connection pool, releasing all resources.
|
|
179
183
|
* Implements the IDisposable interface.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionPool.d.ts","sourceRoot":"","sources":["../../src/pool/ConnectionPool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"ConnectionPool.d.ts","sourceRoot":"","sources":["../../src/pool/ConnectionPool.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,7 +15,8 @@ class Session {
|
|
|
15
15
|
_closePromise = null;
|
|
16
16
|
_activeOperationCount = 0;
|
|
17
17
|
_operationDrain = null;
|
|
18
|
-
|
|
18
|
+
_onTimeout;
|
|
19
|
+
constructor(id, server, timeout, logger, onTimeout = null){
|
|
19
20
|
this._server = server;
|
|
20
21
|
this._id = id;
|
|
21
22
|
this._createdAt = Date.now();
|
|
@@ -23,6 +24,7 @@ class Session {
|
|
|
23
24
|
this._isActiveValue = true;
|
|
24
25
|
this._timeout = timeout;
|
|
25
26
|
this._logger = logger;
|
|
27
|
+
this._onTimeout = onTimeout;
|
|
26
28
|
this._startTimeout();
|
|
27
29
|
}
|
|
28
30
|
get isActive() {
|
|
@@ -72,6 +74,9 @@ class Session {
|
|
|
72
74
|
if (this._closePromise) return this._closePromise;
|
|
73
75
|
const completion = Promise.withResolvers();
|
|
74
76
|
this._closePromise = completion.promise;
|
|
77
|
+
completion.promise.then(void 0, ()=>{
|
|
78
|
+
if (this._closePromise === completion.promise) this._closePromise = null;
|
|
79
|
+
});
|
|
75
80
|
this._isActiveValue = false;
|
|
76
81
|
if (this._cleanupTimer) {
|
|
77
82
|
clearTimeout(this._cleanupTimer);
|
|
@@ -98,7 +103,8 @@ class Session {
|
|
|
98
103
|
this._cleanupTimer = setTimeout(()=>{
|
|
99
104
|
if (this.isTimedOut()) {
|
|
100
105
|
this._logger.warn(`Session ${this._id} timed out, closing`);
|
|
101
|
-
this.
|
|
106
|
+
if (this._onTimeout) this._onTimeout(this._id);
|
|
107
|
+
else this.close().catch((err)=>{
|
|
102
108
|
this._logger.error(`Error closing timed out session ${this._id}:`, err);
|
|
103
109
|
});
|
|
104
110
|
}
|
|
@@ -110,6 +116,7 @@ class Session {
|
|
|
110
116
|
}
|
|
111
117
|
class ConnectionPool {
|
|
112
118
|
_sessions = new Map();
|
|
119
|
+
_ownedSessions = new Map();
|
|
113
120
|
_closingSessions = new Map();
|
|
114
121
|
_createSessionLock = null;
|
|
115
122
|
_maxSessions;
|
|
@@ -120,9 +127,8 @@ class ConnectionPool {
|
|
|
120
127
|
_terminated = false;
|
|
121
128
|
_logger;
|
|
122
129
|
_serverFactory;
|
|
123
|
-
|
|
130
|
+
_terminationGeneration = null;
|
|
124
131
|
_admissionContext = new AsyncLocalStorage();
|
|
125
|
-
_pendingCreateStopFailures = new Set();
|
|
126
132
|
constructor(options = {}){
|
|
127
133
|
this._maxSessions = options.maxSessions ?? 100;
|
|
128
134
|
this._sessionTimeout = options.sessionTimeout ?? 300000;
|
|
@@ -145,7 +151,7 @@ class ConnectionPool {
|
|
|
145
151
|
async createSession() {
|
|
146
152
|
while(this._createSessionLock)await this._createSessionLock;
|
|
147
153
|
if (this._terminated) throw new PoolTerminatedError();
|
|
148
|
-
if (this.
|
|
154
|
+
if (this._ownedSessions.size >= this._maxSessions) throw new MaxSessionsReachedError(this._maxSessions);
|
|
149
155
|
if (!this._serverFactory) throw new Error('ConnectionPool requires a serverFactory option to create sessions');
|
|
150
156
|
let resolveLock;
|
|
151
157
|
this._createSessionLock = new Promise((resolve)=>{
|
|
@@ -153,17 +159,16 @@ class ConnectionPool {
|
|
|
153
159
|
});
|
|
154
160
|
try {
|
|
155
161
|
const sessionId = asSessionId(`session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`);
|
|
162
|
+
if (this._ownedSessions.has(sessionId)) throw new Error(`Session ID collision: ${sessionId}`);
|
|
156
163
|
const server = await this._serverFactory();
|
|
164
|
+
const session = new Session(sessionId, server, this._sessionTimeout, this._logger, (timedOutSessionId)=>this._handleSessionTimeout(timedOutSessionId));
|
|
165
|
+
this._ownedSessions.set(sessionId, session);
|
|
157
166
|
if (this._terminated) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this._pendingCreateStopFailures.add(error);
|
|
162
|
-
throw error;
|
|
163
|
-
}
|
|
167
|
+
const generation = this._terminationGeneration;
|
|
168
|
+
if (generation) await this._coordinateCleanup(sessionId, generation);
|
|
169
|
+
else await this._coordinateCleanup(sessionId);
|
|
164
170
|
throw new PoolTerminatedError();
|
|
165
171
|
}
|
|
166
|
-
const session = new Session(sessionId, server, this._sessionTimeout, this._logger);
|
|
167
172
|
this._sessions.set(sessionId, session);
|
|
168
173
|
this._logger.info(`Created session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`);
|
|
169
174
|
return sessionId;
|
|
@@ -195,7 +200,7 @@ class ConnectionPool {
|
|
|
195
200
|
value: await operation(activeFrame.session)
|
|
196
201
|
}))();
|
|
197
202
|
const session = this._sessions.get(sessionId);
|
|
198
|
-
if (!session) return Promise.resolve(this.
|
|
203
|
+
if (!session) return Promise.resolve(this._ownedSessions.has(sessionId) ? {
|
|
199
204
|
status: 'inactive'
|
|
200
205
|
} : {
|
|
201
206
|
status: 'missing'
|
|
@@ -216,20 +221,35 @@ class ConnectionPool {
|
|
|
216
221
|
});
|
|
217
222
|
}
|
|
218
223
|
closeSession(sessionId) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
224
|
+
if (!this._ownedSessions.has(sessionId)) return Promise.reject(new SessionNotFoundError(sessionId));
|
|
225
|
+
const generation = this._terminationGeneration;
|
|
226
|
+
return generation ? this._coordinateCleanup(sessionId, generation) : this._coordinateCleanup(sessionId);
|
|
227
|
+
}
|
|
228
|
+
_coordinateCleanup(sessionId, generation) {
|
|
229
|
+
const generationAttempt = generation?.attempts.get(sessionId);
|
|
230
|
+
if (generationAttempt) return generationAttempt;
|
|
231
|
+
const session = this._ownedSessions.get(sessionId);
|
|
222
232
|
if (!session) return Promise.reject(new SessionNotFoundError(sessionId));
|
|
223
233
|
this._sessions.delete(sessionId);
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
234
|
+
const currentAttempt = this._closingSessions.get(sessionId);
|
|
235
|
+
const attempt = currentAttempt ?? session.close();
|
|
236
|
+
if (!currentAttempt) this._closingSessions.set(sessionId, attempt);
|
|
237
|
+
generation?.attempts.set(sessionId, attempt);
|
|
238
|
+
if (!currentAttempt) attempt.then(()=>{
|
|
239
|
+
if (this._closingSessions.get(sessionId) === attempt) this._closingSessions.delete(sessionId);
|
|
240
|
+
if (this._ownedSessions.get(sessionId) === session) this._ownedSessions.delete(sessionId);
|
|
228
241
|
this._logger.info(`Closed session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`);
|
|
229
242
|
}, ()=>{
|
|
230
|
-
this._closingSessions.delete(sessionId);
|
|
243
|
+
if (this._closingSessions.get(sessionId) === attempt) this._closingSessions.delete(sessionId);
|
|
244
|
+
});
|
|
245
|
+
return attempt;
|
|
246
|
+
}
|
|
247
|
+
_handleSessionTimeout(sessionId) {
|
|
248
|
+
const generation = this._terminationGeneration;
|
|
249
|
+
const attempt = generation ? this._coordinateCleanup(sessionId, generation) : this._coordinateCleanup(sessionId);
|
|
250
|
+
attempt.catch((err)=>{
|
|
251
|
+
this._logger.error(`Error closing timed out session ${sessionId}:`, err);
|
|
231
252
|
});
|
|
232
|
-
return closePromise;
|
|
233
253
|
}
|
|
234
254
|
getSessionInfo(sessionId) {
|
|
235
255
|
return this._sessions.get(sessionId)?.getInfo();
|
|
@@ -255,8 +275,8 @@ class ConnectionPool {
|
|
|
255
275
|
}
|
|
256
276
|
_cleanupTimedOutSessions() {
|
|
257
277
|
let cleaned = 0;
|
|
258
|
-
for (const [sessionId, session] of this.
|
|
259
|
-
this.
|
|
278
|
+
for (const [sessionId, session] of this._ownedSessions.entries())if (session.isTimedOut()) {
|
|
279
|
+
this._coordinateCleanup(sessionId).catch((err)=>{
|
|
260
280
|
this._logger.error(`Error closing timed out session ${sessionId}:`, err);
|
|
261
281
|
});
|
|
262
282
|
cleaned++;
|
|
@@ -264,30 +284,35 @@ class ConnectionPool {
|
|
|
264
284
|
if (cleaned > 0) this._logger.info(`Cleaned ${cleaned} timed-out sessions (${this._sessions.size}/${this._maxSessions} active sessions)`);
|
|
265
285
|
}
|
|
266
286
|
terminate() {
|
|
267
|
-
if (this.
|
|
287
|
+
if (this._terminationGeneration) return this._terminationGeneration.completion.promise;
|
|
268
288
|
const completion = Promise.withResolvers();
|
|
269
|
-
|
|
289
|
+
const generation = {
|
|
290
|
+
attempts: new Map(),
|
|
291
|
+
completion
|
|
292
|
+
};
|
|
293
|
+
this._terminationGeneration = generation;
|
|
270
294
|
this._terminated = true;
|
|
271
295
|
if (null !== this._cleanupTimerId) {
|
|
272
296
|
clearInterval(this._cleanupTimerId);
|
|
273
297
|
this._cleanupTimerId = null;
|
|
274
298
|
}
|
|
275
|
-
const
|
|
276
|
-
this.
|
|
277
|
-
|
|
278
|
-
if (this._createSessionLock) closePromises.add(this._createSessionLock);
|
|
279
|
-
for (const session of sessions)closePromises.add(session.close());
|
|
280
|
-
Promise.allSettled(closePromises).then((outcomes)=>{
|
|
281
|
-
const failures = [
|
|
282
|
-
...this._pendingCreateStopFailures,
|
|
283
|
-
...outcomes.filter((outcome)=>'rejected' === outcome.status).map((outcome)=>outcome.reason)
|
|
284
|
-
];
|
|
285
|
-
if (failures.length > 0) return void completion.reject(new AggregateError(failures, 'ConnectionPool termination failed'));
|
|
286
|
-
this._logger.info('ConnectionPool terminated');
|
|
287
|
-
completion.resolve();
|
|
288
|
-
});
|
|
299
|
+
const pendingCreate = this._createSessionLock;
|
|
300
|
+
for (const sessionId of this._ownedSessions.keys())this._coordinateCleanup(sessionId, generation);
|
|
301
|
+
this._completeTerminationGeneration(generation, pendingCreate);
|
|
289
302
|
return completion.promise;
|
|
290
303
|
}
|
|
304
|
+
async _completeTerminationGeneration(generation, pendingCreate) {
|
|
305
|
+
if (pendingCreate) await pendingCreate;
|
|
306
|
+
const outcomes = await Promise.allSettled(generation.attempts.values());
|
|
307
|
+
const failures = outcomes.filter((outcome)=>'rejected' === outcome.status).map((outcome)=>outcome.reason);
|
|
308
|
+
if (failures.length > 0) {
|
|
309
|
+
if (this._terminationGeneration === generation) this._terminationGeneration = null;
|
|
310
|
+
generation.completion.reject(new AggregateError(failures, 'ConnectionPool termination failed'));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
this._logger.info('ConnectionPool terminated');
|
|
314
|
+
generation.completion.resolve();
|
|
315
|
+
}
|
|
291
316
|
dispose() {
|
|
292
317
|
return this.terminate();
|
|
293
318
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool/ConnectionPool.js","sources":["../../src/pool/ConnectionPool.ts"],"sourcesContent":["/**\n * Connection Pool for managing concurrent user sessions.\n *\n * This module provides session management for multi-user scenarios,\n * allowing multiple concurrent clients to each have isolated state.\n *\n * @example\n * ```typescript\n * const pool = new ConnectionPool({\n * maxSessions: 100,\n * sessionTimeout: 300000 // 5 minutes\n * });\n *\n * const sessionId = await pool.createSession();\n * await pool.process(sessionId, thought);\n * await pool.closeSession(sessionId);\n * ```\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n\tMaxSessionsReachedError,\n\tPoolTerminatedError,\n} from '../errors.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport { asSessionId, type SessionId } from '../contracts/ids.js';\nimport { assertNever } from '../utils.js';\nimport { SessionNotActiveError, SessionNotFoundError } from './PoolErrors.js';\nimport type {\n\tConnectionPoolStats,\n\tIConnectionPool,\n\tProcessResult,\n\tSessionInfo,\n\tSessionRunResult,\n\tSessionServer,\n\tSessionThoughtInput,\n} from './IConnectionPool.js';\n\nexport interface SessionOptions {\n\t/**\n\t * Maximum number of concurrent sessions\n\t * @default 100\n\t */\n\tmaxSessions?: number;\n\n\t/**\n\t * Logger instance\n\t */\n\tlogger?: Logger;\n\n\tserverFactory?: () => Promise<SessionServer>;\n\n\t/**\n\t * Session timeout in milliseconds\n\t * @default 300000 (5 minutes)\n\t */\n\tsessionTimeout?: number;\n\n\t/**\n\t * Whether to enable automatic session cleanup\n\t * @default true\n\t */\n\tautoCleanup?: boolean;\n\n\t/**\n\t * Cleanup interval in milliseconds\n\t * @default 60000 (1 minute)\n\t */\n\tcleanupInterval?: number;\n}\n\n/**\n * Represents a user session with its own server instance.\n */\nexport class Session {\n\tprivate _server: SessionServer;\n\tprivate _id: SessionId;\n\tprivate _createdAt: number;\n\tprivate _lastActivityAt: number;\n\tprivate _isActiveValue: boolean;\n\tprivate _timeout: number;\n\tprivate _cleanupTimer: NodeJS.Timeout | null = null;\n\tprivate _logger: Logger;\n\tprivate _closePromise: Promise<void> | null = null;\n\tprivate _activeOperationCount = 0;\n\tprivate _operationDrain: PromiseWithResolvers<void> | null = null;\n\n\tconstructor(id: SessionId, server: SessionServer, timeout: number, logger: Logger) {\n\t\tthis._server = server;\n\t\tthis._id = id;\n\t\tthis._createdAt = Date.now();\n\t\tthis._lastActivityAt = this._createdAt;\n\t\tthis._isActiveValue = true;\n\t\tthis._timeout = timeout;\n\t\tthis._logger = logger;\n\n\t\t// Start session timeout timer\n\t\tthis._startTimeout();\n\t}\n\n\t/**\n\t * Check if the session is active.\n\t */\n\tget isActive(): boolean {\n\t\treturn this._isActiveValue;\n\t}\n\n\t/**\n\t * Process a thought through this session's server instance.\n\t */\n\tasync process(input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tif (!this.isActive) {\n\t\t\tthrow new SessionNotActiveError(this._id);\n\t\t}\n\n\t\t// Update last activity\n\t\tthis._lastActivityAt = Date.now();\n\n\t\t// Reset timeout timer\n\t\tthis._resetTimeout();\n\n\t\t// Process the thought\n\t\treturn this._server.processThought(input);\n\t}\n\n\tprocessAdmitted(input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tthis._lastActivityAt = Date.now();\n\t\tif (this.isActive) this._resetTimeout();\n\t\treturn this._server.processThought(input);\n\t}\n\n\trunWhileActive<T>(operation: (session: Session) => Promise<T>): Promise<SessionRunResult<T>> {\n\t\tif (!this.isActive) return Promise.resolve({ status: 'inactive' });\n\t\tthis._activeOperationCount++;\n\t\treturn (async () => {\n\t\t\ttry {\n\t\t\t\treturn { status: 'completed', value: await operation(this) };\n\t\t\t} finally {\n\t\t\t\tthis._activeOperationCount--;\n\t\t\t\tif (this._activeOperationCount === 0) this._operationDrain?.resolve();\n\t\t\t}\n\t\t})();\n\t}\n\n\t/**\n\t * Get session information.\n\t */\n\tgetInfo(): SessionInfo {\n\t\treturn {\n\t\t\tid: this._id,\n\t\t\tserver: this._server,\n\t\t\tcreatedAt: this._createdAt,\n\t\t\tlastActivityAt: this._lastActivityAt,\n\t\t\tisActive: this.isActive,\n\t\t};\n\t}\n\n\t/**\n\t * Check if the session has timed out.\n\t */\n\tisTimedOut(): boolean {\n\t\treturn Date.now() - this._lastActivityAt > this._timeout;\n\t}\n\n\t/**\n\t * Close the session and stop the server.\n\t */\n\tclose(): Promise<void> {\n\t\tif (this._closePromise) {\n\t\t\treturn this._closePromise;\n\t\t}\n\n\t\tconst completion = Promise.withResolvers<void>();\n\t\tthis._closePromise = completion.promise;\n\t\tthis._isActiveValue = false;\n\n\t\t// Stop timeout timer\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t\tthis._cleanupTimer = null;\n\t\t}\n\n\t\tif (this._activeOperationCount === 0) {\n\t\t\tthis._stopServer(completion);\n\t\t} else {\n\t\t\tthis._operationDrain = Promise.withResolvers<void>();\n\t\t\tvoid this._operationDrain.promise.then(() => this._stopServer(completion));\n\t\t}\n\n\t\treturn completion.promise;\n\t}\n\n\tprivate _stopServer(completion: PromiseWithResolvers<void>): void {\n\t\ttry {\n\t\t\tPromise.resolve(this._server.stop()).then(completion.resolve, completion.reject);\n\t\t} catch (error) {\n\t\t\tcompletion.reject(\n\t\t\t\terror instanceof Error ? error : new AggregateError([error], 'Session stop failed')\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Start the session timeout timer.\n\t */\n\tprivate _startTimeout(): void {\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t}\n\n\t\tthis._cleanupTimer = setTimeout(() => {\n\t\t\tif (this.isTimedOut()) {\n\t\t\t\tthis._logger.warn(`Session ${this._id} timed out, closing`);\n\t\t\t\tthis.close().catch((err) => {\n\t\t\t\t\tthis._logger.error(`Error closing timed out session ${this._id}:`, err);\n\t\t\t\t});\n\t\t\t}\n\t\t}, this._timeout);\n\t}\n\n\t/**\n\t * Reset the timeout timer after activity.\n\t */\n\tprivate _resetTimeout(): void {\n\t\tthis._startTimeout();\n\t}\n}\n\n/**\n * ConnectionPool manages multiple concurrent user sessions.\n *\n * Each session has its own server instance with isolated state,\n * allowing multiple users to interact with the system simultaneously.\n */\nexport class ConnectionPool implements IConnectionPool {\n\tprivate _sessions: Map<SessionId, Session> = new Map();\n\tprivate readonly _closingSessions = new Map<SessionId, Promise<void>>();\n\tprivate _createSessionLock: Promise<void> | null = null;\n\tprivate _maxSessions: number;\n\tprivate _sessionTimeout: number;\n\tprivate _autoCleanup: boolean;\n\tprivate _cleanupInterval: number;\n\tprivate _cleanupTimerId: ReturnType<typeof setInterval> | null = null;\n\tprivate _terminated: boolean = false;\n\tprivate _logger: Logger;\n\tprivate _serverFactory: (() => Promise<SessionServer>) | null;\n\tprivate _terminatePromise: Promise<void> | null = null;\n\tprivate readonly _admissionContext = new AsyncLocalStorage<{\n\t\treadonly sessionId: SessionId;\n\t\treadonly session: Session;\n\t\topen: boolean;\n\t}>();\n\tprivate readonly _pendingCreateStopFailures = new Set<unknown>();\n\n\tconstructor(options: SessionOptions = {}) {\n\t\tthis._maxSessions = options.maxSessions ?? 100;\n\t\tthis._sessionTimeout = options.sessionTimeout ?? 300000; // 5 minutes\n\t\tthis._autoCleanup = options.autoCleanup ?? true;\n\t\tthis._cleanupInterval = options.cleanupInterval ?? 60000; // 1 minute\n\t\tthis._serverFactory = options.serverFactory ?? null;\n\t\tthis._logger = options.logger ?? this._createNoopLogger();\n\n\t\tif (this._autoCleanup) {\n\t\t\tthis._startCleanup();\n\t\t}\n\t}\n\n\t/**\n\t * Create a no-op logger when none is provided.\n\t */\n\tprivate _createNoopLogger(): Logger {\n\t\treturn {\n\t\t\tinfo: (): void => {},\n\t\t\twarn: (): void => {},\n\t\t\terror: (): void => {},\n\t\t\tdebug: (): void => {},\n\t\t\tsetLevel: (): void => {},\n\t\t\tgetLevel: (): 'info' => 'info',\n\t\t};\n\t}\n\n\t/**\n\t * Create a new session.\n\t *\n\t * @returns The session ID\n\t * @throws Error if max sessions reached\n\t */\n\tasync createSession(): Promise<SessionId> {\n\t\twhile (this._createSessionLock) {\n\t\t\tawait this._createSessionLock;\n\t\t}\n\n\t\tif (this._terminated) {\n\t\t\tthrow new PoolTerminatedError();\n\t\t}\n\n\t\tif (this._sessions.size >= this._maxSessions) {\n\t\t\tthrow new MaxSessionsReachedError(this._maxSessions);\n\t\t}\n\n\t\tif (!this._serverFactory) {\n\t\t\tthrow new Error('ConnectionPool requires a serverFactory option to create sessions');\n\t\t}\n\n\t\tlet resolveLock!: () => void;\n\t\tthis._createSessionLock = new Promise<void>((resolve) => {\n\t\t\tresolveLock = resolve;\n\t\t});\n\n\t\ttry {\n\t\t\t// Generate unique session ID\n\t\t\tconst sessionId = asSessionId(\n\t\t\t\t`session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`\n\t\t\t);\n\n\t\t\t// Create a new server instance for this session\n\t\t\tconst server = await this._serverFactory();\n\t\t\tif (this._terminated) {\n\t\t\t\ttry {\n\t\t\t\t\tawait server.stop();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis._pendingCreateStopFailures.add(error);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tthrow new PoolTerminatedError();\n\t\t\t}\n\n\t\t\t// Create session\n\t\t\tconst session = new Session(sessionId, server, this._sessionTimeout, this._logger);\n\t\t\tthis._sessions.set(sessionId, session);\n\n\t\t\tthis._logger.info(\n\t\t\t\t`Created session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t\treturn sessionId;\n\t\t} finally {\n\t\t\tresolveLock();\n\t\t\tthis._createSessionLock = null;\n\t\t}\n\t}\n\n\t/**\n\t * Process a thought in the specified session.\n\t *\n\t * @param sessionId - The session ID\n\t * @param input - The thought data to process\n\t * @returns Promise with the processing result\n\t * @throws Error if session not found\n\t */\n\tasync process(sessionId: SessionId, input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tconst result = await this._runWithSession(sessionId, (session) =>\n\t\t\tsession.processAdmitted(input)\n\t\t);\n\t\tswitch (result.status) {\n\t\t\tcase 'completed':\n\t\t\t\treturn result.value;\n\t\t\tcase 'inactive':\n\t\t\t\tthrow new SessionNotActiveError(sessionId);\n\t\t\tcase 'missing':\n\t\t\t\tthrow new SessionNotFoundError(sessionId);\n\t\t\tdefault:\n\t\t\t\treturn assertNever(result);\n\t\t}\n\t}\n\n\trunWithSession<T>(\n\t\tsessionId: SessionId,\n\t\toperation: (session: SessionServer) => Promise<T>\n\t): Promise<SessionRunResult<T>> {\n\t\treturn this._runWithSession(sessionId, (session) => operation(session.getInfo().server));\n\t}\n\n\tprivate _runWithSession<T>(\n\t\tsessionId: SessionId,\n\t\toperation: (session: Session) => Promise<T>\n\t): Promise<SessionRunResult<T>> {\n\t\tconst activeFrame = this._admissionContext.getStore();\n\t\tif (activeFrame?.open === true && activeFrame.sessionId === sessionId) {\n\t\t\treturn (async () => ({ status: 'completed', value: await operation(activeFrame.session) }))();\n\t\t}\n\n\t\tconst session = this._sessions.get(sessionId);\n\t\tif (!session) {\n\t\t\treturn Promise.resolve(\n\t\t\t\tthis._closingSessions.has(sessionId) ? { status: 'inactive' } : { status: 'missing' }\n\t\t\t);\n\t\t}\n\n\t\treturn session.runWhileActive((capturedSession) => {\n\t\t\tconst frame = { sessionId, session: capturedSession, open: true };\n\t\t\treturn this._admissionContext.run(frame, async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn await operation(capturedSession);\n\t\t\t\t} finally {\n\t\t\t\t\tframe.open = false;\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Close a session and release resources.\n\t *\n\t * @param sessionId - The session ID to close\n\t * @throws Error if session not found\n\t */\n\tcloseSession(sessionId: SessionId): Promise<void> {\n\t\tconst closing = this._closingSessions.get(sessionId);\n\t\tif (closing) {\n\t\t\treturn closing;\n\t\t}\n\n\t\tconst session = this._sessions.get(sessionId);\n\n\t\tif (!session) {\n\t\t\treturn Promise.reject(new SessionNotFoundError(sessionId));\n\t\t}\n\n\t\tthis._sessions.delete(sessionId);\n\t\tconst closePromise = session.close();\n\t\tthis._closingSessions.set(sessionId, closePromise);\n\t\tvoid closePromise.then(\n\t\t\t() => {\n\t\t\t\tthis._closingSessions.delete(sessionId);\n\t\t\t\tthis._logger.info(\n\t\t\t\t\t`Closed session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t\t);\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tthis._closingSessions.delete(sessionId);\n\t\t\t}\n\t\t);\n\t\treturn closePromise;\n\t}\n\n\t/**\n\t * Get information about a session.\n\t *\n\t * @param sessionId - The session ID\n\t * @returns Session info or undefined if not found\n\t */\n\tgetSessionInfo(sessionId: SessionId): SessionInfo | undefined {\n\t\treturn this._sessions.get(sessionId)?.getInfo();\n\t}\n\n\t/**\n\t * Get all active sessions.\n\t *\n\t * @returns Array of session information\n\t */\n\tgetActiveSessions(): SessionInfo[] {\n\t\treturn Array.from(this._sessions.values())\n\t\t\t.filter((s) => s.isActive)\n\t\t\t.map((s) => s.getInfo());\n\t}\n\n\t/**\n\t * Get connection pool statistics.\n\t */\n\tgetStats(): ConnectionPoolStats {\n\t\tconst activeSessions = this.getActiveSessions();\n\n\t\treturn {\n\t\t\ttotalSessions: this._sessions.size,\n\t\t\tactiveSessions: activeSessions.length,\n\t\t\tmaxSessions: this._maxSessions,\n\t\t\tcleanupEnabled: this._autoCleanup,\n\t\t\tsessionTimeout: this._sessionTimeout,\n\t\t};\n\t}\n\n\t/**\n\t * Start the automatic cleanup timer.\n\t */\n\tprivate _startCleanup(): void {\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t}\n\n\t\tthis._cleanupTimerId = setInterval(() => {\n\t\t\tthis._cleanupTimedOutSessions();\n\t\t}, this._cleanupInterval);\n\t}\n\n\t/**\n\t * Remove timed-out sessions.\n\t */\n\tprivate _cleanupTimedOutSessions(): void {\n\t\tlet cleaned = 0;\n\n\t\tfor (const [sessionId, session] of this._sessions.entries()) {\n\t\t\tif (session.isTimedOut()) {\n\t\t\t\tthis.closeSession(sessionId).catch((err) => {\n\t\t\t\t\tthis._logger.error(`Error closing timed out session ${sessionId}:`, err);\n\t\t\t\t});\n\t\t\t\tcleaned++;\n\t\t\t}\n\t\t}\n\n\t\tif (cleaned > 0) {\n\t\t\tthis._logger.info(\n\t\t\t\t`Cleaned ${cleaned} timed-out sessions (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Close all sessions and stop the cleanup timer.\n\t */\n\tterminate(): Promise<void> {\n\t\tif (this._terminatePromise) {\n\t\t\treturn this._terminatePromise;\n\t\t}\n\n\t\tconst completion = Promise.withResolvers<void>();\n\t\tthis._terminatePromise = completion.promise;\n\t\tthis._terminated = true;\n\n\t\t// Stop cleanup timer\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t\tthis._cleanupTimerId = null;\n\t\t}\n\n\t\tconst sessions = Array.from(this._sessions.values());\n\t\tthis._sessions.clear();\n\t\tconst closePromises = new Set<Promise<void>>(this._closingSessions.values());\n\t\tif (this._createSessionLock) {\n\t\t\tclosePromises.add(this._createSessionLock);\n\t\t}\n\t\tfor (const session of sessions) {\n\t\t\tclosePromises.add(session.close());\n\t\t}\n\n\t\tvoid Promise.allSettled(closePromises).then((outcomes) => {\n\t\t\tconst failures = [\n\t\t\t\t...this._pendingCreateStopFailures,\n\t\t\t\t...outcomes\n\t\t\t\t\t.filter((outcome): outcome is PromiseRejectedResult => outcome.status === 'rejected')\n\t\t\t\t\t.map((outcome) => outcome.reason),\n\t\t\t];\n\t\t\tif (failures.length > 0) {\n\t\t\t\tcompletion.reject(new AggregateError(failures, 'ConnectionPool termination failed'));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis._logger.info('ConnectionPool terminated');\n\t\t\tcompletion.resolve();\n\t\t});\n\n\t\treturn completion.promise;\n\t}\n\n\t/**\n\t * Dispose of the connection pool, releasing all resources.\n\t * Implements the IDisposable interface.\n\t * Delegates to terminate() for backward compatibility.\n\t */\n\tdispose(): Promise<void> {\n\t\treturn this.terminate();\n\t}\n\n\t/**\n\t * Check if the connection pool is active.\n\t */\n\tisRunning(): boolean {\n\t\treturn !this._terminated;\n\t}\n}\n\n/**\n * Create a connection pool with the given options.\n *\n * @param options - Connection pool configuration\n * @returns A configured connection pool\n *\n * @example\n * ```typescript\n * const pool = createConnectionPool({\n * maxSessions: 50,\n * sessionTimeout: 300000\n * });\n * ```\n */\nexport function createConnectionPool(options?: SessionOptions): ConnectionPool {\n\treturn new ConnectionPool(options);\n}\n"],"names":["Session","id","server","timeout","logger","Date","input","SessionNotActiveError","operation","Promise","completion","clearTimeout","error","Error","AggregateError","setTimeout","err","ConnectionPool","Map","AsyncLocalStorage","Set","options","PoolTerminatedError","MaxSessionsReachedError","resolveLock","resolve","sessionId","asSessionId","Math","session","result","SessionNotFoundError","assertNever","activeFrame","capturedSession","frame","closing","closePromise","Array","s","activeSessions","clearInterval","setInterval","cleaned","sessions","closePromises","outcomes","failures","outcome","createConnectionPool"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pool/ConnectionPool.js","sources":["../../src/pool/ConnectionPool.ts"],"sourcesContent":["/**\n * Connection Pool for managing concurrent user sessions.\n *\n * This module provides session management for multi-user scenarios,\n * allowing multiple concurrent clients to each have isolated state.\n *\n * @example\n * ```typescript\n * const pool = new ConnectionPool({\n * maxSessions: 100,\n * sessionTimeout: 300000 // 5 minutes\n * });\n *\n * const sessionId = await pool.createSession();\n * await pool.process(sessionId, thought);\n * await pool.closeSession(sessionId);\n * ```\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { MaxSessionsReachedError, PoolTerminatedError } from '../errors.js';\nimport type { Logger } from '../logger/StructuredLogger.js';\nimport { asSessionId, type SessionId } from '../contracts/ids.js';\nimport { assertNever } from '../utils.js';\nimport { SessionNotActiveError, SessionNotFoundError } from './PoolErrors.js';\nimport type {\n\tConnectionPoolStats,\n\tIConnectionPool,\n\tProcessResult,\n\tSessionInfo,\n\tSessionRunResult,\n\tSessionServer,\n\tSessionThoughtInput,\n} from './IConnectionPool.js';\n\nexport interface SessionOptions {\n\t/**\n\t * Maximum number of concurrent sessions\n\t * @default 100\n\t */\n\tmaxSessions?: number;\n\n\t/**\n\t * Logger instance\n\t */\n\tlogger?: Logger;\n\n\tserverFactory?: () => Promise<SessionServer>;\n\n\t/**\n\t * Session timeout in milliseconds\n\t * @default 300000 (5 minutes)\n\t */\n\tsessionTimeout?: number;\n\n\t/**\n\t * Whether to enable automatic session cleanup\n\t * @default true\n\t */\n\tautoCleanup?: boolean;\n\n\t/**\n\t * Cleanup interval in milliseconds\n\t * @default 60000 (1 minute)\n\t */\n\tcleanupInterval?: number;\n}\n\n/**\n * Represents a user session with its own server instance.\n */\nexport class Session {\n\tprivate _server: SessionServer;\n\tprivate _id: SessionId;\n\tprivate _createdAt: number;\n\tprivate _lastActivityAt: number;\n\tprivate _isActiveValue: boolean;\n\tprivate _timeout: number;\n\tprivate _cleanupTimer: NodeJS.Timeout | null = null;\n\tprivate _logger: Logger;\n\tprivate _closePromise: Promise<void> | null = null;\n\tprivate _activeOperationCount = 0;\n\tprivate _operationDrain: PromiseWithResolvers<void> | null = null;\n\tprivate readonly _onTimeout: ((sessionId: SessionId) => void) | null;\n\n\tconstructor(\n\t\tid: SessionId,\n\t\tserver: SessionServer,\n\t\ttimeout: number,\n\t\tlogger: Logger,\n\t\tonTimeout: ((sessionId: SessionId) => void) | null = null\n\t) {\n\t\tthis._server = server;\n\t\tthis._id = id;\n\t\tthis._createdAt = Date.now();\n\t\tthis._lastActivityAt = this._createdAt;\n\t\tthis._isActiveValue = true;\n\t\tthis._timeout = timeout;\n\t\tthis._logger = logger;\n\t\tthis._onTimeout = onTimeout;\n\n\t\t// Start session timeout timer\n\t\tthis._startTimeout();\n\t}\n\n\t/**\n\t * Check if the session is active.\n\t */\n\tget isActive(): boolean {\n\t\treturn this._isActiveValue;\n\t}\n\n\t/**\n\t * Process a thought through this session's server instance.\n\t */\n\tasync process(input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tif (!this.isActive) {\n\t\t\tthrow new SessionNotActiveError(this._id);\n\t\t}\n\n\t\t// Update last activity\n\t\tthis._lastActivityAt = Date.now();\n\n\t\t// Reset timeout timer\n\t\tthis._resetTimeout();\n\n\t\t// Process the thought\n\t\treturn this._server.processThought(input);\n\t}\n\n\tprocessAdmitted(input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tthis._lastActivityAt = Date.now();\n\t\tif (this.isActive) this._resetTimeout();\n\t\treturn this._server.processThought(input);\n\t}\n\n\trunWhileActive<T>(operation: (session: Session) => Promise<T>): Promise<SessionRunResult<T>> {\n\t\tif (!this.isActive) return Promise.resolve({ status: 'inactive' });\n\t\tthis._activeOperationCount++;\n\t\treturn (async () => {\n\t\t\ttry {\n\t\t\t\treturn { status: 'completed', value: await operation(this) };\n\t\t\t} finally {\n\t\t\t\tthis._activeOperationCount--;\n\t\t\t\tif (this._activeOperationCount === 0) this._operationDrain?.resolve();\n\t\t\t}\n\t\t})();\n\t}\n\n\t/**\n\t * Get session information.\n\t */\n\tgetInfo(): SessionInfo {\n\t\treturn {\n\t\t\tid: this._id,\n\t\t\tserver: this._server,\n\t\t\tcreatedAt: this._createdAt,\n\t\t\tlastActivityAt: this._lastActivityAt,\n\t\t\tisActive: this.isActive,\n\t\t};\n\t}\n\n\t/**\n\t * Check if the session has timed out.\n\t */\n\tisTimedOut(): boolean {\n\t\treturn Date.now() - this._lastActivityAt > this._timeout;\n\t}\n\n\t/**\n\t * Close the session and stop the server.\n\t */\n\tclose(): Promise<void> {\n\t\tif (this._closePromise) {\n\t\t\treturn this._closePromise;\n\t\t}\n\n\t\tconst completion = Promise.withResolvers<void>();\n\t\tthis._closePromise = completion.promise;\n\t\tvoid completion.promise.then(undefined, () => {\n\t\t\tif (this._closePromise === completion.promise) this._closePromise = null;\n\t\t});\n\t\tthis._isActiveValue = false;\n\n\t\t// Stop timeout timer\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t\tthis._cleanupTimer = null;\n\t\t}\n\n\t\tif (this._activeOperationCount === 0) {\n\t\t\tthis._stopServer(completion);\n\t\t} else {\n\t\t\tthis._operationDrain = Promise.withResolvers<void>();\n\t\t\tvoid this._operationDrain.promise.then(() => this._stopServer(completion));\n\t\t}\n\n\t\treturn completion.promise;\n\t}\n\n\tprivate _stopServer(completion: PromiseWithResolvers<void>): void {\n\t\ttry {\n\t\t\tPromise.resolve(this._server.stop()).then(completion.resolve, completion.reject);\n\t\t} catch (error) {\n\t\t\tcompletion.reject(\n\t\t\t\terror instanceof Error ? error : new AggregateError([error], 'Session stop failed')\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Start the session timeout timer.\n\t */\n\tprivate _startTimeout(): void {\n\t\tif (this._cleanupTimer) {\n\t\t\tclearTimeout(this._cleanupTimer);\n\t\t}\n\n\t\tthis._cleanupTimer = setTimeout(() => {\n\t\t\tif (this.isTimedOut()) {\n\t\t\t\tthis._logger.warn(`Session ${this._id} timed out, closing`);\n\t\t\t\tif (this._onTimeout) {\n\t\t\t\t\tthis._onTimeout(this._id);\n\t\t\t\t} else {\n\t\t\t\t\tthis.close().catch((err) => {\n\t\t\t\t\t\tthis._logger.error(`Error closing timed out session ${this._id}:`, err);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}, this._timeout);\n\t}\n\n\t/**\n\t * Reset the timeout timer after activity.\n\t */\n\tprivate _resetTimeout(): void {\n\t\tthis._startTimeout();\n\t}\n}\n\ninterface TerminationGeneration {\n\treadonly attempts: Map<SessionId, Promise<void>>;\n\treadonly completion: PromiseWithResolvers<void>;\n}\n\n/**\n * ConnectionPool manages multiple concurrent user sessions.\n *\n * Each session has its own server instance with isolated state,\n * allowing multiple users to interact with the system simultaneously.\n */\nexport class ConnectionPool implements IConnectionPool {\n\tprivate _sessions: Map<SessionId, Session> = new Map();\n\tprivate readonly _ownedSessions = new Map<SessionId, Session>();\n\tprivate readonly _closingSessions = new Map<SessionId, Promise<void>>();\n\tprivate _createSessionLock: Promise<void> | null = null;\n\tprivate _maxSessions: number;\n\tprivate _sessionTimeout: number;\n\tprivate _autoCleanup: boolean;\n\tprivate _cleanupInterval: number;\n\tprivate _cleanupTimerId: ReturnType<typeof setInterval> | null = null;\n\tprivate _terminated: boolean = false;\n\tprivate _logger: Logger;\n\tprivate _serverFactory: (() => Promise<SessionServer>) | null;\n\tprivate _terminationGeneration: TerminationGeneration | null = null;\n\tprivate readonly _admissionContext = new AsyncLocalStorage<{\n\t\treadonly sessionId: SessionId;\n\t\treadonly session: Session;\n\t\topen: boolean;\n\t}>();\n\n\tconstructor(options: SessionOptions = {}) {\n\t\tthis._maxSessions = options.maxSessions ?? 100;\n\t\tthis._sessionTimeout = options.sessionTimeout ?? 300000; // 5 minutes\n\t\tthis._autoCleanup = options.autoCleanup ?? true;\n\t\tthis._cleanupInterval = options.cleanupInterval ?? 60000; // 1 minute\n\t\tthis._serverFactory = options.serverFactory ?? null;\n\t\tthis._logger = options.logger ?? this._createNoopLogger();\n\n\t\tif (this._autoCleanup) {\n\t\t\tthis._startCleanup();\n\t\t}\n\t}\n\n\t/**\n\t * Create a no-op logger when none is provided.\n\t */\n\tprivate _createNoopLogger(): Logger {\n\t\treturn {\n\t\t\tinfo: (): void => {},\n\t\t\twarn: (): void => {},\n\t\t\terror: (): void => {},\n\t\t\tdebug: (): void => {},\n\t\t\tsetLevel: (): void => {},\n\t\t\tgetLevel: (): 'info' => 'info',\n\t\t};\n\t}\n\n\t/**\n\t * Create a new session.\n\t *\n\t * @returns The session ID\n\t * @throws Error if max sessions reached\n\t */\n\tasync createSession(): Promise<SessionId> {\n\t\twhile (this._createSessionLock) {\n\t\t\tawait this._createSessionLock;\n\t\t}\n\n\t\tif (this._terminated) {\n\t\t\tthrow new PoolTerminatedError();\n\t\t}\n\n\t\tif (this._ownedSessions.size >= this._maxSessions) {\n\t\t\tthrow new MaxSessionsReachedError(this._maxSessions);\n\t\t}\n\n\t\tif (!this._serverFactory) {\n\t\t\tthrow new Error('ConnectionPool requires a serverFactory option to create sessions');\n\t\t}\n\n\t\tlet resolveLock!: () => void;\n\t\tthis._createSessionLock = new Promise<void>((resolve) => {\n\t\t\tresolveLock = resolve;\n\t\t});\n\n\t\ttry {\n\t\t\t// Generate unique session ID\n\t\t\tconst sessionId = asSessionId(\n\t\t\t\t`session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`\n\t\t\t);\n\t\t\tif (this._ownedSessions.has(sessionId)) {\n\t\t\t\tthrow new Error(`Session ID collision: ${sessionId}`);\n\t\t\t}\n\n\t\t\t// Create a new server instance for this session\n\t\t\tconst server = await this._serverFactory();\n\t\t\tconst session = new Session(\n\t\t\t\tsessionId,\n\t\t\t\tserver,\n\t\t\t\tthis._sessionTimeout,\n\t\t\t\tthis._logger,\n\t\t\t\t(timedOutSessionId) => this._handleSessionTimeout(timedOutSessionId)\n\t\t\t);\n\t\t\tthis._ownedSessions.set(sessionId, session);\n\t\t\tif (this._terminated) {\n\t\t\t\tconst generation = this._terminationGeneration;\n\t\t\t\tif (generation) {\n\t\t\t\t\tawait this._coordinateCleanup(sessionId, generation);\n\t\t\t\t} else {\n\t\t\t\t\tawait this._coordinateCleanup(sessionId);\n\t\t\t\t}\n\t\t\t\tthrow new PoolTerminatedError();\n\t\t\t}\n\n\t\t\tthis._sessions.set(sessionId, session);\n\n\t\t\tthis._logger.info(\n\t\t\t\t`Created session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t\treturn sessionId;\n\t\t} finally {\n\t\t\tresolveLock();\n\t\t\tthis._createSessionLock = null;\n\t\t}\n\t}\n\n\t/**\n\t * Process a thought in the specified session.\n\t *\n\t * @param sessionId - The session ID\n\t * @param input - The thought data to process\n\t * @returns Promise with the processing result\n\t * @throws Error if session not found\n\t */\n\tasync process(sessionId: SessionId, input: SessionThoughtInput): Promise<ProcessResult> {\n\t\tconst result = await this._runWithSession(sessionId, (session) =>\n\t\t\tsession.processAdmitted(input)\n\t\t);\n\t\tswitch (result.status) {\n\t\t\tcase 'completed':\n\t\t\t\treturn result.value;\n\t\t\tcase 'inactive':\n\t\t\t\tthrow new SessionNotActiveError(sessionId);\n\t\t\tcase 'missing':\n\t\t\t\tthrow new SessionNotFoundError(sessionId);\n\t\t\tdefault:\n\t\t\t\treturn assertNever(result);\n\t\t}\n\t}\n\n\trunWithSession<T>(\n\t\tsessionId: SessionId,\n\t\toperation: (session: SessionServer) => Promise<T>\n\t): Promise<SessionRunResult<T>> {\n\t\treturn this._runWithSession(sessionId, (session) => operation(session.getInfo().server));\n\t}\n\n\tprivate _runWithSession<T>(\n\t\tsessionId: SessionId,\n\t\toperation: (session: Session) => Promise<T>\n\t): Promise<SessionRunResult<T>> {\n\t\tconst activeFrame = this._admissionContext.getStore();\n\t\tif (activeFrame?.open === true && activeFrame.sessionId === sessionId) {\n\t\t\treturn (async () => ({ status: 'completed', value: await operation(activeFrame.session) }))();\n\t\t}\n\n\t\tconst session = this._sessions.get(sessionId);\n\t\tif (!session) {\n\t\t\treturn Promise.resolve(\n\t\t\t\tthis._ownedSessions.has(sessionId) ? { status: 'inactive' } : { status: 'missing' }\n\t\t\t);\n\t\t}\n\n\t\treturn session.runWhileActive((capturedSession) => {\n\t\t\tconst frame = { sessionId, session: capturedSession, open: true };\n\t\t\treturn this._admissionContext.run(frame, async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn await operation(capturedSession);\n\t\t\t\t} finally {\n\t\t\t\t\tframe.open = false;\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Close a session and release resources.\n\t *\n\t * @param sessionId - The session ID to close\n\t * @throws Error if session not found\n\t */\n\tcloseSession(sessionId: SessionId): Promise<void> {\n\t\tif (!this._ownedSessions.has(sessionId)) {\n\t\t\treturn Promise.reject(new SessionNotFoundError(sessionId));\n\t\t}\n\n\t\tconst generation = this._terminationGeneration;\n\t\treturn generation\n\t\t\t? this._coordinateCleanup(sessionId, generation)\n\t\t\t: this._coordinateCleanup(sessionId);\n\t}\n\n\tprivate _coordinateCleanup(\n\t\tsessionId: SessionId,\n\t\tgeneration?: TerminationGeneration\n\t): Promise<void> {\n\t\tconst generationAttempt = generation?.attempts.get(sessionId);\n\t\tif (generationAttempt) return generationAttempt;\n\n\t\tconst session = this._ownedSessions.get(sessionId);\n\t\tif (!session) return Promise.reject(new SessionNotFoundError(sessionId));\n\n\t\tthis._sessions.delete(sessionId);\n\t\tconst currentAttempt = this._closingSessions.get(sessionId);\n\t\tconst attempt = currentAttempt ?? session.close();\n\t\tif (!currentAttempt) this._closingSessions.set(sessionId, attempt);\n\t\tgeneration?.attempts.set(sessionId, attempt);\n\n\t\tif (!currentAttempt)\n\t\t\tvoid attempt.then(\n\t\t\t\t() => {\n\t\t\t\t\tif (this._closingSessions.get(sessionId) === attempt) {\n\t\t\t\t\t\tthis._closingSessions.delete(sessionId);\n\t\t\t\t\t}\n\t\t\t\t\tif (this._ownedSessions.get(sessionId) === session) {\n\t\t\t\t\t\tthis._ownedSessions.delete(sessionId);\n\t\t\t\t\t}\n\t\t\t\t\tthis._logger.info(\n\t\t\t\t\t\t`Closed session ${sessionId} (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tif (this._closingSessions.get(sessionId) === attempt) {\n\t\t\t\t\t\tthis._closingSessions.delete(sessionId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\treturn attempt;\n\t}\n\n\tprivate _handleSessionTimeout(sessionId: SessionId): void {\n\t\tconst generation = this._terminationGeneration;\n\t\tconst attempt = generation\n\t\t\t? this._coordinateCleanup(sessionId, generation)\n\t\t\t: this._coordinateCleanup(sessionId);\n\t\tvoid attempt.catch((err) => {\n\t\t\tthis._logger.error(`Error closing timed out session ${sessionId}:`, err);\n\t\t});\n\t}\n\n\t/**\n\t * Get information about a session.\n\t *\n\t * @param sessionId - The session ID\n\t * @returns Session info or undefined if not found\n\t */\n\tgetSessionInfo(sessionId: SessionId): SessionInfo | undefined {\n\t\treturn this._sessions.get(sessionId)?.getInfo();\n\t}\n\n\t/**\n\t * Get all active sessions.\n\t *\n\t * @returns Array of session information\n\t */\n\tgetActiveSessions(): SessionInfo[] {\n\t\treturn Array.from(this._sessions.values())\n\t\t\t.filter((s) => s.isActive)\n\t\t\t.map((s) => s.getInfo());\n\t}\n\n\t/**\n\t * Get connection pool statistics.\n\t */\n\tgetStats(): ConnectionPoolStats {\n\t\tconst activeSessions = this.getActiveSessions();\n\n\t\treturn {\n\t\t\ttotalSessions: this._sessions.size,\n\t\t\tactiveSessions: activeSessions.length,\n\t\t\tmaxSessions: this._maxSessions,\n\t\t\tcleanupEnabled: this._autoCleanup,\n\t\t\tsessionTimeout: this._sessionTimeout,\n\t\t};\n\t}\n\n\t/**\n\t * Start the automatic cleanup timer.\n\t */\n\tprivate _startCleanup(): void {\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t}\n\n\t\tthis._cleanupTimerId = setInterval(() => {\n\t\t\tthis._cleanupTimedOutSessions();\n\t\t}, this._cleanupInterval);\n\t}\n\n\t/**\n\t * Remove timed-out sessions.\n\t */\n\tprivate _cleanupTimedOutSessions(): void {\n\t\tlet cleaned = 0;\n\n\t\tfor (const [sessionId, session] of this._ownedSessions.entries()) {\n\t\t\tif (session.isTimedOut()) {\n\t\t\t\tthis._coordinateCleanup(sessionId).catch((err) => {\n\t\t\t\t\tthis._logger.error(`Error closing timed out session ${sessionId}:`, err);\n\t\t\t\t});\n\t\t\t\tcleaned++;\n\t\t\t}\n\t\t}\n\n\t\tif (cleaned > 0) {\n\t\t\tthis._logger.info(\n\t\t\t\t`Cleaned ${cleaned} timed-out sessions (${this._sessions.size}/${this._maxSessions} active sessions)`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Close all sessions and stop the cleanup timer.\n\t */\n\tterminate(): Promise<void> {\n\t\tif (this._terminationGeneration) {\n\t\t\treturn this._terminationGeneration.completion.promise;\n\t\t}\n\n\t\tconst completion = Promise.withResolvers<void>();\n\t\tconst generation: TerminationGeneration = { attempts: new Map(), completion };\n\t\tthis._terminationGeneration = generation;\n\t\tthis._terminated = true;\n\n\t\t// Stop cleanup timer\n\t\tif (this._cleanupTimerId !== null) {\n\t\t\tclearInterval(this._cleanupTimerId);\n\t\t\tthis._cleanupTimerId = null;\n\t\t}\n\n\t\tconst pendingCreate = this._createSessionLock;\n\t\tfor (const sessionId of this._ownedSessions.keys()) {\n\t\t\tthis._coordinateCleanup(sessionId, generation);\n\t\t}\n\n\t\tvoid this._completeTerminationGeneration(generation, pendingCreate);\n\n\t\treturn completion.promise;\n\t}\n\n\tprivate async _completeTerminationGeneration(\n\t\tgeneration: TerminationGeneration,\n\t\tpendingCreate: Promise<void> | null\n\t): Promise<void> {\n\t\tif (pendingCreate) await pendingCreate;\n\t\tconst outcomes = await Promise.allSettled(generation.attempts.values());\n\t\tconst failures = outcomes\n\t\t\t.filter((outcome): outcome is PromiseRejectedResult => outcome.status === 'rejected')\n\t\t\t.map((outcome) => outcome.reason);\n\t\tif (failures.length > 0) {\n\t\t\tif (this._terminationGeneration === generation) this._terminationGeneration = null;\n\t\t\tgeneration.completion.reject(\n\t\t\t\tnew AggregateError(failures, 'ConnectionPool termination failed')\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tthis._logger.info('ConnectionPool terminated');\n\t\tgeneration.completion.resolve();\n\t}\n\n\t/**\n\t * Dispose of the connection pool, releasing all resources.\n\t * Implements the IDisposable interface.\n\t * Delegates to terminate() for backward compatibility.\n\t */\n\tdispose(): Promise<void> {\n\t\treturn this.terminate();\n\t}\n\n\t/**\n\t * Check if the connection pool is active.\n\t */\n\tisRunning(): boolean {\n\t\treturn !this._terminated;\n\t}\n}\n\n/**\n * Create a connection pool with the given options.\n *\n * @param options - Connection pool configuration\n * @returns A configured connection pool\n *\n * @example\n * ```typescript\n * const pool = createConnectionPool({\n * maxSessions: 50,\n * sessionTimeout: 300000\n * });\n * ```\n */\nexport function createConnectionPool(options?: SessionOptions): ConnectionPool {\n\treturn new ConnectionPool(options);\n}\n"],"names":["Session","id","server","timeout","logger","onTimeout","Date","input","SessionNotActiveError","operation","Promise","completion","undefined","clearTimeout","error","Error","AggregateError","setTimeout","err","ConnectionPool","Map","AsyncLocalStorage","options","PoolTerminatedError","MaxSessionsReachedError","resolveLock","resolve","sessionId","asSessionId","Math","session","timedOutSessionId","generation","result","SessionNotFoundError","assertNever","activeFrame","capturedSession","frame","generationAttempt","currentAttempt","attempt","Array","s","activeSessions","clearInterval","setInterval","cleaned","pendingCreate","outcomes","failures","outcome","createConnectionPool"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iworkforces/tracelattice",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Semantic Sequential Thinking Layer For AI Agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/lib.js",
|
|
@@ -87,14 +87,14 @@
|
|
|
87
87
|
"@eslint/js": "^10.0.1",
|
|
88
88
|
"@rsbuild/core": "^2.2.7",
|
|
89
89
|
"@rslib/core": "^1.0.1",
|
|
90
|
-
"@types/node": "^26.
|
|
90
|
+
"@types/node": "^26.6.1",
|
|
91
91
|
"@typescript-eslint/eslint-plugin": "^8.70.0",
|
|
92
92
|
"@typescript-eslint/parser": "^8.70.0",
|
|
93
93
|
"@vitest/coverage-v8": "^5.0.1",
|
|
94
94
|
"better-sqlite3": "13.0.3",
|
|
95
95
|
"eslint": "^10.10.0",
|
|
96
96
|
"globals": "^17.12.0",
|
|
97
|
-
"prettier": "^3.9.
|
|
97
|
+
"prettier": "^3.9.8",
|
|
98
98
|
"typescript": "^6.0.3",
|
|
99
99
|
"typescript-eslint": "^8.70.0",
|
|
100
100
|
"vitest": "^5.0.1"
|