Skip to content

[finding] service-datasource's published source type-imports from a dev-only workspace dependency — the one instance repo-wide, and nothing checks the class #10062

Description

@os-elon

Measured while implementing #9960 (converging the deletePackage seam on the producer's declared types). That card had to decide whether a published source file may import a type from a package declared only in devDependencies, so the repo was swept for existing instances. Recorded, not acted on — this is an observation about a class nothing currently checks, not a defect with a symptom.

The measurement

For every workspace package: collect the @objectstack/* specifiers imported by its non-testsrc/** files, and subtract the union of its dependencies. Run over all 72 workspace packages at origin/main @ 09b880b0c:

packages whose PUBLISHED src imports a DEV-ONLY workspace dep:
@objectstack/service-datasource <- @objectstack/driver-sql (2 files)
packages/services/service-datasource/src/default-datasource-driver-factory.ts
packages/services/service-datasource/src/sqlite-driver-fallback.ts
total: 1

sqlite-driver-fallback.ts line 36 is the spelling:

importtype{SqliteAbsentFileMode}from'@objectstack/driver-sql';

@objectstack/driver-sql is in that package's devDependencies, not its dependencies.

Why it is probably harmless today, and what the class is anyway

It is type-only, so nothing lands in the emitted JavaScript, and the shared tsup.config.ts externalises dependencies/peerDependencies only — a devDependency is bundled, which for dts means rollup-plugin-dts inlines the declaration rather than leaving an unresolvable import type in the published .d.ts. So an installing consumer does not see a missing module. That is the mechanism, measured on the sibling case in #9960: @objectstack/rest's new type-only import of @objectstack/metadata-protocol produced zero references to that package in packages/rest/dist/index.d.ts.

What is unguarded is the class, not this instance:

  • the property that saves it (inlining) is a property of the bundler configuration, not of the declaration — a package that ever builds its types with a config that externalises everything, or that re-exports the imported type by name, ships a .d.ts naming a package its consumers were never told to install;
  • nothing in the repo asserts the invariant in either direction. There is no import/no-extraneous-dependencies rule in eslint.config.mjs, and no check:* script reads a package's source imports against its declared dependencies (check:type-source-resolution and check:test-source-alias both answer a different question — which copy of a dependency's types is resolved, not whether the dependency is declared);
  • being a single instance is exactly what makes it cheap to decide now, and exactly what makes it invisible later: the next one arrives with no signal.

Not asserted

No claim that the current instance is broken, and no proposal here about which way it should be settled — promote the dependency, keep the convention and write it down, or add a gate. All three are judgement calls about dependency hygiene, and this issue only records that the class exists, is unguarded, and currently has exactly one member.

Repo: objectstack. Files: packages/services/service-datasource/src/sqlite-driver-fallback.ts, packages/services/service-datasource/src/default-datasource-driver-factory.ts, packages/services/service-datasource/package.json.


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions