refactor(api-headless-cms): storage ops - #5490
Merged
Merged
Conversation
…ch 1) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…storage abstractions (batch 1) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…storage abstractions (batch 2)
…ethod storage abstractions
…d barrel export Adds registerCmsStorageOperations(container, registry) and the ICmsStorageOperationsRegistry interface, which TypeScript uses to enforce that a storage adapter provides all 24 per-method CMS storage abstractions (group, model, 22 entry abstractions) in one call. Also adds the storageOperations barrel export and re-exports from exports/api/cms/storage.ts.
…dlessCmsFeature
Calls registerCmsStorageOperations() right after the legacy StorageOperations
registration, wiring the group/model storage ops and all 22 entry methods
(wrapped as { execute } objects) from the existing storageOperations object
into the new per-method DI abstractions. Both registrations coexist during
the migration.
…perations consumers Migrates the last consumers of the legacy monolithic StorageOperations abstraction to the per-method storage operation abstractions: CmsEntriesCrudDecorators (getRevisionById, getLatestRevisionByEntryId), DeleteModel (list), and two webhooks integration tests that also relied on the legacy abstraction to inspect raw stored entries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s abstraction All consumers now resolve the per-method storage operation abstractions via registerCmsStorageOperations. Drops the StorageOperations DI token and namespace from features/shared/abstractions.ts, and removes its registration/resolution from HeadlessCmsFeature.ts. The facade's `storageOperations` field is now populated directly from the local variable instead of a container round-trip. StorageOperationsFactory and the storage bootstrapping (create/beforeInit/bridge) are unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…peration abstractions CmsModel and other types are re-exported from ~/types/index.js, not directly from ~/types/types.js where they are defined. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…allback HeadlessCmsFeature now tries CmsEntryStorageOpsRegistrar first (new-style adapters like DDB). Falls back to StorageOperationsFactory for legacy adapters (ddb-es, sql, pg-os). StorageOperationsFactory kept with @deprecated marker until all adapters migrate. Removed storageOperations field from HeadlessCms interface/facade. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tional deprecated field - Separate resolve from register call so bugs in registrar surface directly - Keep storageOperations on HeadlessCms interface as optional+deprecated for backward compat with existing tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…I abstractions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ly via DI Replace StorageOperationsFactory with direct DI registration, same pattern as DDB adapter. Group and model ops are DI classes (app-scoped singletons), entry ops use the registrar pattern (per-request via CmsEntryStorageOpsRegistrar). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…via DI Replace StorageOperationsFactory with direct DI registration. Group and model ops are DI classes (app-scoped singletons), entry ops bridged via SqlCmsEntryStorageOpsRegistrar. Remove dead factory functions and types. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…geOperations Arrow wrappers instead of bare method refs — class-based entry ops (SQL adapter) use this internally, which breaks when methods are detached. Also fix SQL registrar to resolve entry ops per-request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…y via DI Replace StorageOperationsFactory with direct DI registration. Reuse SqlGroupStorageOperations and SqlModelStorageOperations for group/model ops. Entry ops bridged via PgOsCmsEntryStorageOpsRegistrar composing EntryWriteOperations + EntrySearchOperations + SqlEntryOperations. All four adapters (DDB, DDB+ES, SQL, PG+OS) now use direct DI registration. Legacy StorageOperationsFactory path kept as fallback for external adapters only. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… DI implementations PG-OS entry storage ops now use named impl classes with createImplementation — 14 PG-OS specific (delegating to WriteOps and SearchOps) + 8 SQL DI classes registered directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Delete SqlEntryOperations 728-line monolith — PG-OS write ops now decorate SQL per-method implementations directly via registerDecorator, adding OpenSearch sync as a layer instead of delegating through an intermediate EntryWriteOperations abstraction. Remove CmsEntryStorageOperations monolithic interface from types (param types kept). Remove DDB/DDB-ES extended interfaces that were defined but never imported. Clean up HeadlessCmsStorageOperations dead type from 24 test files and createCmsTestHandler. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…export Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nSearch index configs Resolve CmsEntryOpenSearchIndexCreate lazily in model event handlers instead of eagerly during feature registration. Eager resolution captured only the index configs registered at that point, preventing later registrations (custom CmsEntryOpenSearchIndex impls) from being picked up. Convert DDB-ES test createIndexConfigurationPlugin from legacy RegisterExtensionPlugin to pure DI (createImplementation). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…andlers as proper DI classes Replace registerFactory + anonymous objects in DDB-ES and PG-OS features with proper DI implementation classes: ModelAfterCreateHandler, ModelAfterCreateFromHandler, ModelAfterDeleteHandler. These are now registered centrally in CmsEntryOpenSearchUtilsFeature, eliminating duplicate wiring in each adapter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r-method DI classes Same pattern as entry ops — each of the 5 group methods (get, list, create, update, delete) and 5 model methods is now an independent DI abstraction with one implementation per adapter. Delete monolithic GroupStorageOperations and ModelStorageOperations interfaces and their adapter implementations. Rewire 10 consumer repositories to resolve per-method abstractions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…egistration Each adapter now has DdbGroupStorageOpsFeature, DdbModelStorageOpsFeature (and DdbEs/Sql variants) that encapsulate per-method DI registration. Root features just call Feature.register(container) instead of listing 10 individual container.register() calls. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…operations/
Consistent file naming: all adapter storage ops features are now
at operations/{group,model,entry}/feature.ts instead of named files
at src/ root.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace registerDynamoDbStorageOperations, registerCmsOpenSearchStorageOperations, and registerSqlStorageOperations with direct Feature.register() calls. All callers migrated to use HeadlessCmsDdbFeature, HeadlessCmsDdbEsFeature, and HeadlessCmsSqlFeature directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move infrastructure registrations into HeadlessCmsPgOsFeature.register() with config param, matching HeadlessCmsSqlFeature pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e createImplementation()
Replace standalone `createImplementation({ abstraction: X, ... })` calls with
`X.createImplementation({ ... })` across all headless CMS storage operation
packages. The standalone import from `@webiny/feature/api` belongs to the
abstraction layer and should not be used directly in implementation packages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ModelStorageOperations was removed when storage ops were split into per-method DI classes. Update the test helper to resolve and call the new DeleteModelStorageOperation abstraction instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same fix as helpers.ts — ModelStorageOperations was removed, replaced with per-method abstractions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tenant index creation order is non-deterministic. Use expect.arrayContaining instead of exact array match. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When OPENSEARCH_SHARED_INDEXES is enabled, deleting a model must not delete the OpenSearch index because other tenants still use it. Add an early return guard in CmsEntryOpenSearchIndexDeleteImpl. Also remove unused CmsModel type import in pg-os EntrySearchOperations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Alias abstraction imports as XAbstraction when export name matches - Make impl classes private (not exported), named XImpl - Export name matches abstraction name (no Impl suffix) - index.ts exports only abstractions and features, not implementations - Apply to api-headless-cms-utils-os (7 files), api-graphql (1 file), api-opensearch (1 file) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drop Impl suffix from filenames — file matches class/export name. Update feature.ts import paths accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
createFeature<Foo | undefined> now makes register's context parameter optional at the type level. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Container type is inferred from createFeature's register signature. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
brunozoric
marked this pull request as ready for review
July 29, 2026 08:44
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
registered via
createImplementation/createDecoratorStorageOperationsFactory,HeadlessCmsStorageOperations,CmsEntryStorageOperationsinterface,SqlEntryOperationsmonolith, andEntryWriteOperations— ~4,500 lines of legacy wiring removedEntryWriteOperationsdelegation layerModelAfterCreateHandler,ModelAfterCreateFromHandler,ModelAfterDeleteHandlerlive inapi-headless-cms-utils-osand are shared by both DDB-ES and PG-OSadapters
HeadlessCmsStorageOperationstypePackages changed
api-headless-cmsCmsEntryStorageOperationsinterface,StorageOperationsFactory, legacy bridge codeapi-headless-cms-ddbDdbCreateEntry, etc.),DataLoadersHandleras DI impl,DdbGroupStorageOperations,DdbModelStorageOperationsapi-headless-cms-ddb-esDdbEsCreateEntry, etc.),CmsDdbEsDataLoadersabstraction, test converted from legacy plugin to DIapi-headless-cms-sqlSqlCreateEntry, etc.), deleted 728-lineSqlEntryOperationsmonolithapi-headless-cms-pg-osEntryWriteOperations,write/*.tsfiles; dropped unusedHeadlessCmsPgOsFeatureexportapi-headless-cms-utils-osapi-headless-cms-testingHeadlessCmsStorageOperationstypeArchitecture
Before: Adapters implemented a monolithic
CmsEntryStorageOperationsinterface (22 methods). AStorageOperationsFactorywired the monolith at runtime. PG-OS delegated writes throughEntryWriteOperations→SqlEntryOperationsmonolith.After: Each storage operation is an independent DI abstraction. Adapters register per-method implementations via
createImplementation. PG-OS write ops are decorators (registerDecorator) that wrap SQL implementations withOpenSearch sync. No factories, no registrars, no runtime wiring.
Core abstraction (CreateEntryStorageOperation)
→ SqlCreateEntry (SQL adapter, via createImplementation)
→ PgOsCreateEntry (PG-OS decorator, wraps SQL + adds OS sync)
Rules established
convertToStorageEntry,createKeys, etc.) as importable functions, not DI-injectedapi-headless-cms-utils-osTest plan
yarn test packages/api-headless-cms-ddb)yarn test:os packages/api-headless-cms-ddb-es) — 1 intermittent ordering failure increateIndexTask(passes in isolation)yarn test:os packages/api-headless-cms-pg-os) —syncStreamtests require running Postgres