Skip to content

Split DamModule into composable sub-modules - #6151

Merged
VPS-thodax merged 10 commits into
mainfrom
refactor/split-dam-module
Aug 13, 2026
Merged

Split DamModule into composable sub-modules#6151
VPS-thodax merged 10 commits into
mainfrom
refactor/split-dam-module

Conversation

@VPS-thodax

@VPS-thodax VPS-thodax commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Problem

DamModule registered file handling, image scaling, the block transformers and the dependents resolver as a single unit. A project that only needs DAM file upload and storage therefore had to bring ImgproxyModule and DependenciesModule along with it.

Computing the dominant color of an uploaded image was the one place where FilesService used imgproxy, which is what made that dependency reach into file handling at all.

Solution

DamDominantColorService now holds the dominant color computation, and DamModule is a facade composing four sub-modules. DamModule.register() keeps its signature and still composes all of them.

Module Responsibility Depends on
DamFilesModule file and folder CRUD, upload, storage, serving, dam-items, media alternatives, licenses, warnings BlobStorageModule, UserPermissionsModule, MikroORM
DamImagesModule image scaling and serving, dominant color, image validators, FileImagesResolver ImgproxyModule, BlobStorageModule, UserPermissionsModule, MikroORM, DamFilesModule
DamBlocksModule the four DAM block transformer services DamFilesModule, DamImagesModule
DamDependentsModule the dependents field on DamFile DependenciesModule

FilesService receives the dominant color calculator through the optional DAM_DOMINANT_COLOR_CALCULATOR token, which DamImagesModule provides. Without that module, uploads skip the color and FilesService.calculateDominantColor returns undefined. The contract is a standalone interface (DominantColorCalculatorInterface), so the files code has no runtime link to the imgproxy-backed service — the emitted files.service.js contains no require of anything under dam/images.

DamModule and each sub-module throw when they are registered more than once in the same process. A second registration would mount the DAM routes twice and add the dependents field to the file type again, so it never worked as intended.

FileImagesResolver moved from dam/files/ to dam/images/, so its folder matches the module that registers it.

Decisions

  • DamFilesModule is the only sub-module that is exported. It is the one a project can register on its own. DamImagesModule, DamBlocksModule and DamDependentsModule each need providers that DamFilesModule registers, so exporting them would offer combinations that fail at DI time. DamDominantColorService stays internal for the same reason — it is an implementation detail of DamImagesModule, reachable through FilesService.calculateDominantColor.

Verification

  • Demo API boots with DamFilesModule, DamImagesModule, DamBlocksModule and DamDependentsModule all reporting initialized dependencies, and registers the cms.dam.calculateDominantImageColor command
  • schema.gql and block-meta.json regenerate unchanged, so the split does not affect the GraphQL schema

Outlook

The goal is that DamFilesModule can be registered on its own with as few dependencies as possible, for a service that only stores and serves DAM files:

DamFilesModule.register({ damConfig, Scope: DamScope, File: DamFile, Folder: DamFolder });

What DamFilesModule still needs to resolve:

Dependency Injected by Provided by Status
BlobStorageBackendService FilesService, FoldersService, FilesController BlobStorageModule stays — the DAM has to store files somewhere
EntityManager / MikroORM most providers MikroOrmModule.forRoot stays
ACCESS_CONTROL_SERVICE FilesController, FoldersController UserPermissionsModule optional in #6153

@VPS-thodax VPS-thodax self-assigned this Aug 9, 2026
@VPS-thodax
VPS-thodax force-pushed the refactor/split-dam-module branch from 65935f2 to 4eb9d81 Compare August 9, 2026 21:38
@VPS-thodax
VPS-thodax marked this pull request as ready for review August 9, 2026 22:04
@github-actions
github-actions Bot requested a review from VPS-Obi August 9, 2026 22:04
@VPS-thodax
VPS-thodax requested a review from kaufmo August 9, 2026 22:05
kaufmo
kaufmo previously approved these changes Aug 10, 2026
@VPS-thodax
VPS-thodax marked this pull request as draft August 10, 2026 06:20
Base automatically changed from refactor/content-scopes-are-equal-helper to main August 10, 2026 08:30
VPS-Obi added a commit that referenced this pull request Aug 10, 2026
## Problem

`FilesService` and the DAM files controller injected
`ContentScopeService` only to call its `scopesAreEqual` method — a
stateless deep comparison. That ties the DAM to the user-permissions
layer for a check that needs no state. `FoldersService` ran the same
comparison inline via `lodash.isequal`, so the rule existed twice.

## Solution

The comparison lives in `contentScopesAreEqual` now, next to the
`ContentScope` interface in `user-permissions/`:

- `ContentScopeService.scopesAreEqual` delegates to it, so its callers
are unaffected.
- The DAM services and the files controller call it directly and no
longer inject `ContentScopeService`.

