Skip to content

QueueSchemaService (Knex-only) is exported from the root barrel, pulling the optional knex peer into every consumer's typecheck #24

Description

@gerardp

Summary

The root entry point re-exports QueueSchemaService, whose constructor
signature is constructor(connection: Knex). That single export is the only
reason the generated root declaration file imports knex:

// build/index.d.ts
import { Knex } from 'knex'                                        // line 4

declare class QueueSchemaService {
  constructor(connection: Knex)                                    // line 536
  createJobsTable(tableName?: string,
    extend?: (table: Knex.CreateTableBuilder) => void): Promise<void>       // line 541
  createSchedulesTable(tableName?: string,
    extend?: (table: Knex.CreateTableBuilder) => void): Promise<void>       // line 554
}

knex is declared as an optional peer dependency:

"peerDependencies":     { "knex": "^3.0.0" },
"peerDependenciesMeta": { "knex": { "optional": true } }

but because the type is reachable from the root barrel, any consumer type
checking with skipLibCheck: false has to install knex even when they only
use the Kysely, Redis or Sync driver — or a custom Adapter of their own.

Reproduction

mkdir repro && cd repro
npm init -y && npm pkg set type=module
npm i @boringnode/queue
npm i -D typescript@7.0.2 @types/node@25.9.4

cat > tsconfig.json <<'JSON'
{
  "compilerOptions": {
    "target": "esnext",
    "module": "preserve",
    "moduleResolution": "bundler",
    "types": ["node"],
    "strict": true,
    "skipLibCheck": false,
    "noEmit": true
  }
}
JSON

printf 'import { Job } from "@boringnode/queue"\nexport const j = Job\n' > index.ts
npx tsc --noEmit

Result:

node_modules/@boringnode/queue/build/index.d.ts(4,22): error TS2307:
  Cannot find module 'knex' or its corresponding type declarations.

Installing knex purely for its types — it is never imported by the
application — makes the error disappear:

npm i -D knex@3.1.0
npx tsc --noEmit     # clean

Environment: @boringnode/queue@0.7.1, typescript@7.0.2,
@types/node@25.9.4.

Why this looks like an oversight rather than a design choice

The repository already applies the intended layout everywhere else. The root
barrel is the only place it is not applied:

Module Adapter Schema service
src/drivers/kysely_adapter.ts KyselyAdapter KyselyQueueSchemaService — re-exported at line 17
src/drivers/knex_adapter.ts KnexAdapter
index.ts QueueSchemaService — exported at line 8

src/drivers/kysely_adapter.ts:17 is exactly the line this issue is asking for
in the Knex module:

export { KyselyQueueSchemaService } from '../services/kysely_queue_schema.js'

Because Kysely's schema service is scoped to its driver subpath, importing
@boringnode/queue never pulls in kysely. The same holds for ioredis and
for @opentelemetry/*. Only knex leaks, and only through this one export.

Proposed fix

Move the export next to the adapter that needs it, mirroring the Kysely module:

  // index.ts
- export { QueueSchemaService } from './src/services/queue_schema.js'
  // src/drivers/knex_adapter.ts
+ export { QueueSchemaService } from '../services/queue_schema.js'

Consumers would then import it the same way they already import the Kysely
equivalent:

import { KnexAdapter, QueueSchemaService } from '@boringnode/queue/drivers/knex_adapter'

Renaming it to KnexQueueSchemaService at the same time would make the two
modules symmetrical, though that is orthogonal to the leak itself.

This is a breaking change for anyone importing QueueSchemaService from the
root today. If that matters for the current release line, an alternative that
keeps the leak out of the barrel without breaking anyone is to type the
constructor structurally and drop the import { Knex }, since the class only
needs a query-builder-shaped value. The move is cleaner, though, and consistent
with how the package already treats Kysely.

Happy to send a PR

The change is small and I have a working reproduction; I can open a PR with
whichever of the two options you prefer.


Related packaging/typecheck reports opened at the same time, from the same evaluation: #25 (tracingChannel type arguments on @types/node v26) and #26 (./types re-exporting an OpenTelemetry type). They are independent of each other; each reproduction above pins the environment so only one of them shows up at a time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions