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
Measured while implementing #9960 (converging the
deletePackageseam 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 indevDependencies, 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 itsdependencies. Run over all 72 workspace packages atorigin/main@09b880b0c:sqlite-driver-fallback.tsline 36 is the spelling:@objectstack/driver-sqlis in that package'sdevDependencies, not itsdependencies.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.tsexternalisesdependencies/peerDependenciesonly — a devDependency is bundled, which fordtsmeans rollup-plugin-dts inlines the declaration rather than leaving an unresolvableimport typein 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-protocolproduced zero references to that package inpackages/rest/dist/index.d.ts.What is unguarded is the class, not this instance:
.d.tsnaming a package its consumers were never told to install;import/no-extraneous-dependenciesrule ineslint.config.mjs, and nocheck:*script reads a package's source imports against its declared dependencies (check:type-source-resolutionandcheck:test-source-aliasboth answer a different question — which copy of a dependency's types is resolved, not whether the dependency is declared);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