@karmaniverous/jeeves-watcher 0.2.2 → 0.2.4
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/README.md +14 -0
- package/config.schema.json +22 -0
- package/dist/cjs/index.js +171 -15
- package/dist/cli/jeeves-watcher/index.js +170 -15
- package/dist/index.d.ts +101 -5
- package/dist/index.iife.js +171 -15
- package/dist/index.iife.min.js +1 -1
- package/dist/mjs/index.js +171 -16
- package/package.json +1 -1
package/dist/mjs/index.js
CHANGED
|
@@ -644,6 +644,16 @@ const jeevesWatcherConfigSchema = z.object({
|
|
|
644
644
|
.number()
|
|
645
645
|
.optional()
|
|
646
646
|
.describe('Timeout in milliseconds for graceful shutdown.'),
|
|
647
|
+
/** Maximum consecutive system-level failures before triggering fatal error. Default: Infinity. */
|
|
648
|
+
maxRetries: z
|
|
649
|
+
.number()
|
|
650
|
+
.optional()
|
|
651
|
+
.describe('Maximum consecutive system-level failures before triggering fatal error. Default: Infinity.'),
|
|
652
|
+
/** Maximum backoff delay in milliseconds for system errors. Default: 60000. */
|
|
653
|
+
maxBackoffMs: z
|
|
654
|
+
.number()
|
|
655
|
+
.optional()
|
|
656
|
+
.describe('Maximum backoff delay in milliseconds for system errors. Default: 60000.'),
|
|
647
657
|
});
|
|
648
658
|
|
|
649
659
|
/**
|
|
@@ -656,15 +666,13 @@ const ENV_PATTERN = /\$\{([^}]+)\}/g;
|
|
|
656
666
|
* Replace `${VAR_NAME}` patterns in a string with `process.env.VAR_NAME`.
|
|
657
667
|
*
|
|
658
668
|
* @param value - The string to process.
|
|
659
|
-
* @returns The string with env vars
|
|
660
|
-
* @throws If a referenced env var is not set.
|
|
669
|
+
* @returns The string with resolved env vars; unresolvable expressions left untouched.
|
|
661
670
|
*/
|
|
662
671
|
function substituteString(value) {
|
|
663
672
|
return value.replace(ENV_PATTERN, (match, varName) => {
|
|
664
673
|
const envValue = process.env[varName];
|
|
665
|
-
if (envValue === undefined)
|
|
666
|
-
|
|
667
|
-
}
|
|
674
|
+
if (envValue === undefined)
|
|
675
|
+
return match;
|
|
668
676
|
return envValue;
|
|
669
677
|
});
|
|
670
678
|
}
|
|
@@ -1050,11 +1058,11 @@ async function extractMarkdown(filePath) {
|
|
|
1050
1058
|
}
|
|
1051
1059
|
async function extractPlaintext(filePath) {
|
|
1052
1060
|
const raw = await readFile(filePath, 'utf8');
|
|
1053
|
-
return { text: raw };
|
|
1061
|
+
return { text: raw.replace(/^\uFEFF/, '') };
|
|
1054
1062
|
}
|
|
1055
1063
|
async function extractJson(filePath) {
|
|
1056
1064
|
const raw = await readFile(filePath, 'utf8');
|
|
1057
|
-
const parsed = JSON.parse(raw);
|
|
1065
|
+
const parsed = JSON.parse(raw.replace(/^\uFEFF/, ''));
|
|
1058
1066
|
const json = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
1059
1067
|
? parsed
|
|
1060
1068
|
: undefined;
|
|
@@ -1076,7 +1084,7 @@ async function extractDocx(filePath) {
|
|
|
1076
1084
|
}
|
|
1077
1085
|
async function extractHtml(filePath) {
|
|
1078
1086
|
const raw = await readFile(filePath, 'utf8');
|
|
1079
|
-
const $ = cheerio.load(raw);
|
|
1087
|
+
const $ = cheerio.load(raw.replace(/^\uFEFF/, ''));
|
|
1080
1088
|
$('script, style').remove();
|
|
1081
1089
|
const text = $('body').text().trim() || $.text().trim();
|
|
1082
1090
|
return { text };
|
|
@@ -1906,6 +1914,112 @@ class VectorStoreClient {
|
|
|
1906
1914
|
}
|
|
1907
1915
|
}
|
|
1908
1916
|
|
|
1917
|
+
/**
|
|
1918
|
+
* @module health
|
|
1919
|
+
* Tracks consecutive system-level failures and applies exponential backoff.
|
|
1920
|
+
* Triggers fatal error callback when maxRetries is exceeded.
|
|
1921
|
+
*/
|
|
1922
|
+
/**
|
|
1923
|
+
* Tracks system health via consecutive failure count and exponential backoff.
|
|
1924
|
+
*/
|
|
1925
|
+
class SystemHealth {
|
|
1926
|
+
consecutiveFailures = 0;
|
|
1927
|
+
maxRetries;
|
|
1928
|
+
maxBackoffMs;
|
|
1929
|
+
baseDelayMs;
|
|
1930
|
+
onFatalError;
|
|
1931
|
+
logger;
|
|
1932
|
+
constructor(options) {
|
|
1933
|
+
this.maxRetries = options.maxRetries ?? Number.POSITIVE_INFINITY;
|
|
1934
|
+
this.maxBackoffMs = options.maxBackoffMs ?? 60_000;
|
|
1935
|
+
this.baseDelayMs = options.baseDelayMs ?? 1000;
|
|
1936
|
+
this.onFatalError = options.onFatalError;
|
|
1937
|
+
this.logger = options.logger;
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Record a successful system operation. Resets the failure counter.
|
|
1941
|
+
*/
|
|
1942
|
+
recordSuccess() {
|
|
1943
|
+
if (this.consecutiveFailures > 0) {
|
|
1944
|
+
this.logger.info({ previousFailures: this.consecutiveFailures }, 'System health recovered');
|
|
1945
|
+
}
|
|
1946
|
+
this.consecutiveFailures = 0;
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Record a system-level failure. If maxRetries is exceeded, triggers fatal error.
|
|
1950
|
+
*
|
|
1951
|
+
* @param error - The error that occurred.
|
|
1952
|
+
* @returns Whether the watcher should continue (false = fatal).
|
|
1953
|
+
*/
|
|
1954
|
+
recordFailure(error) {
|
|
1955
|
+
this.consecutiveFailures += 1;
|
|
1956
|
+
this.logger.error({
|
|
1957
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
1958
|
+
maxRetries: this.maxRetries,
|
|
1959
|
+
err: normalizeError(error),
|
|
1960
|
+
}, 'System-level failure recorded');
|
|
1961
|
+
if (this.consecutiveFailures >= this.maxRetries) {
|
|
1962
|
+
this.logger.fatal({ consecutiveFailures: this.consecutiveFailures }, 'Maximum retries exceeded, triggering fatal error');
|
|
1963
|
+
if (this.onFatalError) {
|
|
1964
|
+
this.onFatalError(error);
|
|
1965
|
+
return false;
|
|
1966
|
+
}
|
|
1967
|
+
throw error instanceof Error
|
|
1968
|
+
? error
|
|
1969
|
+
: new Error(`Fatal system error: ${String(error)}`);
|
|
1970
|
+
}
|
|
1971
|
+
return true;
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Compute the current backoff delay based on consecutive failures.
|
|
1975
|
+
*
|
|
1976
|
+
* @returns Delay in milliseconds.
|
|
1977
|
+
*/
|
|
1978
|
+
get currentBackoffMs() {
|
|
1979
|
+
if (this.consecutiveFailures === 0)
|
|
1980
|
+
return 0;
|
|
1981
|
+
const exp = Math.max(0, this.consecutiveFailures - 1);
|
|
1982
|
+
return Math.min(this.maxBackoffMs, this.baseDelayMs * 2 ** exp);
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Sleep for the current backoff duration.
|
|
1986
|
+
*
|
|
1987
|
+
* @param signal - Optional abort signal.
|
|
1988
|
+
*/
|
|
1989
|
+
async backoff(signal) {
|
|
1990
|
+
const delay = this.currentBackoffMs;
|
|
1991
|
+
if (delay <= 0)
|
|
1992
|
+
return;
|
|
1993
|
+
this.logger.warn({ delayMs: delay, consecutiveFailures: this.consecutiveFailures }, 'Backing off before next attempt');
|
|
1994
|
+
await new Promise((resolve, reject) => {
|
|
1995
|
+
const timer = setTimeout(() => {
|
|
1996
|
+
cleanup();
|
|
1997
|
+
resolve();
|
|
1998
|
+
}, delay);
|
|
1999
|
+
const onAbort = () => {
|
|
2000
|
+
cleanup();
|
|
2001
|
+
reject(new Error('Backoff aborted'));
|
|
2002
|
+
};
|
|
2003
|
+
const cleanup = () => {
|
|
2004
|
+
clearTimeout(timer);
|
|
2005
|
+
if (signal)
|
|
2006
|
+
signal.removeEventListener('abort', onAbort);
|
|
2007
|
+
};
|
|
2008
|
+
if (signal) {
|
|
2009
|
+
if (signal.aborted) {
|
|
2010
|
+
onAbort();
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
/** Current consecutive failure count. */
|
|
2018
|
+
get failures() {
|
|
2019
|
+
return this.consecutiveFailures;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
|
|
1909
2023
|
/**
|
|
1910
2024
|
* @module watcher
|
|
1911
2025
|
* Filesystem watcher wrapping chokidar. I/O: watches files/directories for add/change/unlink events, enqueues to processing queue.
|
|
@@ -1918,6 +2032,7 @@ class FileSystemWatcher {
|
|
|
1918
2032
|
queue;
|
|
1919
2033
|
processor;
|
|
1920
2034
|
logger;
|
|
2035
|
+
health;
|
|
1921
2036
|
watcher;
|
|
1922
2037
|
/**
|
|
1923
2038
|
* Create a new FileSystemWatcher.
|
|
@@ -1926,12 +2041,20 @@ class FileSystemWatcher {
|
|
|
1926
2041
|
* @param queue - The event queue.
|
|
1927
2042
|
* @param processor - The document processor.
|
|
1928
2043
|
* @param logger - The logger instance.
|
|
2044
|
+
* @param options - Optional health/fatal error options.
|
|
1929
2045
|
*/
|
|
1930
|
-
constructor(config, queue, processor, logger) {
|
|
2046
|
+
constructor(config, queue, processor, logger, options = {}) {
|
|
1931
2047
|
this.config = config;
|
|
1932
2048
|
this.queue = queue;
|
|
1933
2049
|
this.processor = processor;
|
|
1934
2050
|
this.logger = logger;
|
|
2051
|
+
const healthOptions = {
|
|
2052
|
+
maxRetries: options.maxRetries,
|
|
2053
|
+
maxBackoffMs: options.maxBackoffMs,
|
|
2054
|
+
onFatalError: options.onFatalError,
|
|
2055
|
+
logger,
|
|
2056
|
+
};
|
|
2057
|
+
this.health = new SystemHealth(healthOptions);
|
|
1935
2058
|
}
|
|
1936
2059
|
/**
|
|
1937
2060
|
* Start watching the filesystem and processing events.
|
|
@@ -1948,18 +2071,19 @@ class FileSystemWatcher {
|
|
|
1948
2071
|
});
|
|
1949
2072
|
this.watcher.on('add', (path) => {
|
|
1950
2073
|
this.logger.debug({ path }, 'File added');
|
|
1951
|
-
this.queue.enqueue({ type: 'create', path, priority: 'normal' }, () => this.processor.processFile(path));
|
|
2074
|
+
this.queue.enqueue({ type: 'create', path, priority: 'normal' }, () => this.wrapProcessing(() => this.processor.processFile(path)));
|
|
1952
2075
|
});
|
|
1953
2076
|
this.watcher.on('change', (path) => {
|
|
1954
2077
|
this.logger.debug({ path }, 'File changed');
|
|
1955
|
-
this.queue.enqueue({ type: 'modify', path, priority: 'normal' }, () => this.processor.processFile(path));
|
|
2078
|
+
this.queue.enqueue({ type: 'modify', path, priority: 'normal' }, () => this.wrapProcessing(() => this.processor.processFile(path)));
|
|
1956
2079
|
});
|
|
1957
2080
|
this.watcher.on('unlink', (path) => {
|
|
1958
2081
|
this.logger.debug({ path }, 'File removed');
|
|
1959
|
-
this.queue.enqueue({ type: 'delete', path, priority: 'normal' }, () => this.processor.deleteFile(path));
|
|
2082
|
+
this.queue.enqueue({ type: 'delete', path, priority: 'normal' }, () => this.wrapProcessing(() => this.processor.deleteFile(path)));
|
|
1960
2083
|
});
|
|
1961
2084
|
this.watcher.on('error', (error) => {
|
|
1962
2085
|
this.logger.error({ err: normalizeError(error) }, 'Watcher error');
|
|
2086
|
+
this.health.recordFailure(error);
|
|
1963
2087
|
});
|
|
1964
2088
|
this.queue.process();
|
|
1965
2089
|
this.logger.info({ paths: this.config.paths }, 'Filesystem watcher started');
|
|
@@ -1974,6 +2098,30 @@ class FileSystemWatcher {
|
|
|
1974
2098
|
this.logger.info('Filesystem watcher stopped');
|
|
1975
2099
|
}
|
|
1976
2100
|
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Get the system health tracker.
|
|
2103
|
+
*/
|
|
2104
|
+
get systemHealth() {
|
|
2105
|
+
return this.health;
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Wrap a processing operation with health tracking.
|
|
2109
|
+
* On success, resets the failure counter.
|
|
2110
|
+
* On failure, records the failure and applies backoff.
|
|
2111
|
+
*/
|
|
2112
|
+
async wrapProcessing(fn) {
|
|
2113
|
+
try {
|
|
2114
|
+
await this.health.backoff();
|
|
2115
|
+
await fn();
|
|
2116
|
+
this.health.recordSuccess();
|
|
2117
|
+
}
|
|
2118
|
+
catch (error) {
|
|
2119
|
+
const shouldContinue = this.health.recordFailure(error);
|
|
2120
|
+
if (!shouldContinue) {
|
|
2121
|
+
await this.stop();
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
1977
2125
|
}
|
|
1978
2126
|
|
|
1979
2127
|
/**
|
|
@@ -2049,7 +2197,7 @@ const defaultFactories = {
|
|
|
2049
2197
|
compileRules,
|
|
2050
2198
|
createDocumentProcessor: (config, embeddingProvider, vectorStore, compiledRules, logger) => new DocumentProcessor(config, embeddingProvider, vectorStore, compiledRules, logger),
|
|
2051
2199
|
createEventQueue: (options) => new EventQueue(options),
|
|
2052
|
-
createFileSystemWatcher: (config, queue, processor, logger) => new FileSystemWatcher(config, queue, processor, logger),
|
|
2200
|
+
createFileSystemWatcher: (config, queue, processor, logger, options) => new FileSystemWatcher(config, queue, processor, logger, options),
|
|
2053
2201
|
createApiServer,
|
|
2054
2202
|
};
|
|
2055
2203
|
/**
|
|
@@ -2059,6 +2207,7 @@ class JeevesWatcher {
|
|
|
2059
2207
|
config;
|
|
2060
2208
|
configPath;
|
|
2061
2209
|
factories;
|
|
2210
|
+
runtimeOptions;
|
|
2062
2211
|
logger;
|
|
2063
2212
|
watcher;
|
|
2064
2213
|
queue;
|
|
@@ -2071,11 +2220,13 @@ class JeevesWatcher {
|
|
|
2071
2220
|
* @param config - The application configuration.
|
|
2072
2221
|
* @param configPath - Optional config file path to watch for changes.
|
|
2073
2222
|
* @param factories - Optional component factories (for dependency injection).
|
|
2223
|
+
* @param runtimeOptions - Optional runtime-only options (e.g., onFatalError).
|
|
2074
2224
|
*/
|
|
2075
|
-
constructor(config, configPath, factories = {}) {
|
|
2225
|
+
constructor(config, configPath, factories = {}, runtimeOptions = {}) {
|
|
2076
2226
|
this.config = config;
|
|
2077
2227
|
this.configPath = configPath;
|
|
2078
2228
|
this.factories = { ...defaultFactories, ...factories };
|
|
2229
|
+
this.runtimeOptions = runtimeOptions;
|
|
2079
2230
|
}
|
|
2080
2231
|
/**
|
|
2081
2232
|
* Start the watcher, API server, and all components.
|
|
@@ -2108,7 +2259,11 @@ class JeevesWatcher {
|
|
|
2108
2259
|
rateLimitPerMinute: this.config.embedding.rateLimitPerMinute,
|
|
2109
2260
|
});
|
|
2110
2261
|
this.queue = queue;
|
|
2111
|
-
const watcher = this.factories.createFileSystemWatcher(this.config.watch, queue, processor, logger
|
|
2262
|
+
const watcher = this.factories.createFileSystemWatcher(this.config.watch, queue, processor, logger, {
|
|
2263
|
+
maxRetries: this.config.maxRetries,
|
|
2264
|
+
maxBackoffMs: this.config.maxBackoffMs,
|
|
2265
|
+
onFatalError: this.runtimeOptions.onFatalError,
|
|
2266
|
+
});
|
|
2112
2267
|
this.watcher = watcher;
|
|
2113
2268
|
const server = this.factories.createApiServer({
|
|
2114
2269
|
processor,
|
|
@@ -2213,4 +2368,4 @@ async function startFromConfig(configPath) {
|
|
|
2213
2368
|
return app;
|
|
2214
2369
|
}
|
|
2215
2370
|
|
|
2216
|
-
export { DocumentProcessor, EventQueue, FileSystemWatcher, JeevesWatcher, VectorStoreClient, apiConfigSchema, applyRules, buildAttributes, compileRules, configWatchConfigSchema, contentHash, createApiServer, createEmbeddingProvider, createLogger, deleteMetadata, embeddingConfigSchema, extractText, inferenceRuleSchema, jeevesWatcherConfigSchema, loadConfig, loggingConfigSchema, metadataPath, pointId, readMetadata, startFromConfig, vectorStoreConfigSchema, watchConfigSchema, writeMetadata };
|
|
2371
|
+
export { DocumentProcessor, EventQueue, FileSystemWatcher, JeevesWatcher, SystemHealth, VectorStoreClient, apiConfigSchema, applyRules, buildAttributes, compileRules, configWatchConfigSchema, contentHash, createApiServer, createEmbeddingProvider, createLogger, deleteMetadata, embeddingConfigSchema, extractText, inferenceRuleSchema, jeevesWatcherConfigSchema, loadConfig, loggingConfigSchema, metadataPath, pointId, readMetadata, startFromConfig, vectorStoreConfigSchema, watchConfigSchema, writeMetadata };
|
package/package.json
CHANGED