From e53403acbf83fed6fdc3e47d6c6eda1fdf90707b Mon Sep 17 00:00:00 2001 From: Giovani Guizzo Date: Sat, 4 Oct 2025 09:44:38 -0300 Subject: [PATCH 01/10] feat: implement manual job resolution with configurable jobs file path and enhance stack trace parsing --- package.json | 2 +- packages/core/src/job/job.ts | 15 +- packages/core/src/tools/index.ts | 1 + packages/core/src/tools/stack-parser.test.ts | 52 ++++ packages/core/src/tools/stack-parser.ts | 25 ++ packages/engine/src/engine.test.ts | 109 ++++++++ packages/engine/src/engine.ts | 41 ++- .../src/shared-runner/manual-loader.test.ts | 264 +++++++++++++++++- .../engine/src/shared-runner/manual-loader.ts | 70 ++++- packages/engine/src/shared-runner/runner.ts | 16 +- yarn.lock | 58 ++-- 11 files changed, 603 insertions(+), 50 deletions(-) create mode 100644 packages/core/src/tools/stack-parser.test.ts create mode 100644 packages/core/src/tools/stack-parser.ts diff --git a/package.json b/package.json index b1bd43f9..940ff420 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "semantic-release": "^24.2.7", "tailwindcss": "^4.1.13", "tsx": "^4.20.5", - "turbo": "^2.5.6", + "turbo": "^2.5.8", "typescript": "^5.9.2", "typescript-eslint": "^8.42.0", "vitepress": "^1.6.4", diff --git a/packages/core/src/job/job.ts b/packages/core/src/job/job.ts index 47c843ea..ddef6ea4 100644 --- a/packages/core/src/job/job.ts +++ b/packages/core/src/job/job.ts @@ -4,6 +4,7 @@ import { pathToFileURL } from "url"; import { logger } from "../logger"; import { BackoffStrategy, ErrorData, JobData, JobState } from "../schema"; import { toErrorData } from "../tools"; +import { parseStackTrace } from "../tools/stack-parser"; import { CompletedResult, FailedResult, isJobResult, JobResult, RetryResult, SnoozeResult } from "../transitions"; import { UniquenessConfig } from "../uniquiness"; @@ -250,18 +251,8 @@ export abstract class Job implements JobData { */ async function buildPath(className: string) { const err = new Error(); - let stackLines = err.stack?.split("\n") ?? []; - stackLines = stackLines.slice(1); - logger("Job").debug(`Resolving script file path. Stack lines: ${stackLines.join("\n")}`); - const filePaths = stackLines - .map((line) => { - const match = /(file:\/\/)?(((\/?)(\w:))?([/\\].+)):\d+:\d+/.exec(line); - if (match) { - return `${match[5] ?? ""}${match[6].replaceAll("\\", "/")}`; - } - return null; - }) - .filter(Boolean); + logger("Job").debug(`Resolving script file path. Stack lines: ${err.stack}`); + const filePaths = parseStackTrace(err); for (const filePath of filePaths) { const hasExported = await hasClassExported(filePath!, className); diff --git a/packages/core/src/tools/index.ts b/packages/core/src/tools/index.ts index 2218ae06..8f1e7dd5 100644 --- a/packages/core/src/tools/index.ts +++ b/packages/core/src/tools/index.ts @@ -1,2 +1,3 @@ export * from "./parse-error-data"; export * from "./serialize-error"; +export * from "./stack-parser"; diff --git a/packages/core/src/tools/stack-parser.test.ts b/packages/core/src/tools/stack-parser.test.ts new file mode 100644 index 00000000..3f4eb22b --- /dev/null +++ b/packages/core/src/tools/stack-parser.test.ts @@ -0,0 +1,52 @@ +import { parseStackTrace } from "./stack-parser"; + +describe("parseStackTrace", () => { + it("should parse stack trace with Windows file paths", () => { + const error = new Error("Test error"); + error.stack = `Error: Test error + at function1 (C:\\Users\\test\\file.js:10:5) + at function2 (C:\\Projects\\app\\index.ts:25:12)`; + + const result = parseStackTrace(error); + expect(result).toEqual(["C:/Users/test/file.js", "C:/Projects/app/index.ts"]); + }); + + it("should parse stack trace with Unix file paths", () => { + const error = new Error("Test error"); + error.stack = `Error: Test error + at function1 (/home/user/file.js:10:5) + at function2 (/opt/app/index.ts:25:12)`; + + const result = parseStackTrace(error); + expect(result).toEqual(["/home/user/file.js", "/opt/app/index.ts"]); + }); + + it("should parse stack trace with file:// protocol", () => { + const error = new Error("Test error"); + error.stack = `Error: Test error + at function1 (file:///C:/Users/test/file.js:10:5) + at function2 (file:///home/user/app.ts:15:8)`; + + const result = parseStackTrace(error); + expect(result).toEqual(["C:/Users/test/file.js", "/home/user/app.ts"]); + }); + + it("should handle mixed path formats", () => { + const error = new Error("Test error"); + error.stack = `Error: Test error + at function1 (C:\\Windows\\file.js:10:5) + at function2 (/usr/local/file.ts:20:3) + at function3 (file:///D:/project/main.js:5:1)`; + + const result = parseStackTrace(error); + expect(result).toEqual(["C:/Windows/file.js", "/usr/local/file.ts", "D:/project/main.js"]); + }); + + it("should return empty array when stack is undefined", () => { + const error = new Error("Test error"); + error.stack = undefined; + + const result = parseStackTrace(error); + expect(result).toEqual([]); + }); +}); diff --git a/packages/core/src/tools/stack-parser.ts b/packages/core/src/tools/stack-parser.ts new file mode 100644 index 00000000..6f0ac74d --- /dev/null +++ b/packages/core/src/tools/stack-parser.ts @@ -0,0 +1,25 @@ +/** + * Parses an error stack trace to extract file paths. + * + * @param err - The Error object containing the stack trace to parse + * @returns An array of normalized file paths extracted from the stack trace, with backslashes converted to forward slashes and null entries filtered out + * + * @example + * ```typescript + * const error = new Error('Something went wrong'); + * const filePaths = parseStackTrace(error); + * console.log(filePaths); // ['C:/path/to/file.js', '/another/path/file.ts'] + * ``` + */ +export function parseStackTrace(err: Error): string[] { + const stackLines = err.stack?.split("\n") ?? []; + return stackLines + .map((line) => { + const match = /(file:\/\/)?(((\/?)(\w:))?([/\\].+)):\d+:\d+/.exec(line); + if (match) { + return `${match[5] ?? ""}${match[6].replaceAll("\\", "/")}`; + } + return undefined; + }) + .filter(Boolean) as string[]; +} diff --git a/packages/engine/src/engine.test.ts b/packages/engine/src/engine.test.ts index de334a23..41f6fa3f 100644 --- a/packages/engine/src/engine.test.ts +++ b/packages/engine/src/engine.test.ts @@ -1,4 +1,7 @@ import { sidequestTest } from "@/tests/fixture"; +import { rmSync, unlinkSync, writeFileSync } from "fs"; +import { platform } from "os"; +import { resolve } from "path"; import { describe, expect, vi } from "vitest"; import { Engine } from "./engine"; import { MANUAL_SCRIPT_TAG } from "./shared-runner"; @@ -34,6 +37,112 @@ export class ParameterizedJob extends DummyJob { } describe("Engine", () => { + beforeAll(() => { + // Ensure MANUAL_SCRIPT_TAG file exists for tests that rely on it + writeFileSync(MANUAL_SCRIPT_TAG, "dummy"); + }); + + afterAll(() => { + // Clean up the MANUAL_SCRIPT_TAG file after tests + rmSync(MANUAL_SCRIPT_TAG); + }); + + describe("validateConfig", () => { + sidequestTest("should throw error when maxConcurrentJobs is negative", async () => { + const engine = new Engine(); + + await expect(() => + engine.configure({ + maxConcurrentJobs: -5, + }), + ).rejects.toThrowError(); + + await engine.close(); + }); + + sidequestTest("should throw error when jobsFilePath does not exist with manualJobResolution", async () => { + const engine = new Engine(); + + await expect(() => + engine.configure({ + manualJobResolution: true, + jobsFilePath: "/non/existent/path/sidequest.jobs.js", + }), + ).rejects.toThrowError(); + + await engine.close(); + }); + + sidequestTest("should throw error when jobsFilePath is not found in parent directories", async () => { + const engine = new Engine(); + + // Use a temp directory that doesn't have sidequest.jobs.js + const tempDir = platform() === "win32" ? "C:\\Windows\\Temp" : "/tmp"; + + // Mock process.cwd to return temp directory + const originalCwd = process.cwd.bind(process); + process.cwd = vi.fn(() => tempDir); + + try { + await expect(() => + engine.configure({ + manualJobResolution: true, + // Don't provide jobsFilePath, so it will search parent directories + }), + ).rejects.toThrowError(); + } finally { + process.cwd = originalCwd; + await engine.close(); + } + }); + + sidequestTest("should accept valid jobsFilePath with manualJobResolution", async () => { + const engine = new Engine(); + + // Use the actual sidequest.jobs.js file + const fileLocation = resolve(import.meta.dirname, "sidequest.jobs.js"); + try { + // We create a file like it will resolve, from the file that calls engine.configure + writeFileSync(fileLocation, "// Temporary sidequest.jobs.js file for testing"); + + const config = await engine.configure({ + manualJobResolution: true, + jobsFilePath: "./sidequest.jobs.js", + }); + + expect(config.manualJobResolution).toBe(true); + expect(config.jobsFilePath).toBe("./sidequest.jobs.js"); + } finally { + // Clean up the temporary file + unlinkSync(fileLocation); + await engine.close(); + } + }); + + sidequestTest("should accept valid jobsFilePath with manualJobResolution on a different dir", async () => { + const engine = new Engine(); + + // Use the actual sidequest.jobs.js file + const fileLocation = resolve(import.meta.dirname, "..", "sidequest.jobs.js"); + try { + // We create a file like it will resolve, from the file that calls engine.configure + writeFileSync(fileLocation, "// Temporary sidequest.jobs.js file for testing"); + + const config = await engine.configure({ + manualJobResolution: true, + jobsFilePath: "../sidequest.jobs.js", + }); + + expect(config.manualJobResolution).toBe(true); + expect(config.jobsFilePath).toBe("../sidequest.jobs.js"); + } finally { + // Clean up the temporary file + unlinkSync(fileLocation); + await engine.close(); + } + }); + }); + describe("configure", () => { sidequestTest("should configure engine with default values", async () => { const engine = new Engine(); diff --git a/packages/engine/src/engine.ts b/packages/engine/src/engine.ts index 443218d2..658a07f6 100644 --- a/packages/engine/src/engine.ts +++ b/packages/engine/src/engine.ts @@ -1,13 +1,16 @@ import { Backend, BackendConfig, LazyBackend, MISC_FALLBACK, NewQueueData, QUEUE_FALLBACK } from "@sidequest/backend"; import { configureLogger, JobClassType, logger, LoggerOptions } from "@sidequest/core"; import { ChildProcess, fork } from "child_process"; +import { existsSync } from "fs"; import { cpus } from "os"; +import { fileURLToPath } from "url"; import { inspect } from "util"; import { DEFAULT_WORKER_PATH } from "./constants"; import { JOB_BUILDER_FALLBACK } from "./job/constants"; import { ScheduledJobRegistry } from "./job/cron-registry"; import { JobBuilder, JobBuilderDefaults } from "./job/job-builder"; import { grantQueueConfig, QueueDefaults } from "./queue/grant-queue-config"; +import { findSidequestJobsScriptInParentDirs, resolveScriptPath } from "./shared-runner"; import { clearGracefulShutdown, gracefulShutdown } from "./utils/shutdown"; /** @@ -71,6 +74,21 @@ export interface EngineConfig { * Defaults to `false`. */ manualJobResolution?: boolean; + + /** + * Optional path to the `sidequest.jobs.js` file when using manual job resolution. + * If not provided, the engine will search for `sidequest.jobs.js` starting from the current working directory + * and walking up through parent directories until it finds the file or reaches the root. + * + * This is useful if your `sidequest.jobs.js` file is located in a non-standard location + * or if you want to explicitly specify its path. + * + * IMPORTANT: if a relative path is provided, it will be resolved relative to the file calling the engine or + * `Sidequest.configure()`, NOT the current working directory. + * + * If manualJobResolution === false, this option is ignored. + */ + jobsFilePath?: string; } /** @@ -158,11 +176,10 @@ export class Engine { state: config?.queueDefaults?.state ?? QUEUE_FALLBACK.state, }, manualJobResolution: config?.manualJobResolution ?? false, + jobsFilePath: config?.jobsFilePath?.trim() ?? "", }; - if (this.config.maxConcurrentJobs !== undefined && this.config.maxConcurrentJobs < 1) { - throw new Error(`Invalid "maxConcurrentJobs" value: must be at least 1.`); - } + this.validateConfig(); logger("Engine").debug(`Configuring Sidequest engine: ${inspect(this.config)}`); @@ -178,6 +195,24 @@ export class Engine { return this.config; } + validateConfig() { + if (this.config!.maxConcurrentJobs !== undefined && this.config!.maxConcurrentJobs < 1) { + throw new Error(`Invalid "maxConcurrentJobs" value: must be at least 1.`); + } + + if (this.config!.manualJobResolution) { + if (this.config!.jobsFilePath) { + const scriptUrl = resolveScriptPath(this.config!.jobsFilePath); + if (!existsSync(fileURLToPath(scriptUrl))) { + throw new Error(`The specified jobsFilePath does not exist. Resolved to: ${scriptUrl}`); + } + } else { + // This should throw an error if not found + findSidequestJobsScriptInParentDirs(); + } + } + } + /** * Starts the Sidequest engine and worker process. * @param config Optional configuration object. diff --git a/packages/engine/src/shared-runner/manual-loader.test.ts b/packages/engine/src/shared-runner/manual-loader.test.ts index c23c4725..9bd2630d 100644 --- a/packages/engine/src/shared-runner/manual-loader.test.ts +++ b/packages/engine/src/shared-runner/manual-loader.test.ts @@ -2,7 +2,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { join, resolve } from "node:path"; import { pathToFileURL } from "node:url"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { findSidequestJobsScriptInParentDirs } from "./manual-loader"; +import { findSidequestJobsScriptInParentDirs, resolveScriptPath } from "./manual-loader"; describe("findSidequestJobsScriptInParentDirs", () => { const tempDir = resolve(import.meta.dirname, "temp-test-dir"); @@ -254,3 +254,265 @@ describe("findSidequestJobsScriptInParentDirs", () => { }); }); }); + +describe("resolveScriptPath", () => { + describe("error handling", () => { + it("should throw error for empty string", () => { + expect(() => { + resolveScriptPath(""); + }).toThrow(); + }); + + it("should throw error for whitespace-only string", () => { + expect(() => { + resolveScriptPath(" "); + }).toThrow(); + }); + + it("should throw error when relative path cannot be resolved", () => { + const nonExistentRelativePath = "./non-existent-file.js"; + + expect(() => { + resolveScriptPath(nonExistentRelativePath); + }).toThrow(); + }); + + it("should throw error for non-existent relative path", () => { + const relativePath = "../some/random/path/that/does/not/exist.js"; + + expect(() => { + resolveScriptPath(relativePath); + }).toThrow(); + }); + }); + + describe("file URL handling", () => { + it("should return file URL as-is when already a file URL", () => { + const fileUrl = "file:///C:/Users/test/project/script.js"; + + const result = resolveScriptPath(fileUrl); + + expect(result).toBe(fileUrl); + }); + + it("should return file URL for Windows-style file URL", () => { + const fileUrl = "file:///C:/path/to/script.js"; + + const result = resolveScriptPath(fileUrl); + + expect(result).toBe(fileUrl); + }); + + it("should return file URL for Unix-style file URL", () => { + const fileUrl = "file:///home/user/project/script.js"; + + const result = resolveScriptPath(fileUrl); + + expect(result).toBe(fileUrl); + }); + + it("should not return non-file protocol URLs as-is", () => { + const httpUrl = "http://example.com/script.js"; + + // Since it's not a file: URL and not a valid file path, it should throw + expect(() => { + resolveScriptPath(httpUrl); + }).toThrow(); + }); + + it("should handle file URLs with encoded characters", () => { + const fileUrl = "file:///C:/path%20with%20spaces/script.js"; + + const result = resolveScriptPath(fileUrl); + + expect(result).toBe(fileUrl); + }); + }); + + describe("absolute path handling", () => { + it("should convert Windows absolute path to file URL", () => { + const absolutePath = "C:\\Users\\test\\project\\script.js"; + + const result = resolveScriptPath(absolutePath); + + expect(result).toBe(pathToFileURL(absolutePath).href); + expect(result).toMatch(/^file:\/\//); + }); + + it("should convert Unix absolute path to file URL", () => { + const absolutePath = "/home/user/project/script.js"; + + const result = resolveScriptPath(absolutePath); + + expect(result).toBe(pathToFileURL(absolutePath).href); + expect(result).toMatch(/^file:\/\//); + }); + + it("should handle absolute path with spaces", () => { + const absolutePath = resolve(".", "dir with spaces", "script.js"); + + const result = resolveScriptPath(absolutePath); + + expect(result).toBe(pathToFileURL(absolutePath).href); + expect(result).toContain("dir%20with%20spaces"); + }); + + it("should handle absolute path with special characters", () => { + const absolutePath = "C:\\Users\\test@123\\project_name\\script-file.js"; + + const result = resolveScriptPath(absolutePath); + + expect(result).toBe(pathToFileURL(absolutePath).href); + }); + + it("should work with absolute paths on Windows", () => { + const windowsPath = "D:\\Projects\\MyApp\\src\\index.js"; + + const result = resolveScriptPath(windowsPath); + + expect(result).toBe(pathToFileURL(windowsPath).href); + expect(result).toMatch(/^file:\/\//); + }); + }); + + describe("relative path handling", () => { + it("should resolve relative path from stack trace context", () => { + // Create a file relative to the current test file location + const fileName = "relative-test-file.js"; + const filePath = join(import.meta.dirname, fileName); + writeFileSync(filePath, "test content"); + + try { + const result = resolveScriptPath(`./${fileName}`); + + expect(result).toBe(pathToFileURL(filePath).href); + } finally { + if (existsSync(filePath)) { + rmSync(filePath); + } + } + }); + + it("should resolve relative path with parent directory", () => { + // Create a file in parent directory + const fileName = "parent-relative-test.js"; + const parentDir = resolve(import.meta.dirname, ".."); + const filePath = join(parentDir, fileName); + writeFileSync(filePath, "test content"); + + try { + const result = resolveScriptPath(`../${fileName}`); + + expect(result).toBe(pathToFileURL(filePath).href); + } finally { + if (existsSync(filePath)) { + rmSync(filePath); + } + } + }); + + it("should resolve deeply nested relative path", () => { + // Create a nested directory structure + const deepDir = join(import.meta.dirname, "deep", "nested", "path"); + mkdirSync(deepDir, { recursive: true }); + + const fileName = "deep-file.js"; + const filePath = join(deepDir, fileName); + writeFileSync(filePath, "deep content"); + + try { + const result = resolveScriptPath(`./deep/nested/path/${fileName}`); + + expect(result).toBe(pathToFileURL(filePath).href); + } finally { + if (existsSync(join(import.meta.dirname, "deep"))) { + rmSync(join(import.meta.dirname, "deep"), { recursive: true, force: true }); + } + } + }); + + it("should resolve current directory reference", () => { + const fileName = "current-dir-test.js"; + const filePath = join(import.meta.dirname, fileName); + writeFileSync(filePath, "current dir content"); + + try { + const result = resolveScriptPath(`./${fileName}`); + + expect(result).toBe(pathToFileURL(filePath).href); + } finally { + if (existsSync(filePath)) { + rmSync(filePath); + } + } + }); + + it("should handle multiple parent directory traversals", () => { + const fileName = "grandparent-test.js"; + const grandparentDir = resolve(import.meta.dirname, "..", ".."); + const filePath = join(grandparentDir, fileName); + writeFileSync(filePath, "grandparent content"); + + try { + const result = resolveScriptPath(`../../${fileName}`); + + expect(result).toBe(pathToFileURL(filePath).href); + } finally { + if (existsSync(filePath)) { + rmSync(filePath); + } + } + }); + }); + + describe("edge cases", () => { + it("should handle paths without extension", () => { + const absolutePath = "C:\\Users\\test\\project\\Dockerfile"; + + const result = resolveScriptPath(absolutePath); + + expect(result).toBe(pathToFileURL(absolutePath).href); + }); + + it("should handle paths with multiple dots", () => { + const absolutePath = "C:\\Users\\test\\project\\script.test.js"; + + const result = resolveScriptPath(absolutePath); + + expect(result).toBe(pathToFileURL(absolutePath).href); + }); + + it("should handle very long paths", () => { + const longPath = "C:\\Users\\test\\" + "very-long-directory-name\\".repeat(20) + "script.js"; + + const result = resolveScriptPath(longPath); + + expect(result).toBe(pathToFileURL(longPath).href); + }); + + it("should trim whitespace from input", () => { + const absolutePath = " C:\\Users\\test\\project\\script.js "; + + const result = resolveScriptPath(absolutePath); + + // Should trim and then convert to file URL + expect(result).toBe(pathToFileURL(absolutePath.trim()).href); + }); + + it("should handle file names with unicode characters", () => { + const absolutePath = "C:\\Users\\test\\项目\\脚本.js"; + + const result = resolveScriptPath(absolutePath); + + expect(result).toBe(pathToFileURL(absolutePath).href); + }); + + it("should handle paths with dots in directory names", () => { + const absolutePath = "C:\\Users\\test\\.hidden\\script.js"; + + const result = resolveScriptPath(absolutePath); + + expect(result).toBe(pathToFileURL(absolutePath).href); + }); + }); +}); diff --git a/packages/engine/src/shared-runner/manual-loader.ts b/packages/engine/src/shared-runner/manual-loader.ts index 12bee32d..ef586778 100644 --- a/packages/engine/src/shared-runner/manual-loader.ts +++ b/packages/engine/src/shared-runner/manual-loader.ts @@ -1,5 +1,6 @@ +import { parseStackTrace } from "@sidequest/core"; import { existsSync } from "node:fs"; -import { dirname, join, resolve } from "node:path"; +import { dirname, isAbsolute, join, resolve } from "node:path"; import { pathToFileURL } from "node:url"; /** @@ -28,7 +29,7 @@ export const MANUAL_SCRIPT_TAG = "sidequest.jobs.js"; * const packageFile = findFileInParentDirs("package.json", "/some/project/dir"); * ``` */ -export function findSidequestJobsScriptInParentDirs(fileName = "sidequest.jobs.js", startDir = process.cwd()): string { +export function findSidequestJobsScriptInParentDirs(fileName = MANUAL_SCRIPT_TAG, startDir = process.cwd()): string { if (!fileName.trim()) { throw new Error("fileName must be a non-empty string"); } @@ -63,3 +64,68 @@ export function findSidequestJobsScriptInParentDirs(fileName = "sidequest.jobs.j throw new Error(`File "${fileName}" not found in "${startDir}" or any parent directory`); } + +/** + * Resolves a script path to a file URL, handling various path formats and resolution strategies. + * + * This function attempts to resolve script paths in the following order: + * 1. If the path is already a valid URL, returns it as-is (for file: protocol URLs) + * 2. If the path is absolute, converts it directly to a file URL + * 3. If the path is relative, searches for the file in directories from the call stack, + * starting from the caller's directory and walking up the stack + * + * The stack-based resolution helps resolve relative paths based on the script's + * execution context rather than the current working directory. + * + * @param scriptPath - The script path to resolve. Can be a relative path, absolute path, or URL string. + * @returns The resolved file URL as a string + * @throws {Error} When scriptPath is empty or when the file cannot be found in any searched location + * + * @example + * ```typescript + * // Absolute path + * resolveScriptPath('/path/to/script.js') // Returns 'file:///path/to/script.js' + * + * // Relative path (searches based on call stack) + * resolveScriptPath('./script.js') // Returns file URL of script.js found in caller's directory + * + * // Already a URL + * resolveScriptPath('file:///path/to/script.js') // Returns 'file:///path/to/script.js' + * ``` + */ +export function resolveScriptPath(scriptPath: string): string { + scriptPath = scriptPath.trim(); + if (!scriptPath) { + throw new Error("scriptPath must be a non-empty string"); + } + + // If the scriptPath is already a URL, return as is + try { + const url = new URL(scriptPath); + if (url.protocol === "file:") { + return url.href; + } + } catch { + // Not a valid URL, proceed to resolve as file path + } + + // If it's an absolute path, convert directly to file URL + if (isAbsolute(scriptPath)) { + return pathToFileURL(scriptPath).href; + } + + // Otherwise, search in current and parent directories based on stack trace + // This helps in resolving relative paths based on where the script is executed + // rather than the current working directory + const err = new Error(); + const stackFiles = parseStackTrace(err); + for (const file of stackFiles) { + const parentDir = dirname(file); + const resolvedPath = resolve(parentDir, scriptPath); + if (existsSync(resolvedPath)) { + return pathToFileURL(resolvedPath).href; + } + } + + throw new Error(`Unable to resolve script path: ${scriptPath}`); +} diff --git a/packages/engine/src/shared-runner/runner.ts b/packages/engine/src/shared-runner/runner.ts index 47a29ee6..0bb4cbb6 100644 --- a/packages/engine/src/shared-runner/runner.ts +++ b/packages/engine/src/shared-runner/runner.ts @@ -1,4 +1,6 @@ import { Job, JobClassType, JobData, JobResult, logger, resolveScriptPath, toErrorData } from "@sidequest/core"; +import { existsSync } from "node:fs"; +import { fileURLToPath } from "node:url"; import { EngineConfig } from "../engine"; import { importSidequest } from "../utils"; import { findSidequestJobsScriptInParentDirs, MANUAL_SCRIPT_TAG } from "./manual-loader"; @@ -16,12 +18,22 @@ export default async function run({ jobData, config }: { jobData: JobData; confi try { logger("Runner").debug(`Importing job script "${jobData.script}"`); - let scriptUrl; + let scriptUrl: string; if (jobData.script === MANUAL_SCRIPT_TAG) { logger("Runner").debug("Manual job resolution is enabled; importing 'sidequest.jobs.js' job script."); try { // When manual job resolution is enabled, import from the sidequest.jobs.js script - scriptUrl = findSidequestJobsScriptInParentDirs(); + if (!config.jobsFilePath) { + // If no custom path is provided, search for sidequest.jobs.js in parent directories + // throws if not found + scriptUrl = findSidequestJobsScriptInParentDirs(); + } else { + // If a custom path is provided, resolve it and ensure it exists + scriptUrl = resolveScriptPath(config.jobsFilePath); + if (!existsSync(fileURLToPath(scriptUrl))) { + throw new Error(`The specified jobsFilePath does not exist. Resolved to: ${scriptUrl}`); + } + } } catch (error) { const errorMessage = `Failed to locate 'sidequest.jobs.js' for manual job resolution: ${error instanceof Error ? error.message : String(error)}`; logger("Runner").error(errorMessage); diff --git a/yarn.lock b/yarn.lock index b25d5789..342f340b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11417,7 +11417,7 @@ __metadata: semantic-release: "npm:^24.2.7" tailwindcss: "npm:^4.1.13" tsx: "npm:^4.20.5" - turbo: "npm:^2.5.6" + turbo: "npm:^2.5.8" typescript: "npm:^5.9.2" typescript-eslint: "npm:^8.42.0" vitepress: "npm:^1.6.4" @@ -12343,58 +12343,58 @@ __metadata: languageName: node linkType: hard -"turbo-darwin-64@npm:2.5.6": - version: 2.5.6 - resolution: "turbo-darwin-64@npm:2.5.6" +"turbo-darwin-64@npm:2.5.8": + version: 2.5.8 + resolution: "turbo-darwin-64@npm:2.5.8" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"turbo-darwin-arm64@npm:2.5.6": - version: 2.5.6 - resolution: "turbo-darwin-arm64@npm:2.5.6" +"turbo-darwin-arm64@npm:2.5.8": + version: 2.5.8 + resolution: "turbo-darwin-arm64@npm:2.5.8" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"turbo-linux-64@npm:2.5.6": - version: 2.5.6 - resolution: "turbo-linux-64@npm:2.5.6" +"turbo-linux-64@npm:2.5.8": + version: 2.5.8 + resolution: "turbo-linux-64@npm:2.5.8" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"turbo-linux-arm64@npm:2.5.6": - version: 2.5.6 - resolution: "turbo-linux-arm64@npm:2.5.6" +"turbo-linux-arm64@npm:2.5.8": + version: 2.5.8 + resolution: "turbo-linux-arm64@npm:2.5.8" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"turbo-windows-64@npm:2.5.6": - version: 2.5.6 - resolution: "turbo-windows-64@npm:2.5.6" +"turbo-windows-64@npm:2.5.8": + version: 2.5.8 + resolution: "turbo-windows-64@npm:2.5.8" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"turbo-windows-arm64@npm:2.5.6": - version: 2.5.6 - resolution: "turbo-windows-arm64@npm:2.5.6" +"turbo-windows-arm64@npm:2.5.8": + version: 2.5.8 + resolution: "turbo-windows-arm64@npm:2.5.8" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"turbo@npm:^2.5.6": - version: 2.5.6 - resolution: "turbo@npm:2.5.6" +"turbo@npm:^2.5.8": + version: 2.5.8 + resolution: "turbo@npm:2.5.8" dependencies: - turbo-darwin-64: "npm:2.5.6" - turbo-darwin-arm64: "npm:2.5.6" - turbo-linux-64: "npm:2.5.6" - turbo-linux-arm64: "npm:2.5.6" - turbo-windows-64: "npm:2.5.6" - turbo-windows-arm64: "npm:2.5.6" + turbo-darwin-64: "npm:2.5.8" + turbo-darwin-arm64: "npm:2.5.8" + turbo-linux-64: "npm:2.5.8" + turbo-linux-arm64: "npm:2.5.8" + turbo-windows-64: "npm:2.5.8" + turbo-windows-arm64: "npm:2.5.8" dependenciesMeta: turbo-darwin-64: optional: true @@ -12410,7 +12410,7 @@ __metadata: optional: true bin: turbo: bin/turbo - checksum: 10c0/b9657bf211bbcfe3911496e4fee6c9a24526bc37e9cadf7f0c76d0376e9c9dfafaacec27f5a61bb621dfe330600ce68c1b21e4cdc19bdccd3ebfe66766fcf62f + checksum: 10c0/34e8dc87fc2c5d63c3cd5aede9068c1123509d88f9bb99283ffec1687de6ad6df7ebfb83a5d348580afb3fdac53af479456e36938a1b6ed80fc1c3416c6dc3f3 languageName: node linkType: hard From 8a992151a75ec9f0baadb85ae4e559047d1a9543 Mon Sep 17 00:00:00 2001 From: Giovani Guizzo Date: Sat, 4 Oct 2025 10:36:40 -0300 Subject: [PATCH 02/10] feat: enhance manual job resolution with custom jobs file path and improve error handling --- packages/engine/src/engine.test.ts | 5 +- packages/engine/src/engine.ts | 5 + .../engine/src/shared-runner/runner.test.ts | 35 +++ tests/integration/shared-test-suite.js | 204 ++++++++++++++++-- 4 files changed, 232 insertions(+), 17 deletions(-) diff --git a/packages/engine/src/engine.test.ts b/packages/engine/src/engine.test.ts index 41f6fa3f..208323ff 100644 --- a/packages/engine/src/engine.test.ts +++ b/packages/engine/src/engine.test.ts @@ -2,6 +2,7 @@ import { sidequestTest } from "@/tests/fixture"; import { rmSync, unlinkSync, writeFileSync } from "fs"; import { platform } from "os"; import { resolve } from "path"; +import { pathToFileURL } from "url"; import { describe, expect, vi } from "vitest"; import { Engine } from "./engine"; import { MANUAL_SCRIPT_TAG } from "./shared-runner"; @@ -111,7 +112,7 @@ describe("Engine", () => { }); expect(config.manualJobResolution).toBe(true); - expect(config.jobsFilePath).toBe("./sidequest.jobs.js"); + expect(config.jobsFilePath).toBe(pathToFileURL(fileLocation).href); } finally { // Clean up the temporary file unlinkSync(fileLocation); @@ -134,7 +135,7 @@ describe("Engine", () => { }); expect(config.manualJobResolution).toBe(true); - expect(config.jobsFilePath).toBe("../sidequest.jobs.js"); + expect(config.jobsFilePath).toBe(pathToFileURL(fileLocation).href); } finally { // Clean up the temporary file unlinkSync(fileLocation); diff --git a/packages/engine/src/engine.ts b/packages/engine/src/engine.ts index 658a07f6..13d81b69 100644 --- a/packages/engine/src/engine.ts +++ b/packages/engine/src/engine.ts @@ -83,6 +83,9 @@ export interface EngineConfig { * This is useful if your `sidequest.jobs.js` file is located in a non-standard location * or if you want to explicitly specify its path. * + * This option will be resolved and changed at configuration time, and if the file does not exist, + * an error will be thrown. + * * IMPORTANT: if a relative path is provided, it will be resolved relative to the file calling the engine or * `Sidequest.configure()`, NOT the current working directory. * @@ -206,6 +209,8 @@ export class Engine { if (!existsSync(fileURLToPath(scriptUrl))) { throw new Error(`The specified jobsFilePath does not exist. Resolved to: ${scriptUrl}`); } + logger("Engine").info(`Using manual jobs file at: ${this.config!.jobsFilePath}`); + this.config!.jobsFilePath = scriptUrl; } else { // This should throw an error if not found findSidequestJobsScriptInParentDirs(); diff --git a/packages/engine/src/shared-runner/runner.test.ts b/packages/engine/src/shared-runner/runner.test.ts index 58027eb9..b4e3f6b8 100644 --- a/packages/engine/src/shared-runner/runner.test.ts +++ b/packages/engine/src/shared-runner/runner.test.ts @@ -167,6 +167,31 @@ export default { DummyJob }; }); }); + sidequestTest("runs a job with manual resolution enabled and custom path", async ({ config }) => { + // Create sidequest.jobs.js with unique name + const jobsFileContent = ` +import { DummyJob } from "./packages/engine/src/test-jobs/dummy-job.js"; + +export { DummyJob }; +export default { DummyJob }; + `; + await writeFile(sidequestJobsPath, jobsFileContent); + + // Mock the function to return our unique file + mockedFindSidequestJobsScript.mockReturnValue(`file://${sidequestJobsPath.replace(/\\/g, "/")}`); + + // Enable manual job resolution + const configWithManualResolution = { ...config, manualJobResolution: true, jobsFilePath: sidequestJobsPath }; + + const result = await run({ jobData, config: configWithManualResolution }); + + expect(result).toEqual({ + __is_job_transition__: true, + type: "completed", + result: "dummy job", + }); + }); + sidequestTest("fails when sidequest.jobs.js file is not found", async ({ config }) => { // Mock the function to throw an error mockedFindSidequestJobsScript.mockImplementation(() => { @@ -182,6 +207,16 @@ export default { DummyJob }; expect(result.error.message).toMatch(/not found in.*or any parent directory/); }); + sidequestTest("fails when sidequest.jobs.js file is not found in custom path", async ({ config }) => { + // Enable manual job resolution + const configWithManualResolution = { ...config, manualJobResolution: true, jobsFilePath: "./non-existing" }; + + const result = (await run({ jobData, config: configWithManualResolution })) as FailedResult; + + expect(result.type).toEqual("failed"); + expect(result.error.message).toMatch(/does not exist/); + }); + sidequestTest("fails when job class is not exported in sidequest.jobs.js", async ({ config }) => { // Create sidequest.jobs.js without the required job class const jobsFileContent = ` diff --git a/tests/integration/shared-test-suite.js b/tests/integration/shared-test-suite.js index 56dcc302..7cabe68b 100644 --- a/tests/integration/shared-test-suite.js +++ b/tests/integration/shared-test-suite.js @@ -1,6 +1,7 @@ import { existsSync } from "node:fs"; import { unlink, writeFile } from "node:fs/promises"; import path from "node:path"; +import { pathToFileURL } from "node:url"; import { MANUAL_SCRIPT_TAG, ScheduledJobRegistry } from "sidequest"; import { afterAll, afterEach, beforeAll, describe, expect, test, vi } from "vitest"; @@ -48,18 +49,21 @@ export function createIntegrationTestSuite(Sidequest, jobs, moduleType = "ESM") queues: [{ name: "default" }], }); - const jobBuilder = Sidequest.build(EnqueueFromWithinJob); - const jobData = await jobBuilder.enqueue(); + const jobBuilder = Sidequest.build(SuccessJob); + const jobData = await jobBuilder.enqueue("Hello World"); expect(jobData.id).toBeDefined(); expect(jobData.state).toBe("waiting"); - expect(jobData.class).toBe("EnqueueFromWithinJob"); + expect(jobData.class).toBe("SuccessJob"); // Wait for job to be processed await vi.waitUntil(async () => { - const jobs = await Sidequest.job.list(); - return jobs.length === 2 && jobs.every((job) => job.state === "completed"); - }, 5000); + const job = await Sidequest.job.get(jobData.id); + return job?.state === "completed"; + }); + + const processedJob = await Sidequest.job.get(jobData.id); + expect(processedJob?.state).toBe("completed"); }); test(`[${moduleType}] should start Sidequest and execute a job that enqueues another job`, async () => { @@ -68,21 +72,21 @@ export function createIntegrationTestSuite(Sidequest, jobs, moduleType = "ESM") queues: [{ name: "default" }], }); - const jobBuilder = Sidequest.build(SuccessJob); - const jobData = await jobBuilder.enqueue("Hello World"); + const jobs = await Sidequest.job.list(); + expect(jobs.length).toBe(0); + + const jobBuilder = Sidequest.build(EnqueueFromWithinJob); + const jobData = await jobBuilder.enqueue(); expect(jobData.id).toBeDefined(); expect(jobData.state).toBe("waiting"); - expect(jobData.class).toBe("SuccessJob"); + expect(jobData.class).toBe("EnqueueFromWithinJob"); // Wait for job to be processed await vi.waitUntil(async () => { - const job = await Sidequest.job.get(jobData.id); - return job?.state === "completed"; - }); - - const processedJob = await Sidequest.job.get(jobData.id); - expect(processedJob?.state).toBe("completed"); + const jobs = await Sidequest.job.list(); + return jobs.length === 2 && jobs.every((job) => job.state === "completed"); + }, 5000); }); test(`[${moduleType}] should enqueue multiple jobs and execute them all`, async () => { @@ -648,6 +652,7 @@ export function createIntegrationTestSuite(Sidequest, jobs, moduleType = "ESM") describe(`[${moduleType}] Manual Job Resolution`, () => { const projectRoot = path.resolve(process.cwd()); const manualJobsPath = path.join(projectRoot, "sidequest.jobs.js"); + const manualJobsPathCustom = path.join(projectRoot, "tests", "integration", "sidequest.jobs.js"); beforeAll(async () => { const manualJobsContent = ` @@ -656,6 +661,13 @@ import { SuccessJob, RetryJob, FailingJob, TimeoutJob, EnqueueFromWithinJob } fr export { SuccessJob, RetryJob, FailingJob, TimeoutJob, EnqueueFromWithinJob }; `; await writeFile(manualJobsPath, manualJobsContent); + + const manualJobsContentCustom = ` +import { SuccessJob, RetryJob, FailingJob, TimeoutJob, EnqueueFromWithinJob } from "./jobs/test-jobs.js"; + +export { SuccessJob, RetryJob, FailingJob, TimeoutJob, EnqueueFromWithinJob }; + `; + await writeFile(manualJobsPathCustom, manualJobsContentCustom); }); afterAll(async () => { @@ -664,6 +676,9 @@ export { SuccessJob, RetryJob, FailingJob, TimeoutJob, EnqueueFromWithinJob }; if (existsSync(manualJobsPath)) { await unlink(manualJobsPath); } + if (existsSync(manualJobsPathCustom)) { + await unlink(manualJobsPathCustom); + } } catch { // Ignore cleanup errors } @@ -739,6 +754,165 @@ export { SuccessJob, RetryJob, FailingJob, TimeoutJob, EnqueueFromWithinJob }; const processedJob = await Sidequest.job.get(jobData.id); expect(processedJob?.state).toBe("completed"); }); + + test(`[${moduleType}] should use custom jobsFilePath when provided`, async () => { + await Sidequest.start({ + ...defaultConfig, + queues: [{ name: "default" }], + manualJobResolution: true, + jobsFilePath: "./sidequest.jobs.js", // Relative path to custom jobs file + }); + + const jobBuilder = Sidequest.build(SuccessJob); + const jobData = await jobBuilder.enqueue("custom jobs path test"); + + expect(jobData.script).toBe(MANUAL_SCRIPT_TAG); + expect(jobData.class).toBe("SuccessJob"); + + // Wait for job to be processed + await vi.waitUntil(async () => { + const job = await Sidequest.job.get(jobData.id); + return job?.state === "completed"; + }, 5000); + + const processedJob = await Sidequest.job.get(jobData.id); + expect(processedJob?.state).toBe("completed"); + }); + + test(`[${moduleType}] should handle jobs with constructor arguments using custom jobsFilePath`, async () => { + await Sidequest.start({ + ...defaultConfig, + queues: [{ name: "default" }], + manualJobResolution: true, + jobsFilePath: "./sidequest.jobs.js", + }); + + const jobData = await Sidequest.build(SuccessJob) + .with("constructor-arg1", "constructor-arg2") + .enqueue("test-arg1", "test-arg2"); + + expect(jobData.script).toBe(MANUAL_SCRIPT_TAG); + expect(jobData.constructor_args).toEqual(["constructor-arg1", "constructor-arg2"]); + expect(jobData.args).toEqual(["test-arg1", "test-arg2"]); + + // Wait for job to be processed + await vi.waitUntil(async () => { + const job = await Sidequest.job.get(jobData.id); + return job?.state === "completed"; + }, 5000); + + const processedJob = await Sidequest.job.get(jobData.id); + expect(processedJob?.state).toBe("completed"); + }); + + test.only(`[${moduleType}] should handle different job types with custom jobsFilePath`, async () => { + await Sidequest.start({ + ...defaultConfig, + queues: [{ name: "default" }], + manualJobResolution: true, + jobsFilePath: "./sidequest.jobs.js", + }); + + // Test SuccessJob + const successJob = await Sidequest.build(SuccessJob).enqueue("success-test"); + expect(successJob.script).toBe(MANUAL_SCRIPT_TAG); + + // Test RetryJob + const retryJob = await Sidequest.build(RetryJob).maxAttempts(2).enqueue("retry-test"); + expect(retryJob.script).toBe(MANUAL_SCRIPT_TAG); + + // Wait for all jobs to complete + await vi.waitUntil(async () => { + const jobs = await Sidequest.job.list(); + return jobs.length === 2 && jobs.every((job) => job.state === "completed"); + }, 5000); + + const allJobs = await Sidequest.job.list(); + expect(allJobs).toHaveLength(2); + allJobs.forEach((job) => { + expect(job.state).toBe("completed"); + }); + }); + + test(`[${moduleType}] should handle scheduled jobs with custom jobsFilePath`, async () => { + await Sidequest.start({ + ...defaultConfig, + queues: [{ name: "default" }], + manualJobResolution: true, + jobsFilePath: "./sidequest.jobs.js", + }); + + // Schedule a job to run every second + await Sidequest.build(SuccessJob).schedule("*/1 * * * * *", "scheduled-custom-path"); + + // Wait for at least 2 scheduled executions + await vi.waitUntil(async () => { + const currentJobs = await Sidequest.job.list(); + return currentJobs.length >= 2 && currentJobs.every((job) => job.state === "completed"); + }, 5000); + + const scheduledJobs = await Sidequest.job.list(); + expect(scheduledJobs.length).toBeGreaterThanOrEqual(2); + scheduledJobs.forEach((job) => { + expect(job.script).toBe(MANUAL_SCRIPT_TAG); + expect(job.state).toBe("completed"); + }); + }); + + test(`[${moduleType}] should throw error when jobsFilePath points to non-existent file`, async () => { + await expect( + Sidequest.start({ + ...defaultConfig, + queues: [{ name: "default" }], + manualJobResolution: true, + jobsFilePath: "./non-existent-jobs-file.js", + }), + ).rejects.toThrow(); + }); + + test(`[${moduleType}] should work with absolute path for jobsFilePath`, async () => { + await Sidequest.start({ + ...defaultConfig, + queues: [{ name: "default" }], + manualJobResolution: true, + jobsFilePath: manualJobsPathCustom, // Absolute path + }); + + const jobData = await Sidequest.build(SuccessJob).enqueue("absolute-path-test"); + + expect(jobData.script).toBe(MANUAL_SCRIPT_TAG); + + // Wait for job to be processed + await vi.waitUntil(async () => { + const job = await Sidequest.job.get(jobData.id); + return job?.state === "completed"; + }, 5000); + + const processedJob = await Sidequest.job.get(jobData.id); + expect(processedJob?.state).toBe("completed"); + }); + + test(`[${moduleType}] should work with file URL for jobsFilePath`, async () => { + await Sidequest.start({ + ...defaultConfig, + queues: [{ name: "default" }], + manualJobResolution: true, + jobsFilePath: pathToFileURL(manualJobsPathCustom).href, // File URL + }); + + const jobData = await Sidequest.build(SuccessJob).enqueue("absolute-path-test"); + + expect(jobData.script).toBe(MANUAL_SCRIPT_TAG); + + // Wait for job to be processed + await vi.waitUntil(async () => { + const job = await Sidequest.job.get(jobData.id); + return job?.state === "completed"; + }, 5000); + + const processedJob = await Sidequest.job.get(jobData.id); + expect(processedJob?.state).toBe("completed"); + }); }); }); } From 5de38104449f5f97fe46d4fd68731e5754ddd069 Mon Sep 17 00:00:00 2001 From: Giovani Guizzo Date: Sat, 4 Oct 2025 11:07:07 -0300 Subject: [PATCH 03/10] feat: add dashboard configuration options and enhance manual job resolution documentation --- packages/docs/engine/configuration.md | 5 +++++ packages/docs/jobs/manual-resolution.md | 25 +++++++++++++++++++++++-- packages/engine/src/engine.ts | 16 ++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/docs/engine/configuration.md b/packages/docs/engine/configuration.md index cd6e150e..47927c15 100644 --- a/packages/docs/engine/configuration.md +++ b/packages/docs/engine/configuration.md @@ -176,6 +176,9 @@ await Sidequest.start({ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------- | | `backend.driver` | Backend driver package name (SQLite, Postgres, MySQL, MongoDB) | `@sidequest/sqlite-backend` | | `backend.config` | Backend-specific connection string or [Knex configuration object](https://knexjs.org/guide/#configuration-options) | `./sidequest.sqlite` | +| `dashboard.enabled` | Whether to enable the dashboard web interface | `true` | +| `dashboard.port` | Port for the dashboard web interface | `8678` | +| `dashboard.auth` | Basic auth configuration with `user` and `password`. If omitted, no auth is required. | `undefined` | | `queues` | Array of queue configurations with name, concurrency, priority, and state | `[]` | | `maxConcurrentJobs` | Maximum number of jobs processed simultaneously across all queues | `10` | | `minThreads` | Minimum number of worker threads to use | Number of CPU cores | @@ -192,6 +195,8 @@ await Sidequest.start({ | `gracefulShutdown` | Whether to enable graceful shutdown handling | `true` | | `jobDefaults` | Default values for new jobs. Used while enqueueing | `undefined` | | `queueDefaults` | Default values for auto-created queues | `undefined` | +| `manualJobResolution` | Whether to manually resolve job classes. See [Manual Job Resolution](/jobs/manual-resolution.md) | `false` | +| `jobsFilePath` | Optional path to the file where job classes are exported. Ignored if `manualJobResolution` is `false`. | `undefined` | ::: danger If `auth` is not configured and `dashboard: true` is enabled in production, the dashboard will be publicly accessible. This is a security risk and **not recommended**. diff --git a/packages/docs/jobs/manual-resolution.md b/packages/docs/jobs/manual-resolution.md index 3dacf73e..63dbc15d 100644 --- a/packages/docs/jobs/manual-resolution.md +++ b/packages/docs/jobs/manual-resolution.md @@ -28,7 +28,7 @@ When you see `sidequest.jobs.js` as the job script, it indicates that the job cl When manual job resolution is enabled: 1. **Job Enqueuing**: Jobs are enqueued with `script: "sidequest.jobs.js"` instead of specific file paths -2. **Job Execution**: The runner looks for a `sidequest.jobs.js` file in the current directory or any parent directory +2. **Job Execution**: The runner looks for a `sidequest.jobs.js` file in the current directory or any parent directory. If the `jobsFilePath` configuration is set, it uses that path instead. 3. **Class Resolution**: Job classes are imported from the central registry file instead of individual script files ## Setting Up Manual Job Resolution @@ -45,12 +45,13 @@ await Sidequest.start({ backend: { driver: "@sidequest/sqlite-backend" }, queues: [{ name: "default" }], manualJobResolution: true, // Enable manual job resolution + jobsFilePath: "./sidequest.jobs.js", // Optional: specify custom path. If not set, defaults to searching for sidequest.jobs.js in parent dirs }); ``` ### Step 2: Create the Job Registry -Create a `sidequest.jobs.js` file in your project root (or any parent directory): +Create a `sidequest.jobs.js` file in your project root (or any parent directory), or where you specified in `jobsFilePath`: ```javascript // sidequest.jobs.js @@ -75,6 +76,8 @@ await Sidequest.build(ProcessImageJob).with(imageProcessor).enqueue("/path/to/im ## File Discovery +### Finding `sidequest.jobs.js` when `jobsFilePath` is not set + Sidequest searches for the `sidequest.jobs.js` file using the following strategy: 1. **Current Working Directory**: Starts from `process.cwd()` @@ -126,6 +129,24 @@ For example: In this case, since `sidequest.jobs.js` is at the `My Projects/` level, both worker projects can find it when they start up. If this file exports all job classes used by both projects, everything will work seamlessly. +### Finding `sidequest.jobs.js` when `jobsFilePath` is set + +If you set the `jobsFilePath` configuration option, Sidequest will use that exact path to locate the `sidequest.jobs.js` file. +This is useful if you want to place the file in a non-standard location or have multiple job registries for different environments. + +If you provide an absolute path or file URL, Sidequest will use that directly. +However, if you provide a relative path, it will be resolved relative to the file that called `Sidequest.start` or `Sidequest.configure`. + +For example, if you provide `jobsFilePath: "./config/sidequest.jobs.js"` in your main application file which is located at `/app/src/server.js`, Sidequest will look for the jobs file at `/app/src/config/sidequest.jobs.js`. + +```text +/app/ +└── src/ + ├── server.js + └── config/ + └── sidequest.jobs.js +``` + ## Best Practices ### 1. Consistent Export Strategy diff --git a/packages/engine/src/engine.ts b/packages/engine/src/engine.ts index 13d81b69..f344dd84 100644 --- a/packages/engine/src/engine.ts +++ b/packages/engine/src/engine.ts @@ -198,6 +198,22 @@ export class Engine { return this.config; } + /** + * Validates the engine configuration settings. + * + * This method also resolves the jobs file path to a file URL if manual job resolution is enabled. + * + * @throws {Error} When `maxConcurrentJobs` is defined but less than 1 + * @throws {Error} When `manualJobResolution` is enabled but the specified `jobsFilePath` does not exist + * @throws {Error} When `manualJobResolution` is enabled but no jobs script can be found in parent directories + * + * @remarks + * - Ensures `maxConcurrentJobs` is at least 1 if specified + * - When `manualJobResolution` is enabled, validates that either: + * - A valid `jobsFilePath` exists and resolves it to an absolute URL + * - A sidequest jobs script can be found in parent directories + * - Logs the resolved jobs file path when using manual job resolution + */ validateConfig() { if (this.config!.maxConcurrentJobs !== undefined && this.config!.maxConcurrentJobs < 1) { throw new Error(`Invalid "maxConcurrentJobs" value: must be at least 1.`); From 33c518ef717fdfe60e5b9ac1b58b9d1a6a3151b0 Mon Sep 17 00:00:00 2001 From: Giovani Guizzo Date: Sat, 4 Oct 2025 11:16:25 -0300 Subject: [PATCH 04/10] feat: rename resolveScriptPath to resolveScriptPathForJob and update references --- packages/core/src/job/job.test.ts | 12 ++++++------ packages/core/src/job/job.ts | 10 +++++----- packages/engine/src/shared-runner/runner.ts | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/core/src/job/job.test.ts b/packages/core/src/job/job.test.ts index 52804cf3..74706418 100644 --- a/packages/core/src/job/job.test.ts +++ b/packages/core/src/job/job.test.ts @@ -1,7 +1,7 @@ import { CompletedResult, RetryResult, SnoozeResult } from "@sidequest/core"; import path from "path"; import { pathToFileURL } from "url"; -import { Job, resolveScriptPath } from "./job"; +import { Job, resolveScriptPathForJob } from "./job"; export class DummyJob extends Job { // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -116,16 +116,16 @@ describe("job.ts", () => { }); }); -describe("resolveScriptPath", () => { +describe("resolveScriptPathForJob", () => { it("should return the input if it is already a file URL", () => { const fileUrl = "file:///some/path/to/file.js"; - expect(resolveScriptPath(fileUrl)).toBe(fileUrl); + expect(resolveScriptPathForJob(fileUrl)).toBe(fileUrl); }); it("should convert an absolute path to a file URL", () => { const absPath = path.resolve("/tmp/test.js"); const expected = pathToFileURL(absPath).href; - expect(resolveScriptPath(absPath)).toBe(expected); + expect(resolveScriptPathForJob(absPath)).toBe(expected); }); it("should resolve a relative path to a file URL based on import.meta.dirname", () => { @@ -133,7 +133,7 @@ describe("resolveScriptPath", () => { const baseDir = import.meta?.dirname ? import.meta.dirname : __dirname; const absPath = path.resolve(baseDir, relativePath); const expected = pathToFileURL(absPath).href; - expect(resolveScriptPath(relativePath)).toBe(expected); + expect(resolveScriptPathForJob(relativePath)).toBe(expected); }); it("should handle relative paths with ./ and ../", () => { @@ -141,6 +141,6 @@ describe("resolveScriptPath", () => { const baseDir = import.meta?.dirname ? import.meta.dirname : __dirname; const absPath = path.resolve(baseDir, relativePath); const expected = pathToFileURL(absPath).href; - expect(resolveScriptPath(relativePath)).toBe(expected); + expect(resolveScriptPathForJob(relativePath)).toBe(expected); }); }); diff --git a/packages/core/src/job/job.ts b/packages/core/src/job/job.ts index ddef6ea4..e32bd9c7 100644 --- a/packages/core/src/job/job.ts +++ b/packages/core/src/job/job.ts @@ -255,16 +255,16 @@ async function buildPath(className: string) { const filePaths = parseStackTrace(err); for (const filePath of filePaths) { - const hasExported = await hasClassExported(filePath!, className); + const hasExported = await hasClassExported(filePath, className); if (hasExported) { - const relativePath = path.relative(import.meta.dirname, filePath!); + const relativePath = path.relative(import.meta.dirname, filePath); logger("Job").debug(`${filePath} exports class ${className}, relative path: ${relativePath}`); return relativePath.replaceAll("\\", "/"); } } if (filePaths.length > 0) { - const relativePath = path.relative(import.meta.dirname, filePaths[0]!); + const relativePath = path.relative(import.meta.dirname, filePaths[0]); logger("Job").debug(`No class ${className} found in stack, returning first file path: ${relativePath}`); return relativePath.replaceAll("\\", "/"); } @@ -285,11 +285,11 @@ async function buildPath(className: string) { * * @example * ```typescript - * const scriptUrl = resolveScriptPath("../../../examples/hello-job.js"); + * const scriptUrl = resolveScriptPathForJob("../../../examples/hello-job.js"); * const module = await import(scriptUrl); * ``` */ -export function resolveScriptPath(relativePath: string): string { +export function resolveScriptPathForJob(relativePath: string): string { // If it's already a file URL, return as-is if (relativePath.startsWith("file://")) { return relativePath; diff --git a/packages/engine/src/shared-runner/runner.ts b/packages/engine/src/shared-runner/runner.ts index 0bb4cbb6..8e31abde 100644 --- a/packages/engine/src/shared-runner/runner.ts +++ b/packages/engine/src/shared-runner/runner.ts @@ -1,9 +1,9 @@ -import { Job, JobClassType, JobData, JobResult, logger, resolveScriptPath, toErrorData } from "@sidequest/core"; +import { Job, JobClassType, JobData, JobResult, logger, resolveScriptPathForJob, toErrorData } from "@sidequest/core"; import { existsSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { EngineConfig } from "../engine"; import { importSidequest } from "../utils"; -import { findSidequestJobsScriptInParentDirs, MANUAL_SCRIPT_TAG } from "./manual-loader"; +import { findSidequestJobsScriptInParentDirs, MANUAL_SCRIPT_TAG, resolveScriptPath } from "./manual-loader"; /** * Runs a job by dynamically importing its script and executing the specified class. @@ -43,7 +43,7 @@ export default async function run({ jobData, config }: { jobData: JobData; confi } else { logger("Runner").debug("Manual job resolution is disabled; importing specified job script."); // Convert relative path to absolute file URL for dynamic import - scriptUrl = resolveScriptPath(jobData.script); + scriptUrl = resolveScriptPathForJob(jobData.script); } script = (await import(scriptUrl)) as Record; From 14ed51fd0bd28bfc0ba4337cef4c33e2198bee40 Mon Sep 17 00:00:00 2001 From: Giovani Guizzo Date: Sat, 4 Oct 2025 11:24:54 -0300 Subject: [PATCH 05/10] feat: update manual-loader mock to include resolveScriptPath and improve error message clarity --- packages/engine/src/shared-runner/runner.test.ts | 14 +++++++++----- tests/integration/shared-test-suite.js | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/engine/src/shared-runner/runner.test.ts b/packages/engine/src/shared-runner/runner.test.ts index b4e3f6b8..555cd270 100644 --- a/packages/engine/src/shared-runner/runner.test.ts +++ b/packages/engine/src/shared-runner/runner.test.ts @@ -14,10 +14,14 @@ vi.mock("../utils/import", () => ({ })); // Mock the manual loader to control which file it returns -vi.mock("./manual-loader", () => ({ - findSidequestJobsScriptInParentDirs: vi.fn(), - MANUAL_SCRIPT_TAG: "sidequest.jobs.js", -})); +vi.mock("./manual-loader", async (importOriginal) => { + const originalModule = await importOriginal(); + return { + findSidequestJobsScriptInParentDirs: vi.fn(), + resolveScriptPath: originalModule.resolveScriptPath, + MANUAL_SCRIPT_TAG: "sidequest.jobs.js", + }; +}); import { findSidequestJobsScriptInParentDirs, MANUAL_SCRIPT_TAG } from "./manual-loader"; @@ -214,7 +218,7 @@ export default { DummyJob }; const result = (await run({ jobData, config: configWithManualResolution })) as FailedResult; expect(result.type).toEqual("failed"); - expect(result.error.message).toMatch(/does not exist/); + expect(result.error.message).toMatch(/Unable to resolve script path/); }); sidequestTest("fails when job class is not exported in sidequest.jobs.js", async ({ config }) => { diff --git a/tests/integration/shared-test-suite.js b/tests/integration/shared-test-suite.js index 7cabe68b..a89f7cdb 100644 --- a/tests/integration/shared-test-suite.js +++ b/tests/integration/shared-test-suite.js @@ -805,7 +805,7 @@ export { SuccessJob, RetryJob, FailingJob, TimeoutJob, EnqueueFromWithinJob }; expect(processedJob?.state).toBe("completed"); }); - test.only(`[${moduleType}] should handle different job types with custom jobsFilePath`, async () => { + test(`[${moduleType}] should handle different job types with custom jobsFilePath`, async () => { await Sidequest.start({ ...defaultConfig, queues: [{ name: "default" }], From cd0630a7e3332e57a253171a47009530c0b1c4e4 Mon Sep 17 00:00:00 2001 From: Giovani Guizzo Date: Sat, 4 Oct 2025 11:33:55 -0300 Subject: [PATCH 06/10] feat: update resolveScriptPath tests for cross-platform compatibility and improve error handling --- .../src/shared-runner/manual-loader.test.ts | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/engine/src/shared-runner/manual-loader.test.ts b/packages/engine/src/shared-runner/manual-loader.test.ts index 9bd2630d..e1926a20 100644 --- a/packages/engine/src/shared-runner/manual-loader.test.ts +++ b/packages/engine/src/shared-runner/manual-loader.test.ts @@ -1,4 +1,5 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { platform } from "node:os"; import { join, resolve } from "node:path"; import { pathToFileURL } from "node:url"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -330,7 +331,12 @@ describe("resolveScriptPath", () => { }); describe("absolute path handling", () => { - it("should convert Windows absolute path to file URL", () => { + it("should convert Windows absolute path to file URL", (context) => { + if (platform() !== "win32") { + context.skip(); + return; + } + const absolutePath = "C:\\Users\\test\\project\\script.js"; const result = resolveScriptPath(absolutePath); @@ -358,21 +364,12 @@ describe("resolveScriptPath", () => { }); it("should handle absolute path with special characters", () => { - const absolutePath = "C:\\Users\\test@123\\project_name\\script-file.js"; + const absolutePath = "/Users/test@123/project_name/script-file.js"; const result = resolveScriptPath(absolutePath); expect(result).toBe(pathToFileURL(absolutePath).href); }); - - it("should work with absolute paths on Windows", () => { - const windowsPath = "D:\\Projects\\MyApp\\src\\index.js"; - - const result = resolveScriptPath(windowsPath); - - expect(result).toBe(pathToFileURL(windowsPath).href); - expect(result).toMatch(/^file:\/\//); - }); }); describe("relative path handling", () => { @@ -467,7 +464,7 @@ describe("resolveScriptPath", () => { describe("edge cases", () => { it("should handle paths without extension", () => { - const absolutePath = "C:\\Users\\test\\project\\Dockerfile"; + const absolutePath = "/Users/test/project/Dockerfile"; const result = resolveScriptPath(absolutePath); @@ -475,7 +472,7 @@ describe("resolveScriptPath", () => { }); it("should handle paths with multiple dots", () => { - const absolutePath = "C:\\Users\\test\\project\\script.test.js"; + const absolutePath = "/Users/test/project/script.test.js"; const result = resolveScriptPath(absolutePath); @@ -483,7 +480,7 @@ describe("resolveScriptPath", () => { }); it("should handle very long paths", () => { - const longPath = "C:\\Users\\test\\" + "very-long-directory-name\\".repeat(20) + "script.js"; + const longPath = "/Users/test/" + "very-long-directory-name/".repeat(20) + "script.js"; const result = resolveScriptPath(longPath); @@ -491,7 +488,7 @@ describe("resolveScriptPath", () => { }); it("should trim whitespace from input", () => { - const absolutePath = " C:\\Users\\test\\project\\script.js "; + const absolutePath = " /Users/test/project/script.js "; const result = resolveScriptPath(absolutePath); @@ -500,7 +497,7 @@ describe("resolveScriptPath", () => { }); it("should handle file names with unicode characters", () => { - const absolutePath = "C:\\Users\\test\\项目\\脚本.js"; + const absolutePath = "/Users/test/项目/脚本.js"; const result = resolveScriptPath(absolutePath); @@ -508,7 +505,7 @@ describe("resolveScriptPath", () => { }); it("should handle paths with dots in directory names", () => { - const absolutePath = "C:\\Users\\test\\.hidden\\script.js"; + const absolutePath = "/Users/test/.hidden/script.js"; const result = resolveScriptPath(absolutePath); From fd582c7b743c0a5f981d10ca2b3de56798c2cd4b Mon Sep 17 00:00:00 2001 From: Giovani Guizzo Date: Sat, 4 Oct 2025 11:40:36 -0300 Subject: [PATCH 07/10] fix: improve error handling in afterAll cleanup for MANUAL_SCRIPT_TAG file --- packages/engine/src/engine.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/engine/src/engine.test.ts b/packages/engine/src/engine.test.ts index 208323ff..2b7ec97b 100644 --- a/packages/engine/src/engine.test.ts +++ b/packages/engine/src/engine.test.ts @@ -45,7 +45,11 @@ describe("Engine", () => { afterAll(() => { // Clean up the MANUAL_SCRIPT_TAG file after tests - rmSync(MANUAL_SCRIPT_TAG); + try { + rmSync(MANUAL_SCRIPT_TAG); + } catch { + // Ignore errors if file doesn't exist + } }); describe("validateConfig", () => { From 2044e0f29dd7e7f74206bd259360370169d5d92a Mon Sep 17 00:00:00 2001 From: Giovani Guizzo Date: Sat, 4 Oct 2025 11:43:26 -0300 Subject: [PATCH 08/10] refactor: move MANUAL_SCRIPT_TAG setup and teardown to manualJobResolution tests --- packages/engine/src/engine.test.ts | 31 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/engine/src/engine.test.ts b/packages/engine/src/engine.test.ts index 2b7ec97b..b21f5028 100644 --- a/packages/engine/src/engine.test.ts +++ b/packages/engine/src/engine.test.ts @@ -38,20 +38,6 @@ export class ParameterizedJob extends DummyJob { } describe("Engine", () => { - beforeAll(() => { - // Ensure MANUAL_SCRIPT_TAG file exists for tests that rely on it - writeFileSync(MANUAL_SCRIPT_TAG, "dummy"); - }); - - afterAll(() => { - // Clean up the MANUAL_SCRIPT_TAG file after tests - try { - rmSync(MANUAL_SCRIPT_TAG); - } catch { - // Ignore errors if file doesn't exist - } - }); - describe("validateConfig", () => { sidequestTest("should throw error when maxConcurrentJobs is negative", async () => { const engine = new Engine(); @@ -162,6 +148,8 @@ describe("Engine", () => { const engine = new Engine(); const customAvailableAt = new Date("2025-01-01"); + writeFileSync(MANUAL_SCRIPT_TAG, "dummy"); + const config = await engine.configure({ backend: { driver: "@sidequest/sqlite-backend", config: ":memory:" }, queues: [{ name: "test-queue", concurrency: 5 }], @@ -218,6 +206,7 @@ describe("Engine", () => { expect(config.idleWorkerTimeout).toBe(600); expect(config.manualJobResolution).toBe(true); + rmSync(MANUAL_SCRIPT_TAG); await engine.close(); }); @@ -679,6 +668,20 @@ describe("Engine", () => { }); describe("manualJobResolution", () => { + beforeAll(() => { + // Ensure MANUAL_SCRIPT_TAG file exists for tests that rely on it + writeFileSync(MANUAL_SCRIPT_TAG, "dummy"); + }); + + afterAll(() => { + // Clean up the MANUAL_SCRIPT_TAG file after tests + try { + rmSync(MANUAL_SCRIPT_TAG); + } catch { + // Ignore errors if file doesn't exist + } + }); + sidequestTest("should configure engine with manualJobResolution enabled", async () => { const engine = new Engine(); const config = await engine.configure({ From 32c1d9702412fd1a8c2e88c742f2eb3ce0ef6e46 Mon Sep 17 00:00:00 2001 From: Giovani Guizzo Date: Sat, 4 Oct 2025 11:55:27 -0300 Subject: [PATCH 09/10] feat: change to better-sqlite3 --- packages/backends/sqlite/package.json | 4 +- .../backends/sqlite/src/sqlite-backend.ts | 2 +- yarn.lock | 439 ++---------------- 3 files changed, 31 insertions(+), 414 deletions(-) diff --git a/packages/backends/sqlite/package.json b/packages/backends/sqlite/package.json index 6856b7cc..20120190 100644 --- a/packages/backends/sqlite/package.json +++ b/packages/backends/sqlite/package.json @@ -45,8 +45,8 @@ "dependencies": { "@sidequest/backend": "workspace:*", "@sidequest/core": "workspace:*", - "knex": "^3.1.0", - "sqlite3": "^5.1.7" + "better-sqlite3": "^12.4.1", + "knex": "^3.1.0" }, "devDependencies": { "@sidequest/backend-test": "workspace:*" diff --git a/packages/backends/sqlite/src/sqlite-backend.ts b/packages/backends/sqlite/src/sqlite-backend.ts index 0d500892..e0874bfa 100644 --- a/packages/backends/sqlite/src/sqlite-backend.ts +++ b/packages/backends/sqlite/src/sqlite-backend.ts @@ -5,7 +5,7 @@ import { hostname } from "os"; import path from "path"; const defaultKnexConfig = { - client: "sqlite3", + client: "better-sqlite3", useNullAsDefault: true, migrations: { directory: path.join(import.meta.dirname, "..", "migrations"), diff --git a/yarn.lock b/yarn.lock index 342f340b..eded6bcc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -995,13 +995,6 @@ __metadata: languageName: node linkType: hard -"@gar/promisify@npm:^1.0.1": - version: 1.1.3 - resolution: "@gar/promisify@npm:1.1.3" - checksum: 10c0/0b3c9958d3cd17f4add3574975e3115ae05dc7f1298a60810414b16f6f558c137b5fb3cd3905df380bacfd955ec13f67c1e6710cbb5c246a7e8d65a8289b2bff - languageName: node - linkType: hard - "@highlightjs/cdn-assets@npm:^11.11.1": version: 11.11.1 resolution: "@highlightjs/cdn-assets@npm:11.11.1" @@ -1700,16 +1693,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/fs@npm:^1.0.0": - version: 1.1.1 - resolution: "@npmcli/fs@npm:1.1.1" - dependencies: - "@gar/promisify": "npm:^1.0.1" - semver: "npm:^7.3.5" - checksum: 10c0/4143c317a7542af9054018b71601e3c3392e6704e884561229695f099a71336cbd580df9a9ffb965d0024bf0ed593189ab58900fd1714baef1c9ee59c738c3e2 - languageName: node - linkType: hard - "@npmcli/fs@npm:^4.0.0": version: 4.0.0 resolution: "@npmcli/fs@npm:4.0.0" @@ -1772,16 +1755,6 @@ __metadata: languageName: node linkType: hard -"@npmcli/move-file@npm:^1.0.1": - version: 1.1.2 - resolution: "@npmcli/move-file@npm:1.1.2" - dependencies: - mkdirp: "npm:^1.0.4" - rimraf: "npm:^3.0.2" - checksum: 10c0/02e946f3dafcc6743132fe2e0e2b585a96ca7265653a38df5a3e53fcf26c7c7a57fc0f861d7c689a23fdb6d6836c7eea5050c8086abf3c994feb2208d1514ff0 - languageName: node - linkType: hard - "@npmcli/name-from-folder@npm:^3.0.0": version: 3.0.0 resolution: "@npmcli/name-from-folder@npm:3.0.0" @@ -2754,8 +2727,8 @@ __metadata: "@sidequest/backend": "workspace:*" "@sidequest/backend-test": "workspace:*" "@sidequest/core": "workspace:*" + better-sqlite3: "npm:^12.4.1" knex: "npm:^3.1.0" - sqlite3: "npm:^5.1.7" languageName: unknown linkType: soft @@ -3021,13 +2994,6 @@ __metadata: languageName: node linkType: hard -"@tootallnate/once@npm:1": - version: 1.1.2 - resolution: "@tootallnate/once@npm:1.1.2" - checksum: 10c0/8fe4d006e90422883a4fa9339dd05a83ff626806262e1710cee5758d493e8cbddf2db81c0e4690636dc840b02c9fda62877866ea774ebd07c1777ed5fafbdec6 - languageName: node - linkType: hard - "@trysound/sax@npm:0.2.0": version: 0.2.0 resolution: "@trysound/sax@npm:0.2.0" @@ -3828,13 +3794,6 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:1": - version: 1.1.1 - resolution: "abbrev@npm:1.1.1" - checksum: 10c0/3f762677702acb24f65e813070e306c61fafe25d4b2583f9dfc935131f774863f3addd5741572ed576bd69cabe473c5af18e1e108b829cb7b6b4747884f726e6 - languageName: node - linkType: hard - "abbrev@npm:^3.0.0, abbrev@npm:^3.0.1": version: 3.0.1 resolution: "abbrev@npm:3.0.1" @@ -3870,15 +3829,6 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:6, agent-base@npm:^6.0.2": - version: 6.0.2 - resolution: "agent-base@npm:6.0.2" - dependencies: - debug: "npm:4" - checksum: 10c0/dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 - languageName: node - linkType: hard - "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.4 resolution: "agent-base@npm:7.1.4" @@ -3886,15 +3836,6 @@ __metadata: languageName: node linkType: hard -"agentkeepalive@npm:^4.1.3": - version: 4.6.0 - resolution: "agentkeepalive@npm:4.6.0" - dependencies: - humanize-ms: "npm:^1.2.1" - checksum: 10c0/235c182432f75046835b05f239708107138a40103deee23b6a08caee5136873709155753b394ec212e49e60e94a378189562cb01347765515cff61b692c69187 - languageName: node - linkType: hard - "aggregate-error@npm:^3.0.0": version: 3.1.0 resolution: "aggregate-error@npm:3.1.0" @@ -4035,7 +3976,7 @@ __metadata: languageName: node linkType: hard -"aproba@npm:^1.0.3 || ^2.0.0, aproba@npm:^2.0.0": +"aproba@npm:^2.0.0": version: 2.1.0 resolution: "aproba@npm:2.1.0" checksum: 10c0/ec8c1d351bac0717420c737eb062766fb63bde1552900e0f4fdad9eb064c3824fef23d1c416aa5f7a80f21ca682808e902d79b7c9ae756d342b5f1884f36932f @@ -4049,16 +3990,6 @@ __metadata: languageName: node linkType: hard -"are-we-there-yet@npm:^3.0.0": - version: 3.0.1 - resolution: "are-we-there-yet@npm:3.0.1" - dependencies: - delegates: "npm:^1.0.0" - readable-stream: "npm:^3.6.0" - checksum: 10c0/8373f289ba42e4b5ec713bb585acdac14b5702c75f2a458dc985b9e4fa5762bc5b46b40a21b72418a3ed0cfb5e35bdc317ef1ae132f3035f633d581dd03168c3 - languageName: node - linkType: hard - "argparse@npm:^2.0.1": version: 2.0.1 resolution: "argparse@npm:2.0.1" @@ -4149,6 +4080,17 @@ __metadata: languageName: node linkType: hard +"better-sqlite3@npm:^12.4.1": + version: 12.4.1 + resolution: "better-sqlite3@npm:12.4.1" + dependencies: + bindings: "npm:^1.5.0" + node-gyp: "npm:latest" + prebuild-install: "npm:^7.1.1" + checksum: 10c0/88773a75d996b4171e5690a38459b05dc814a792701b224bd9909ee084dc0b4c64aaffbdbcf4bbbc6d4e247faf19e91b2a56cf4175d746d3bd9ff14764eb05aa + languageName: node + linkType: hard + "bin-links@npm:^5.0.0": version: 5.0.0 resolution: "bin-links@npm:5.0.0" @@ -4300,32 +4242,6 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^15.2.0": - version: 15.3.0 - resolution: "cacache@npm:15.3.0" - dependencies: - "@npmcli/fs": "npm:^1.0.0" - "@npmcli/move-file": "npm:^1.0.1" - chownr: "npm:^2.0.0" - fs-minipass: "npm:^2.0.0" - glob: "npm:^7.1.4" - infer-owner: "npm:^1.0.4" - lru-cache: "npm:^6.0.0" - minipass: "npm:^3.1.1" - minipass-collect: "npm:^1.0.2" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.2" - mkdirp: "npm:^1.0.3" - p-map: "npm:^4.0.0" - promise-inflight: "npm:^1.0.1" - rimraf: "npm:^3.0.2" - ssri: "npm:^8.0.1" - tar: "npm:^6.0.2" - unique-filename: "npm:^1.1.1" - checksum: 10c0/886fcc0acc4f6fd5cd142d373d8276267bc6d655d7c4ce60726fbbec10854de3395ee19bbf9e7e73308cdca9fdad0ad55060ff3bd16c6d4165c5b8d21515e1d8 - languageName: node - linkType: hard - "cacache@npm:^19.0.0, cacache@npm:^19.0.1": version: 19.0.1 resolution: "cacache@npm:19.0.1" @@ -4713,15 +4629,6 @@ __metadata: languageName: node linkType: hard -"color-support@npm:^1.1.3": - version: 1.1.3 - resolution: "color-support@npm:1.1.3" - bin: - color-support: bin.js - checksum: 10c0/8ffeaa270a784dc382f62d9be0a98581db43e11eee301af14734a6d089bd456478b1a8b3e7db7ca7dc5b18a75f828f775c44074020b51c05fc00e6d0992b1cc6 - languageName: node - linkType: hard - "color@npm:^3.1.3": version: 3.2.1 resolution: "color@npm:3.2.1" @@ -4841,13 +4748,6 @@ __metadata: languageName: node linkType: hard -"console-control-strings@npm:^1.1.0": - version: 1.1.0 - resolution: "console-control-strings@npm:1.1.0" - checksum: 10c0/7ab51d30b52d461412cd467721bb82afe695da78fff8f29fe6f6b9cbaac9a2328e27a22a966014df9532100f6dd85370460be8130b9c677891ba36d96a343f50 - languageName: node - linkType: hard - "content-disposition@npm:^1.0.0": version: 1.0.0 resolution: "content-disposition@npm:1.0.0" @@ -5205,7 +5105,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.1": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.1": version: 4.4.1 resolution: "debug@npm:4.4.1" dependencies: @@ -5280,13 +5180,6 @@ __metadata: languageName: node linkType: hard -"delegates@npm:^1.0.0": - version: 1.0.0 - resolution: "delegates@npm:1.0.0" - checksum: 10c0/ba05874b91148e1db4bf254750c042bf2215febd23a6d3cda2e64896aef79745fbd4b9996488bd3cafb39ce19dbce0fd6e3b6665275638befffe1c9b312b91b5 - languageName: node - linkType: hard - "denque@npm:^2.1.0": version: 2.1.0 resolution: "denque@npm:2.1.0" @@ -5531,7 +5424,7 @@ __metadata: languageName: node linkType: hard -"encoding@npm:^0.1.12, encoding@npm:^0.1.13": +"encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" dependencies: @@ -6514,22 +6407,6 @@ __metadata: languageName: node linkType: hard -"gauge@npm:^4.0.3": - version: 4.0.4 - resolution: "gauge@npm:4.0.4" - dependencies: - aproba: "npm:^1.0.3 || ^2.0.0" - color-support: "npm:^1.1.3" - console-control-strings: "npm:^1.1.0" - has-unicode: "npm:^2.0.1" - signal-exit: "npm:^3.0.7" - string-width: "npm:^4.2.3" - strip-ansi: "npm:^6.0.1" - wide-align: "npm:^1.1.5" - checksum: 10c0/ef10d7981113d69225135f994c9f8c4369d945e64a8fc721d655a3a38421b738c9fe899951721d1b47b73c41fdb5404ac87cc8903b2ecbed95d2800363e7e58c - languageName: node - linkType: hard - "generate-function@npm:^2.3.1": version: 2.3.1 resolution: "generate-function@npm:2.3.1" @@ -6721,7 +6598,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.3, glob@npm:^7.1.4": +"glob@npm:^7.1.3": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -6855,13 +6732,6 @@ __metadata: languageName: node linkType: hard -"has-unicode@npm:^2.0.1": - version: 2.0.1 - resolution: "has-unicode@npm:2.0.1" - checksum: 10c0/ebdb2f4895c26bb08a8a100b62d362e49b2190bcfd84b76bc4be1a3bd4d254ec52d0dd9f2fbcc093fc5eb878b20c52146f9dfd33e2686ed28982187be593b47c - languageName: node - linkType: hard - "hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" @@ -6971,7 +6841,7 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.0, http-cache-semantics@npm:^4.1.1": +"http-cache-semantics@npm:^4.1.1": version: 4.2.0 resolution: "http-cache-semantics@npm:4.2.0" checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 @@ -6991,17 +6861,6 @@ __metadata: languageName: node linkType: hard -"http-proxy-agent@npm:^4.0.1": - version: 4.0.1 - resolution: "http-proxy-agent@npm:4.0.1" - dependencies: - "@tootallnate/once": "npm:1" - agent-base: "npm:6" - debug: "npm:4" - checksum: 10c0/4fa4774d65b5331814b74ac05cefea56854fc0d5989c80b13432c1b0d42a14c9f4342ca3ad9f0359a52e78da12b1744c9f8a28e50042136ea9171675d972a5fd - languageName: node - linkType: hard - "http-proxy-agent@npm:^7.0.0": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" @@ -7012,16 +6871,6 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^5.0.0": - version: 5.0.1 - resolution: "https-proxy-agent@npm:5.0.1" - dependencies: - agent-base: "npm:6" - debug: "npm:4" - checksum: 10c0/6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 - languageName: node - linkType: hard - "https-proxy-agent@npm:^7.0.0, https-proxy-agent@npm:^7.0.1": version: 7.0.6 resolution: "https-proxy-agent@npm:7.0.6" @@ -7053,15 +6902,6 @@ __metadata: languageName: node linkType: hard -"humanize-ms@npm:^1.2.1": - version: 1.2.1 - resolution: "humanize-ms@npm:1.2.1" - dependencies: - ms: "npm:^2.0.0" - checksum: 10c0/f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a - languageName: node - linkType: hard - "husky@npm:^9.1.7": version: 9.1.7 resolution: "husky@npm:9.1.7" @@ -7208,13 +7048,6 @@ __metadata: languageName: node linkType: hard -"infer-owner@npm:^1.0.4": - version: 1.0.4 - resolution: "infer-owner@npm:1.0.4" - checksum: 10c0/a7b241e3149c26e37474e3435779487f42f36883711f198c45794703c7556bc38af224088bd4d1a221a45b8208ae2c2bcf86200383621434d0c099304481c5b9 - languageName: node - linkType: hard - "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -7390,13 +7223,6 @@ __metadata: languageName: node linkType: hard -"is-lambda@npm:^1.0.1": - version: 1.0.1 - resolution: "is-lambda@npm:1.0.1" - checksum: 10c0/85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d - languageName: node - linkType: hard - "is-module@npm:^1.0.0": version: 1.0.0 resolution: "is-module@npm:1.0.0" @@ -8287,15 +8113,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^6.0.0": - version: 6.0.0 - resolution: "lru-cache@npm:6.0.0" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 - languageName: node - linkType: hard - "lru-cache@npm:^7.14.1": version: 7.18.3 resolution: "lru-cache@npm:7.18.3" @@ -8358,30 +8175,6 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^9.1.0": - version: 9.1.0 - resolution: "make-fetch-happen@npm:9.1.0" - dependencies: - agentkeepalive: "npm:^4.1.3" - cacache: "npm:^15.2.0" - http-cache-semantics: "npm:^4.1.0" - http-proxy-agent: "npm:^4.0.1" - https-proxy-agent: "npm:^5.0.0" - is-lambda: "npm:^1.0.1" - lru-cache: "npm:^6.0.0" - minipass: "npm:^3.1.3" - minipass-collect: "npm:^1.0.2" - minipass-fetch: "npm:^1.3.2" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^0.6.2" - promise-retry: "npm:^2.0.1" - socks-proxy-agent: "npm:^6.0.0" - ssri: "npm:^8.0.0" - checksum: 10c0/2c737faf6a7f67077679da548b5bfeeef890595bf8c4323a1f76eae355d27ebb33dcf9cf1a673f944cf2f2a7cbf4e2b09f0a0a62931737728f210d902c6be966 - languageName: node - linkType: hard - "mark.js@npm:8.11.1": version: 8.11.1 resolution: "mark.js@npm:8.11.1" @@ -8636,15 +8429,6 @@ __metadata: languageName: node linkType: hard -"minipass-collect@npm:^1.0.2": - version: 1.0.2 - resolution: "minipass-collect@npm:1.0.2" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/8f82bd1f3095b24f53a991b04b67f4c710c894e518b813f0864a31de5570441a509be1ca17e0bb92b047591a8fdbeb886f502764fefb00d2f144f4011791e898 - languageName: node - linkType: hard - "minipass-collect@npm:^2.0.1": version: 2.0.1 resolution: "minipass-collect@npm:2.0.1" @@ -8654,21 +8438,6 @@ __metadata: languageName: node linkType: hard -"minipass-fetch@npm:^1.3.2": - version: 1.4.1 - resolution: "minipass-fetch@npm:1.4.1" - dependencies: - encoding: "npm:^0.1.12" - minipass: "npm:^3.1.0" - minipass-sized: "npm:^1.0.3" - minizlib: "npm:^2.0.0" - dependenciesMeta: - encoding: - optional: true - checksum: 10c0/a43da7401cd7c4f24b993887d41bd37d097356083b0bb836fd655916467463a1e6e9e553b2da4fcbe8745bf23d40c8b884eab20745562199663b3e9060cd8e7a - languageName: node - linkType: hard - "minipass-fetch@npm:^4.0.0": version: 4.0.1 resolution: "minipass-fetch@npm:4.0.1" @@ -8693,7 +8462,7 @@ __metadata: languageName: node linkType: hard -"minipass-pipeline@npm:^1.2.2, minipass-pipeline@npm:^1.2.4": +"minipass-pipeline@npm:^1.2.4": version: 1.2.4 resolution: "minipass-pipeline@npm:1.2.4" dependencies: @@ -8711,7 +8480,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^3.0.0, minipass@npm:^3.1.0, minipass@npm:^3.1.1, minipass@npm:^3.1.3": +"minipass@npm:^3.0.0": version: 3.3.6 resolution: "minipass@npm:3.3.6" dependencies: @@ -8741,7 +8510,7 @@ __metadata: languageName: node linkType: hard -"minizlib@npm:^2.0.0, minizlib@npm:^2.1.1": +"minizlib@npm:^2.1.1": version: 2.1.2 resolution: "minizlib@npm:2.1.2" dependencies: @@ -8774,7 +8543,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": +"mkdirp@npm:^1.0.3": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" bin: @@ -8870,7 +8639,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.2, ms@npm:^2.1.3": +"ms@npm:^2.1.1, ms@npm:^2.1.2, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 @@ -8944,13 +8713,6 @@ __metadata: languageName: node linkType: hard -"negotiator@npm:^0.6.2": - version: 0.6.4 - resolution: "negotiator@npm:0.6.4" - checksum: 10c0/3e677139c7fb7628a6f36335bf11a885a62c21d5390204590a1a214a5631fcbe5ea74ef6a610b60afe84b4d975cbe0566a23f20ee17c77c73e74b80032108dea - languageName: node - linkType: hard - "negotiator@npm:^1.0.0": version: 1.0.0 resolution: "negotiator@npm:1.0.0" @@ -9009,26 +8771,6 @@ __metadata: languageName: node linkType: hard -"node-gyp@npm:8.x": - version: 8.4.1 - resolution: "node-gyp@npm:8.4.1" - dependencies: - env-paths: "npm:^2.2.0" - glob: "npm:^7.1.4" - graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^9.1.0" - nopt: "npm:^5.0.0" - npmlog: "npm:^6.0.0" - rimraf: "npm:^3.0.2" - semver: "npm:^7.3.5" - tar: "npm:^6.1.2" - which: "npm:^2.0.2" - bin: - node-gyp: bin/node-gyp.js - checksum: 10c0/80ef333b3a882eb6a2695a8e08f31d618f4533eff192864e4a3a16b67ff0abc9d8c1d5fac0395550ec699326b9248c5e2b3be178492f7f4d1ccf97d2cf948021 - languageName: node - linkType: hard - "node-gyp@npm:^11.0.0, node-gyp@npm:^11.2.0, node-gyp@npm:latest": version: 11.4.2 resolution: "node-gyp@npm:11.4.2" @@ -9056,17 +8798,6 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^5.0.0": - version: 5.0.0 - resolution: "nopt@npm:5.0.0" - dependencies: - abbrev: "npm:1" - bin: - nopt: bin/nopt.js - checksum: 10c0/fc5c4f07155cb455bf5fc3dd149fac421c1a40fd83c6bfe83aa82b52f02c17c5e88301321318adaa27611c8a6811423d51d29deaceab5fa158b585a61a551061 - languageName: node - linkType: hard - "nopt@npm:^8.0.0, nopt@npm:^8.1.0": version: 8.1.0 resolution: "nopt@npm:8.1.0" @@ -9326,18 +9057,6 @@ __metadata: languageName: node linkType: hard -"npmlog@npm:^6.0.0": - version: 6.0.2 - resolution: "npmlog@npm:6.0.2" - dependencies: - are-we-there-yet: "npm:^3.0.0" - console-control-strings: "npm:^1.1.0" - gauge: "npm:^4.0.3" - set-blocking: "npm:^2.0.0" - checksum: 10c0/0cacedfbc2f6139c746d9cd4a85f62718435ad0ca4a2d6459cd331dd33ae58206e91a0742c1558634efcde3f33f8e8e7fd3adf1bfe7978310cf00bd55cccf890 - languageName: node - linkType: hard - "nth-check@npm:^2.0.1": version: 2.1.1 resolution: "nth-check@npm:2.1.1" @@ -9531,15 +9250,6 @@ __metadata: languageName: node linkType: hard -"p-map@npm:^4.0.0": - version: 4.0.0 - resolution: "p-map@npm:4.0.0" - dependencies: - aggregate-error: "npm:^3.0.0" - checksum: 10c0/592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75 - languageName: node - linkType: hard - "p-map@npm:^7.0.1, p-map@npm:^7.0.2, p-map@npm:^7.0.3": version: 7.0.3 resolution: "p-map@npm:7.0.3" @@ -10578,13 +10288,6 @@ __metadata: languageName: node linkType: hard -"promise-inflight@npm:^1.0.1": - version: 1.0.1 - resolution: "promise-inflight@npm:1.0.1" - checksum: 10c0/d179d148d98fbff3d815752fa9a08a87d3190551d1420f17c4467f628214db12235ae068d98cd001f024453676d8985af8f28f002345646c4ece4600a79620bc - languageName: node - linkType: hard - "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -10775,7 +10478,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": +"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.2": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -10920,17 +10623,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" - dependencies: - glob: "npm:^7.1.3" - bin: - rimraf: bin.js - checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 - languageName: node - linkType: hard - "rimraf@npm:^6.0.1": version: 6.0.1 resolution: "rimraf@npm:6.0.1" @@ -11282,13 +10974,6 @@ __metadata: languageName: node linkType: hard -"set-blocking@npm:^2.0.0": - version: 2.0.0 - resolution: "set-blocking@npm:2.0.0" - checksum: 10c0/9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454 - languageName: node - linkType: hard - "setprototypeof@npm:1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" @@ -11443,7 +11128,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.3": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 @@ -11539,17 +11224,6 @@ __metadata: languageName: node linkType: hard -"socks-proxy-agent@npm:^6.0.0": - version: 6.2.1 - resolution: "socks-proxy-agent@npm:6.2.1" - dependencies: - agent-base: "npm:^6.0.2" - debug: "npm:^4.3.3" - socks: "npm:^2.6.2" - checksum: 10c0/d75c1cf1fdd7f8309a43a77f84409b793fc0f540742ef915154e70ac09a08b0490576fe85d4f8d68bbf80e604a62957a17ab5ef50d312fe1442b0ab6f8f6e6f6 - languageName: node - linkType: hard - "socks-proxy-agent@npm:^8.0.3": version: 8.0.5 resolution: "socks-proxy-agent@npm:8.0.5" @@ -11561,7 +11235,7 @@ __metadata: languageName: node linkType: hard -"socks@npm:^2.6.2, socks@npm:^2.8.3": +"socks@npm:^2.8.3": version: 2.8.7 resolution: "socks@npm:2.8.7" dependencies: @@ -11675,27 +11349,6 @@ __metadata: languageName: node linkType: hard -"sqlite3@npm:^5.1.7": - version: 5.1.7 - resolution: "sqlite3@npm:5.1.7" - dependencies: - bindings: "npm:^1.5.0" - node-addon-api: "npm:^7.0.0" - node-gyp: "npm:8.x" - prebuild-install: "npm:^7.1.1" - tar: "npm:^6.1.11" - peerDependencies: - node-gyp: 8.x - dependenciesMeta: - node-gyp: - optional: true - peerDependenciesMeta: - node-gyp: - optional: true - checksum: 10c0/10daab5d7854bd0ec3c7690c00359cd3444eabc869b68c68dcb61374a8fa5e2f4be06cf0aba78f7a16336d49e83e4631e8af98f8bd33c772fe8d60b45fa60bc1 - languageName: node - linkType: hard - "sqlstring@npm:^2.3.2": version: 2.3.3 resolution: "sqlstring@npm:2.3.3" @@ -11712,15 +11365,6 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^8.0.0, ssri@npm:^8.0.1": - version: 8.0.1 - resolution: "ssri@npm:8.0.1" - dependencies: - minipass: "npm:^3.1.1" - checksum: 10c0/5cfae216ae02dcd154d1bbed2d0a60038a4b3a2fcaac3c7e47401ff4e058e551ee74cfdba618871bf168cd583db7b8324f94af6747d4303b73cd4c3f6dc5c9c2 - languageName: node - linkType: hard - "stable@npm:^0.1.8": version: 0.1.8 resolution: "stable@npm:0.1.8" @@ -11780,7 +11424,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -12042,7 +11686,7 @@ __metadata: languageName: node linkType: hard -"tar@npm:^6.0.2, tar@npm:^6.1.11, tar@npm:^6.1.2, tar@npm:^6.2.1": +"tar@npm:^6.1.11, tar@npm:^6.2.1": version: 6.2.1 resolution: "tar@npm:6.2.1" dependencies: @@ -12541,15 +12185,6 @@ __metadata: languageName: node linkType: hard -"unique-filename@npm:^1.1.1": - version: 1.1.1 - resolution: "unique-filename@npm:1.1.1" - dependencies: - unique-slug: "npm:^2.0.0" - checksum: 10c0/d005bdfaae6894da8407c4de2b52f38b3c58ec86e79fc2ee19939da3085374413b073478ec54e721dc8e32b102cf9e50d0481b8331abdc62202e774b789ea874 - languageName: node - linkType: hard - "unique-filename@npm:^4.0.0": version: 4.0.0 resolution: "unique-filename@npm:4.0.0" @@ -12559,15 +12194,6 @@ __metadata: languageName: node linkType: hard -"unique-slug@npm:^2.0.0": - version: 2.0.2 - resolution: "unique-slug@npm:2.0.2" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10c0/9eabc51680cf0b8b197811a48857e41f1364b25362300c1ff636c0eca5ec543a92a38786f59cf0697e62c6f814b11ecbe64e8093db71246468a1f03b80c83970 - languageName: node - linkType: hard - "unique-slug@npm:^5.0.0": version: 5.0.0 resolution: "unique-slug@npm:5.0.0" @@ -13006,7 +12632,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^2.0.1, which@npm:^2.0.2": +"which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" dependencies: @@ -13040,15 +12666,6 @@ __metadata: languageName: node linkType: hard -"wide-align@npm:^1.1.5": - version: 1.1.5 - resolution: "wide-align@npm:1.1.5" - dependencies: - string-width: "npm:^1.0.2 || 2 || 3 || 4" - checksum: 10c0/1d9c2a3e36dfb09832f38e2e699c367ef190f96b82c71f809bc0822c306f5379df87bab47bed27ea99106d86447e50eb972d3c516c2f95782807a9d082fbea95 - languageName: node - linkType: hard - "winston-transport@npm:^4.9.0": version: 4.9.0 resolution: "winston-transport@npm:4.9.0" From 5b43dc60033c3bb5a303e4f1174a8d5dcd972511 Mon Sep 17 00:00:00 2001 From: Giovani Guizzo Date: Sat, 4 Oct 2025 11:59:15 -0300 Subject: [PATCH 10/10] docs: update documentation to specify better-sqlite3 in SqliteBackend --- packages/backends/sqlite/src/sqlite-backend.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backends/sqlite/src/sqlite-backend.ts b/packages/backends/sqlite/src/sqlite-backend.ts index e0874bfa..05afa3a4 100644 --- a/packages/backends/sqlite/src/sqlite-backend.ts +++ b/packages/backends/sqlite/src/sqlite-backend.ts @@ -18,7 +18,7 @@ const defaultKnexConfig = { * Represents a backend implementation for SQLite databases using Knex. * * This class extends the `SQLBackend` and configures a Knex instance - * for SQLite3, specifying the database file path, migration directory, + * for better-sqlite3, specifying the database file path, migration directory, * migration table name, and file extension for migration files. * * @example