No changeset because `contentScopesAreEqual` is internal and
`ContentScopeService.scopesAreEqual` keeps its signature, so the public
API is unchanged.

## Verification

- New unit tests in `content-scopes-are-equal.spec.ts` cover a class
instance against a plain object plus differing values, differing keys
and `undefined` scopes

## Outlook

First of three stacked pull requests that let `DamFilesModule` be
registered without `UserPermissionsModule`: #6151 splits `DamModule`
into sub-modules, #6153 makes the DAM's scope-based access control
optional.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
VPS-thodax and others added 2 commits August 10, 2026 10:30
Computing the dominant color of an uploaded image was the only part of FilesService
that used imgproxy, which made ImgproxyModule a hard dependency of the whole DAM
file handling.

Move the computation into DamDominantColorService, so ImgproxyService is injected in
one small service instead. FilesService.calculateDominantColor remains as a
deprecated delegator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…BlocksModule

DamModule registered file handling, image scaling, the block transformers and the
dependents resolver as one unit, so a project that only needs DAM file upload and
storage had to bring ImgproxyModule and DependenciesModule along with it.

Move the registrations into four sub-modules and turn DamModule into a facade that
composes all of them, so DamModule.register() behaves as before. FilesService
receives the dominant color calculator through the optional
DAM_DOMINANT_COLOR_CALCULATOR token, which DamImagesModule provides. Without that
module, uploads skip the color.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@VPS-Obi
VPS-Obi force-pushed the refactor/split-dam-module branch from 4eb9d81 to 0c0c331 Compare August 10, 2026 08:30
@VPS-Obi
VPS-Obi removed their request for review August 10, 2026 08:31
@VPS-thodax
VPS-thodax marked this pull request as ready for review August 10, 2026 10:46
@VPS-thodax
VPS-thodax requested a review from VPS-Obi August 10, 2026 10:47
Comment thread packages/api/cms-api/src/index.ts Outdated
VPS-thodax and others added 8 commits August 10, 2026 13:45
DamModule registers the four DAM sub-modules internally, so registering one of them
alongside DamModule goes wrong in a different way each time: DamFilesModule and
DamImagesModule mount their routes twice, DamDependentsModule adds a second resolver
for the dependents field and breaks the schema, DamBlocksModule leaves two instances
of each transformer service with one silently unused.

Throw on the second register() call instead, naming the module and what to do about
it, the way UserPermissionsModule already guards its permission enum.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DamImagesModule, DamBlocksModule and DamDependentsModule are composition details of
DamModule, and registering one of them next to DamModule is now an error. Exporting
them advertises a setup that isn't supported, so keep them internal. DamFilesModule
stays exported because it is the one that can be registered on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The service is an implementation detail of DamImagesModule and is not
meant to be injected by projects, so it does not belong in the public API.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The last use moved out with the dominant color computation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
BlobStorageBackendService imports nothing from dam/, so there is no
import cycle the forwardRef would resolve here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both new modules pulled a symbol from the package index, which imports
the modules back. Importing the source files directly avoids the cycle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Registering a DAM module twice now throws, so the changeset can no
longer claim that existing setups behave identically.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DamModule accepts a damConfig without a basePath and falls back to
"dam". DamFilesModule required it, so a project registering the module
on its own could not pass the damConfig it already had.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@VPS-thodax
VPS-thodax force-pushed the refactor/split-dam-module branch from c2b3edb to 05fd2a4 Compare August 10, 2026 20:02
export { HashImageParams, ImageParams } from "./dam/images/dto/image.params";
export { ImageCropAreaInput } from "./dam/images/dto/image-crop-area.input";
export { ImageCropArea } from "./dam/images/entities/image-crop-area.entity";
export { FileImagesResolver } from "./dam/images/file-image.resolver";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out-of-scope: I don't remember why we initially exported this resolver, but IMO this shouldn't be part of the public API.

@VPS-Obi

VPS-Obi commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Needs human review — large diff, foundational for an auth-related change.

10 commits, +408/-216 across 16 files, splitting DamModule into four composable sub-modules (DamFilesModule, DamImagesModule, DamBlocksModule, DamDependentsModule) and moving dominant-color calculation behind an optional injection token. Verification relies on manual "Demo API boots" checks and unchanged schema.gql/block-meta.json rather than new automated tests for the module split itself. It's also base for #6153, which changes DAM authorization logic, so getting this structural change right matters. Already has 3 inline review comments, so review is in progress.


Generated by Claude Code

@VPS-thodax
VPS-thodax merged commit 4eadaf5 into main Aug 13, 2026
15 checks passed
@VPS-thodax
VPS-thodax deleted the refactor/split-dam-module branch August 13, 2026 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants