From 837174d1ab42b4d42b823d04a7487cf680733996 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Wed, 24 Jun 2026 06:56:50 +0530 Subject: [PATCH 1/2] feat(node): default SQLite to .taskito/taskito.db Mirror the Python SDK: zero-config SQLite under .taskito/ with auto-created parent dirs; require explicit dsn for postgres/redis. --- sdks/node/README.md | 8 ++++++- sdks/node/src/queue.ts | 41 ++++++++++++++++++++++++++++---- sdks/node/test/core/open.test.ts | 30 +++++++++++++++++++++++ 3 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 sdks/node/test/core/open.test.ts diff --git a/sdks/node/README.md b/sdks/node/README.md index c12f3379..d9e1227f 100644 --- a/sdks/node/README.md +++ b/sdks/node/README.md @@ -47,11 +47,17 @@ worker.stop(); ## Backends ```ts -new Queue({ dbPath: "taskito.db" }); // SQLite (default) +new Queue(); // SQLite at .taskito/taskito.db (default) +new Queue({ dbPath: "data/taskito.db" }); // SQLite at a custom path (dirs created) new Queue({ backend: "postgres", dsn: process.env.PG_URL, schema: "taskito" }); new Queue({ backend: "redis", dsn: "redis://localhost", prefix: "taskito" }); ``` +SQLite defaults to `.taskito/taskito.db` and creates the parent directory +automatically. Postgres isolates its tables in the `schema` (default +`"taskito"`); Redis isolates its keys under `prefix`. Override either to share +or separate state. + ## Enqueue options `priority`, `maxRetries`, `timeoutMs`, `delayMs` (delayed run), `uniqueKey` diff --git a/sdks/node/src/queue.ts b/sdks/node/src/queue.ts index a6266199..1aad96a0 100644 --- a/sdks/node/src/queue.ts +++ b/sdks/node/src/queue.ts @@ -1,3 +1,5 @@ +import { mkdirSync } from "node:fs"; +import { dirname } from "node:path"; import { JobCancelledError, JobFailedError, @@ -52,7 +54,8 @@ import { WorkflowManager } from "./workflows"; /** Construction options for a {@link Queue}. */ export interface QueueOptions { - /** SQLite file path — shorthand for `{ backend: "sqlite", dsn: path }`. */ + /** SQLite file path — shorthand for `{ backend: "sqlite", dsn: path }`. + * Defaults to `.taskito/taskito.db`; missing parent directories are created. */ dbPath?: string; /** `"sqlite"` (default), `"postgres"`, or `"redis"`. */ backend?: "sqlite" | "postgres" | "redis"; @@ -87,7 +90,7 @@ export class Queue { /** Built lazily — its constructor throws on addons lacking the `workflows` feature. */ private workflowManager?: WorkflowManager; - constructor(options: QueueOptions) { + constructor(options: QueueOptions = {}) { this.native = JsQueue.open(toOpenOptions(options)); this.serializer = options.serializer ?? new JsonSerializer(); this.webhookManager = new WebhookManager(this.native, this.emitter); @@ -510,14 +513,27 @@ function toNativeEnqueueOptions(options: EnqueueOptions): NativeEnqueueOptions { return notes === undefined ? rest : { ...rest, notes: encodeNotes(notes) }; } +/** Default on-disk SQLite location — mirrors the Python SDK's `.taskito/taskito.db`. */ +const DEFAULT_SQLITE_DB = ".taskito/taskito.db"; + /** Resolve a {@link QueueOptions} into the native open options. */ function toOpenOptions(options: QueueOptions): OpenOptions { - const dsn = options.dsn ?? options.dbPath; + const backend = options.backend ?? "sqlite"; + if (backend === "sqlite") { + // Zero-config default, like Python: an on-disk DB under `.taskito/`. + const dsn = options.dsn ?? options.dbPath ?? DEFAULT_SQLITE_DB; + ensureSqliteParentDir(dsn); + return { backend, dsn, poolSize: options.poolSize, namespace: options.namespace }; + } + // Postgres/Redis have no sensible default endpoint — require an explicit dsn. + // The Postgres `schema` (default `"taskito"`, resolved in the addon) and the + // Redis `prefix` give each backend its own isolated namespace. + const dsn = options.dsn; if (!dsn) { - throw new QueueError("Queue requires `dbPath` (SQLite) or `dsn`"); + throw new QueueError(`Queue backend "${backend}" requires a \`dsn\` connection string`); } return { - backend: options.backend ?? (options.dbPath ? "sqlite" : undefined), + backend, dsn, poolSize: options.poolSize, schema: options.schema, @@ -525,3 +541,18 @@ function toOpenOptions(options: QueueOptions): OpenOptions { namespace: options.namespace, }; } + +/** + * Create the parent directory of a SQLite file path, as the Python SDK does — + * SQLite won't create missing directories itself. In-memory databases have no + * parent and are skipped. + */ +function ensureSqliteParentDir(dsn: string): void { + if (dsn === ":memory:" || dsn.startsWith("file::memory:")) { + return; + } + const dir = dirname(dsn); + if (dir && dir !== ".") { + mkdirSync(dir, { recursive: true }); + } +} diff --git a/sdks/node/test/core/open.test.ts b/sdks/node/test/core/open.test.ts new file mode 100644 index 00000000..36b3b18a --- /dev/null +++ b/sdks/node/test/core/open.test.ts @@ -0,0 +1,30 @@ +import { existsSync, mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { Queue } from "../../src/index"; + +describe("Queue construction", () => { + it("defaults SQLite to .taskito/taskito.db and creates the directory", () => { + const cwd = process.cwd(); + const tmp = mkdtempSync(join(tmpdir(), "taskito-default-")); + try { + process.chdir(tmp); + new Queue(); + expect(existsSync(join(tmp, ".taskito", "taskito.db"))).toBe(true); + } finally { + process.chdir(cwd); + } + }); + + it("creates missing parent directories for a nested dbPath", () => { + const dbPath = join(mkdtempSync(join(tmpdir(), "taskito-nested-")), "deep", "sub", "queue.db"); + new Queue({ dbPath }); + expect(existsSync(dbPath)).toBe(true); + }); + + it("requires a dsn for non-sqlite backends", () => { + expect(() => new Queue({ backend: "postgres" })).toThrow(/dsn/); + expect(() => new Queue({ backend: "redis" })).toThrow(/dsn/); + }); +}); From 010b2a042fc01fd7c8702835e8243836cc33a628 Mon Sep 17 00:00:00 2001 From: Pratyush Sharma <56130065+pratyush618@users.noreply.github.com> Date: Wed, 24 Jun 2026 07:14:08 +0530 Subject: [PATCH 2/2] test(node): update construction test for default sqlite path --- sdks/node/test/core/errors.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdks/node/test/core/errors.test.ts b/sdks/node/test/core/errors.test.ts index 5c338018..67f67eab 100644 --- a/sdks/node/test/core/errors.test.ts +++ b/sdks/node/test/core/errors.test.ts @@ -48,8 +48,8 @@ it("specific errors extend the right bases and carry a name", () => { expect(new ResultTimeoutError("j", 50).timeoutMs).toBe(50); }); -it("Queue construction without storage throws QueueError", () => { - expect(() => new Queue({})).toThrow(QueueError); +it("Queue construction for a non-sqlite backend without a dsn throws QueueError", () => { + expect(() => new Queue({ backend: "postgres" })).toThrow(QueueError); }); it("invalid notes throw NotesValidationError", () => {