chore(api): migrate all api-* packages to DI-native handler architecture#5313
Open
adrians5j wants to merge 270 commits into
Open
chore(api): migrate all api-* packages to DI-native handler architecture#5313adrians5j wants to merge 270 commits into
adrians5j wants to merge 270 commits into
Conversation
# Conflicts: # extensions/MyApiKeyAfterUpdate.ts # packages/api-core/package.json # packages/api-core/src/ApiCoreFeature.ts # packages/api-core/src/features/logger/LoggerService.ts # packages/api-core/src/features/logger/abstractions.ts # packages/api-core/src/features/logger/feature.ts # packages/api-core/src/features/logger/index.ts # packages/handler-aws/package.json # packages/handler-aws/src/createHandler.ts # packages/webiny/package.json # yarn.lock
# Conflicts: # yarn.lock
…routing - @webiny/event-handler-core: EventHandler, HttpEventHandler, EventType, EventContext, HttpRouter, IHttpRoute, SecureHeadersDecorator, HttpFeature, ErrorHandler, NotFoundHandler, HttpTenantInitializer, HttpTenantIdExtractor - @webiny/event-handler-aws: createLambdaHandler, ApiGatewayEventType, FunctionUrlEventType, S3/SQS/SNS/EventBridge/DynamoDB event types, ApiGatewayTranslator, FunctionUrlTranslator, AwsHttpTranslator, S3TenantIdExtractor, S3TenantInitializer, S3Feature - @webiny/event-handler-node: createNodeHandler, NodeHttpEventType, NodeHttpTranslator - packages/ev-test: example app wiring both Node and Lambda setups with tenant context Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… ErrorHandler, SecureHeaders) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…outer, not a chain handler Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…rs, S3TenantIdExtractor) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Exports TestHttpEventType and createTestHandler from @webiny/event-handler-core/testing. Feed IHttpRequest directly into the chain in tests — no AWS/Node transport needed. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- @webiny/event-handler-core: RequestContainer abstraction (per-request child container),
registered automatically in createHandler/createLambdaHandler on each request
- @webiny/event-handler-core: HttpRouterImpl changed to transient so per-request
routes (e.g. GraphQLRoute) resolve from child container
- @webiny/handler-graphql: IGraphQLEngine abstraction + GraphQLEngineImpl (wraps
GraphQLSchemaComposer + graphql execution, passes {container} as contextValue)
- @webiny/handler-graphql: GraphQLRoute IHttpRoute (POST /graphql → GraphQLEngine)
- @webiny/handler-graphql: GraphQLEngineFeature groups all registrations
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ler.ts - @webiny/event-handler-core: RequestContainer (per-request child container injection), fix createTestHandler to await async root setup, remove console.error from ErrorHandler - @webiny/handler-graphql: IGraphQLContextEnhancer abstraction, GraphQLContextEnhancer extensibility point so packages can add context.security/tenancy/wcp to resolver context - @webiny/api-core: ApiCoreContextEnhancer (adds security/tenancy/wcp to GraphQL context), ApiCoreSchemaFactory (CoreGraphQLSchemaFactory bridge for old GraphQLSchemaPlugin schemas), HttpTenantInitializer moved from event-handler-core (breaks circular dep), WcpFeature + NullLicense added to ApiCoreFeature, ApiCoreSchemaFactory in ApiCoreFeature - packages/api-core/__tests__/useGqlHandler.ts: rewritten using createTestHandler — no more @webiny/handler-aws, uses DI-native event handler chain 138/144 api-core tests pass with new handler. 4 remaining relate to WCP AACL mock integration and test state persistence (pre-existing behavior differences). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # packages/background-tasks/package.json # yarn.lock
…arate files - __tests__/mocks/TestAuthenticator.ts - __tests__/mocks/TestAuthorizer.ts - __tests__/handlers/AuthTriggerHandler.ts - __tests__/handlers/RootTenantInitializer.ts - ExtraSchemaFactory named class, registered via registerFactory Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…y in tests - parallelQueries.ts: convert withoutAuthorizationPlugin to withoutAuthorizationFactory (GraphQLSchemaFactory implementation — proper DI-native pattern) - useGqlHandler.ts: remove GraphQLSchemaPlugin bridge entirely, add schemaFactories option for registering GraphQLSchemaFactory implementations directly Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…extPlugin items directly Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…qlHandler TenantContext, IdentityContext and other per-request singletons from ApiCoreFeature now live in the child container automatically — no special workarounds needed. 159/162 api-core tests pass (1 remaining: WCP/AACL mock integration). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…needs container Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ations directly - roles.test.ts: RoleFactory.createImplementation replaces ContextPlugin wrapper - teams.test.ts: RoleFactory + TeamFactory createImplementation - lifecycleEvents.ts: 12 named tracker handler classes + createImplementation, functions return arrays of implementations instead of a ContextPlugin - useGqlHandler.ts: support arrow function setup callbacks and DI class implementations (distinguish via plugin.prototype check), remove ContextPlugin import Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…x schemaPlugins build-type read - 12 test files: context.cms.* -> context.container.resolve(HeadlessCms).* (token). Validated: entry create/publish, storage-ops, traverser tests pass via the token. (generateSchema.manage/read failures are pre-existing, confirmed at baseline.) - schemaPlugins: revert to reading the (forked) context.cms for type/READ. These are BUILD-time schema params set per endpoint by CmsSchemaExecutor's fork, NOT the request-fixed facade, so resolve(HeadlessCms) was incorrect here. Added a TODO to thread `type` through getSchema -> generateSchema -> generateSchemaPlugins, which is the remaining blocker to fully drop the ctx.cms bag. State: HeadlessCms token in place; all query-time consumers + tests use it; ctx.cms still built as a bridge until the build-type threading lands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…nly (HeadlessCms)
Removes the `cms` property from the CmsContext type and stops setting `ctx.cms` in both
init paths. The CMS facade is resolved exclusively via container.resolve(HeadlessCms).
- Thread the BUILD-time endpoint `type` through getSchema -> generateSchema ->
buildSchemaPlugins -> generateSchemaPlugins, so schema generation no longer reads
context.cms.type/READ (those are per-endpoint build params, forked by CmsSchemaExecutor).
- HeadlessCmsContextEnhancer / legacy context.ts: build the facade as a local, register it
as HeadlessCms, drop `ctx.cms = {...}`; simplify the CmsSchemaExecutor fork (no cms override).
- HeadlessCmsContextualSchema: take `type` from HeadlessCmsEnhancerConfig instead of cms.type.
- DeleteModelWithEntryCleanup, modelFields validation: resolve(HeadlessCms).
- Tests (hcms + hcms-tasks): context.cms.* -> resolve(HeadlessCms); drop obsolete
`if (!context.cms)` guards.
- CmsContext is now `Context & DbContext & ApiCoreContext` (no cms) so any stray reader is a
compile error.
Validated: hcms + api-aco build clean (cms removed from type); read+manage schema-build,
entry create/publish, storage-ops tests pass via the token. Pre-existing failures
(generateSchema.manage/read, republish.entries, tasks crud "No registration for DbInstance")
confirmed at baseline — unrelated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…text.ts/extension.ts) Removes the pre-DI CMS init: src/context.ts (createContextPlugin — the legacy ContextPlugin whose apply(context) set up the CMS on the old plugin handler) and extension.ts (createCmsExtension). Production has used the DI-native HeadlessCmsFeature for some time; the only remaining consumer was the converter test helper (usePlugins), which over-pulled the entire extension just to get field converters. - converters test helper: build a PluginsContainer from createFieldConverters() directly (ConverterCollection only needs CmsModelFieldConverterPlugin instances) - delete usePlugins.ts test helper - drop createCmsExtension / ICreateCmsExtensionParams from the package public exports hcms builds clean; converter tests pass (the 2 transient file-level failures are the known parallel-run port-conflict flakiness — both pass in isolation). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…ences generateTsConfigsInPackages built `references`/`paths` in tsconfig.build.json from dependencies + devDependencies, so @webiny/testing (a test-only devDep used only from __tests__, never from src) got injected as a build reference. Since packages like api-audit-logs devDepend on @webiny/testing AND @webiny/testing depends back on them, this created a circular project reference. With composite + ts.createProgram, the cycle makes TS resolve a project's own not-yet-emitted .d.ts, throwing TS6305 across every file on a clean build (masked previously only because dist was never fully wiped). tsconfig.build.json compiles only `src`, which never imports test-only packages, so excluding them from the build config is safe. The dev tsconfig.json (which compiles __tests__) still references them. Regenerated affected tsconfig.build.json files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…acade, used as context) The AuditLogsContext DI token was typed as the AuditLogsContextValue facade, but it is registered with — and consumed as — the full audit-logs request context (handlers use .container and pass it to getAuditConfig). The wrong type param produced ~75 TS errors, previously masked by the TS6305 project-reference cycle. Type it as the full context to unblock the build. The deeper cleanup (drop whole-context injection for an AuditLogs facade token + remove context.auditLogs) is the context-removal migration, deferred (revisit after api-headless-cms). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…hase 3) Deletes the legacy GraphQL-via-ContextPlugin wrappers, all unreachable after the extension.ts/createGraphQL path was removed (the DI-native HeadlessCmsFeature already registers typeDefs/resolvers + base-schema factory + exportPlugin directly): - delete graphql/index.ts (createGraphQL) and graphql/schema/cms/index.ts (createCmsSchema) - drop createBaseSchema (ContextPlugin) from graphql/schema/baseSchema.ts; keep createBaseSchemaPlugins (used by the enhancer) - drop createExportGraphQL (ContextPlugin) from export/graphql/index.ts; keep exportPlugin No ContextPlugin remains in api-headless-cms/src. hcms builds clean; tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…ametersPlugin (Phase 2a) The CmsParametersPlugin registry (path/header/context parameter plugins) determined the endpoint type for the legacy context.ts init, now deleted. Endpoint type is sourced from HeadlessCmsEnhancerConfig.type via DI everywhere; the parameters/ subsystem + CmsParametersPlugin had no remaining consumers (only the deleted extension.ts). Removes src/parameters/, plugins/CmsParametersPlugin.ts, __tests__/parameters/, and the plugins/index.ts re-export. hcms builds clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…r token) — Phase 2b/1 Replace the CmsModelFieldConverterPlugin plugins-container registry with a multi-instance CmsFieldConverter DI token: - new ~/fieldConverters/abstractions.ts: CmsFieldConverter token + registerFieldConverters() - ConverterCollection + valueKeyTo/FromStorageConverter take a resolved fieldConverters[] array instead of a PluginsContainer - createCmsModelFieldConvertersAttachFactory resolves CmsFieldConverter from the container - enhancer registers field converters via DI; drops them from ctx.plugins; removes the now-redundant CmsSchemaExecutor context fork (uses the request context directly) ctx.plugins now only carries legacy extension plugins (model/group), removed in 2b/2 + 2c. hcms builds clean; converter tests 112/112, entry storage 3/3 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…o DI (Phase 2b/2c)
Finishes moving the remaining CMS plugin types off the plugins container onto DI
tokens, and fixes schema-plugin sourcing gaps surfaced by the earlier
CmsGraphQLSchemaFactory migration (42786b).
Phase 2b/2 — StorageOperationsCmsModelPlugin -> CmsStorageModelProvider:
- Add CmsStorageModelProvider DI token; the enhancer registers the storage-model
implementation under it instead of ctx.plugins.register.
- Storage adapters (storage/ddb/ddb-es/sql) resolve the token from the container
instead of plugins.oneByType.
Phase 2c — CmsModelPlugin/CmsGroupPlugin -> DI:
- Add multi-instance CmsModelPluginInstance / CmsGroupPluginInstance tokens.
- The enhancer registers code-defined model/group plugins (from extraPlugins)
under these tokens; Plugin{Models,Groups}Provider inject them via
{multiple:true} instead of reading cmsContext.plugins.byType.
Schema-plugin sourcing fixes:
- validation/modelFields.ts: build the model-validation schema from
CmsGraphQLSchemaFactory (base types) instead of ctx.plugins, mirroring
generateSchema — fixes "Unknown type CmsError/CmsIdentity/..." on model create.
- enhancer: bridge user-provided CmsGraphQLSchemaPlugin (extraPlugins) to
CmsGraphQLSchemaFactory so generateSchema includes schema extensions again.
- Regenerate generateSchema manage/read empty-schema snapshots (now the unified
base schema; superset, no types removed).
Tests:
- Migrate fieldIdStorageConverter, pluginsContentModels, entryPagination off the
removed ctx.cms / converter-plugins-container reads.
ddb package tests green (41/41); core entry CRUD green (46/46). Remaining hcms
failures (benchmark output flush, sdkGraphql cms-namespace) are pre-existing and
unrelated to this change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…S SDK API Fixes the two remaining pre-existing api-headless-cms test failures (benchmark + sdkGraphql), both unrelated to the plugin/context migration. Benchmark output flush: - The request lifecycle never called benchmark.output() after the ctx.benchmark -> BenchmarkAbstraction migration (38d5faa), so measurements were never emitted. The CMS route now flushes via container.resolve(BenchmarkAbstraction).output() after executing the request (no-op unless benchmarking was enabled). - Restore the "headlessCms.graphql.createRequestBody" measure in CmsSchemaExecutor (also reinstates request-body validation via createRequestBody), which had been dropped in 8200cdf. Export createRequestBody from @webiny/handler-graphql. - Update benchmark.test.ts to the actual DI request flow (getSchema -> createRequestBody -> processRequestBody); the per-operation CRUD measures live on the HeadlessCms facade, which the DI resolvers no longer route through. SDK (unified cms namespace): - The SDK targets `mutation { cms { createEntry ... } }`, served by the core GraphQL engine at /graphql (CoreGraphQLSchemaFactory impls already registered by HeadlessCmsFeature). The test helper routed SDK requests to /cms/* (the per-model schema), which has no `cms` field. useWebinySdk now routes to /graphql. api-headless-cms suite green: 0 test failures (the few file-level fails are the documented EADDRINUSE jest-dynalite port-conflict flake under parallel runs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…(HeadlessCms) deleteModel/index.ts still read `inputContext.cms.MANAGE`, a leftover from the Phase-1 ctx.cms bag removal (3aedc4b) that broke `yarn build` with TS2339 "Property 'cms' does not exist on type 'T'". Resolve the HeadlessCms facade from the DI container instead. This also clears the cascading TS6305 on api-background-tasks-os (the aborted build had left api-headless-cms-es-tasks/dist half-built). Full `yarn build` passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…SchemaPlugin usage
ApiCoreSchemaFactory created legacy GraphQLSchemaPlugin objects only to immediately
unpack them into the DI GraphQLSchemaBuilder via a local addPluginsToBuilder + a
duplicated addResolvers (a copy of builder.addLegacyResolvers); plugin.isApplicable
was never even consulted. Removed the bridge:
- security/{base,apiKey,role,team,identity}.gql, users/user.gql,
system/createSystemGraphQL, wcp/graphql now export plain
GraphQLSchemaDefinition objects instead of `new GraphQLSchemaPlugin(...)`.
- ApiCoreSchemaFactory feeds the builder directly (addTypeDefs +
addLegacyResolvers), deleting addPluginsToBuilder and the duplicated addResolvers.
The 4 create*GraphQL helpers are api-core-internal (only consumed by
ApiCoreSchemaFactory), so no public API change. GraphQLSchemaPlugin remains the
framework-wide authoring API for other packages/user code; this only removes
api-core's own use of it. api-core 165/165 tests pass; full `yarn build` green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…hema createSchema() in baseSchema.ts built the base types as a CmsGraphQLSchemaPlugin AND a duplicate core GraphQLSchemaPlugin (corePlugin), but the only caller, createBaseSchemaPlugins, destructured [cmsPlugin] and discarded corePlugin — leftover from the Phase-3 core-schema-path removal. Collapsed createSchema into createBaseSchemaPlugins, removed the dead corePlugin and the now-unused core GraphQLSchemaPlugin / IGraphQLSchemaPlugin imports. hcms now uses only the CMS plugin API internally for its schema. Base schema output unchanged (generateSchema snapshots + group CRUD pass); full `yarn build` green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…text to base Context A — context.benchmark -> DI: - The CRUD factories (contentModel, contentModelGroup, contentEntry) read context.benchmark.measure(...) — a service-locator off the merged context. Each factory now resolves BenchmarkAbstraction once (context.container.resolve) and uses that. 31 call sites migrated; BenchmarkAbstraction is the same instance the enhancer registers, so benchmark output is unchanged. B — drop the merged CmsContext type: - CmsContext was `Context & DbContext & ApiCoreContext`, but ApiCoreContext aliases the base Context and DbContext only adds an unused `db` — nothing in hcms or any dependent reads .db/.tenancy/.security. Collapsed to `export type CmsContext = Context` and removed the now-unused DbContext/ApiCoreContext imports. This also made an obsolete `@ts-expect-error` in RefToGraphQL.ts unused (removed it). Verified: full `yarn build --no-cache` across all 128 packages green (so no dependent relied on the merge); hcms suite 856 pass / 0 fail. (C — plugin->factory schema/DI-resolver migration — is the next step.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…extPlugin createAcoHcmsContext() was a legacy ContextPlugin that did SetLocationOnEntryRestoreFeature.register(context.container) — but AcoHcmsFeature already performs the identical registration, and createAcoHcmsContext had zero references anywhere (the project template + tests use AcoHcmsFeature.register). Removed it; api-headless-cms-aco now has no ContextPlugin and no context-bag reads. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
Collapsing CmsContext to base Context (67d2aaa) removed the last @webiny/handler-db import (the DbContext type). adio flagged it as an unused dependency; removed it from package.json and regenerated tsconfigs. Bonus: the context cleanup also sheds a package dependency. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
createRootTenantMock, customAuthenticator, customGroupAuthorizer, and triggerAuthentication were legacy ContextPlugin-based test mocks with zero references — superseded by the DI-native RootTenantInitializer decorator and TestAuthenticator/TestAuthorizer (used by useGqlHandler). customGroupAuthorizer also still read context.security off the bag. api-core now has zero ContextPlugin usages. 165/165 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…ng from CMS route
The CMS GraphQL route resolved [GraphQLContextEnhancer, { multiple: true }] and ran
an enhance(ctx) loop, but nothing ever registers a GraphQLContextEnhancer — the only
registrar (registerLegacyPluginsViaGqlContextEnhancer) has zero callers repo-wide.
So the dependency always resolved to [] and the loop was a no-op. Dropped the
enhancers constructor param, the dependency, the loop, and the now-unused
GraphQLContextEnhancer/IGraphQLContextEnhancer imports. CMS route tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…eadlessCmsInitializer.ts The file's misleading name predates the DI migration: it contains HeadlessCmsInitializerImpl, which `implements IGraphQLContextualSchema` (a contextual schema with build(ctx)), not a GraphQLContextEnhancer. Renamed the file to match the class and updated all internal import paths. No symbol/API changes (HeadlessCmsEnhancerConfig kept). Build + CMS route tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…ce() comments Three comments referenced HeadlessCmsContextEnhancer.enhance() — both the old filename (now HeadlessCmsInitializer) and a method that never existed (it's a GraphQLContextualSchema with build(), not an enhancer). Updated them to reference the CMS contextual schema (HeadlessCmsInitializer.build()). Comments only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…a build RecordLockingContextualSchema.build() builds a standalone executable schema from a generated CMS-model schema whose date/json fields reference base scalars (DateTime, JSON, ...), but it only passed "type Query\ntype Mutation" + the model typeDefs — never declaring those scalars — so makeExecutableSchema threw "Unknown type DateTime" at runtime on the core /graphql endpoint. The normal CMS schema build (buildSchemaPlugins) always includes createBaseContentSchema() first; do the same here so the standalone schema is self-contained before the engine merges it. Pre-existing since the record-locking DI migration (d93f519); surfaced now on deploy. The package's GraphQL tests were already red before this change (and before this session) on an unrelated test-wiring gap — they fail resolving CmsContext/ AccessControl during build(), never reaching the schema-build line — so production (which registers those) reaches this path but the tests don't. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…ix construction order RecordLockingContextualSchema injected GetModelUseCase/ListModelsUseCase/ CmsModelFieldToGraphQLRegistry as constructor dependencies. The GraphQL engine constructs all GraphQLContextualSchema implementations eagerly (when resolving the engine), and those use-cases depend on AccessControl — which the CMS initializer only registers later, during its own build(). So record-locking's construction failed with "No registration found for AccessControl" (and "CmsContext" before the context collapse) before any build() ran, which is why its GraphQL tests had been red since the DI migration (d93f519). Resolve those use-cases lazily inside build() (from ctx.container) instead — by then the CMS initializer has run and registered AccessControl/CmsContext. This is the same request-time pattern the CMS initializer itself uses. api-record-locking now green: 18/18 tests pass — which also end-to-end verifies the base-scalars fix in 4fb2844. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…init (no stub schema) Request-scoped initializers (the CMS facade/storage/AccessControl setup, task/ bulk-action bridges) were hijacking GraphQLContextualSchema.build() purely for its per-request, post-enhancer timing — returning a throwaway "type Query / type Mutation" schema to satisfy the interface, since they contribute no schema content. Introduce a dedicated GraphQLContextInitializer abstraction whose init(ctx) returns void. The GraphQL engine and the CMS route now run initializers AFTER context enhancers and BEFORE contextual schemas — so contextual schemas (e.g. record-locking) reliably see what initializers register (AccessControl/CmsContext), by construction rather than by registration-order luck. Migrated HeadlessCmsInitializer to it (dropping the stub-schema return and the makeExecutableSchema/GraphQLSchema/IGraphQLContextualSchema imports). The remaining costume-wearers (HcmsTasksInitializerImpl and the bulk-action bridges) still use the contextual-schema stub and can be migrated next, now that the proper abstraction exists and is easy to find. Verified: api-headless-cms 856/0, api-record-locking 18/18, full build green. handler-graphql's 6 pre-existing debug-logging test failures are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…ContextInitializer Several classes implemented IGraphQLContextualSchema purely for its per-request build() timing hook, returning a throwaway `type Query\ntype Mutation` stub schema they never used. They are request initializers wearing a contextual-schema costume. Migrate them to the dedicated GraphQLContextInitializer abstraction so their intent (side-effecting per-request init, no schema contribution) is explicit and they run in the initializer phase (after context enhancers, before contextual schemas) rather than emitting a fake schema. Migrated: - api-file-manager: FileModelContextualSchema (resolves GetModelUseCase lazily) - api-scheduler: SchedulerModelContextualSchema - api-workflows: WorkflowsInitializer - api-headless-cms-tasks: HcmsTasksFeature initializer - api-aco: AcoFeature initializer (AcoSchemaFactory kept; it builds real schema) - handler-graphql: registerLegacyPluginsViaGqlContextualSchema legacy bridge - testing: TenancyAndSecurityFeature — seeds tenants and authenticates identity; as an initializer it now runs before the other initializers, so tenant/identity are established before they need them (previously it set them in build(), too late for the migrated initializers). Drop the now-unused @graphql-tools/schema dependency from the migrated feature packages and the unused graphql-tag from handler-graphql. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
…pers These test helpers manually replicate the GraphQL engine flow (resolveAll the enhancers, then contextual schemas, calling enhance()/build() directly). Now that the migrated costume-wearers live under GraphQLContextInitializer, add the missing initializer step — resolveAll(GraphQLContextInitializer) and call init(ctx) after enhancers and before contextual schemas — so they run the same three phases the real engine does. Without this, the migrated initializers never run and the use-cases/models they register are missing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
HttpRouter injects [HttpRoute, { multiple: true }] and so eagerly constructs every
route on each request to match the path. AssetDeliveryRoute took AssetRequestResolver,
AssetResolver, AssetProcessor and AssetOutputStrategy as constructor deps, and
AssetProcessor's PrivateFilesAssetProcessor decorator depends on GetFileUseCase, whose
chain (GetFileRepository -> GetEntryByIdUseCase -> ... -> GetEntriesByIdsRepository)
needs EntryFromStorageTransform. That token is only registered while a CMS GraphQL
request is in flight, so constructing the asset route for an unrelated request (e.g.
/graphql) threw "No registration found for EntryFromStorageTransform" before any handler
ran — failing all file-manager GraphQL tests.
Inject the request Container instead and resolve the four collaborators inside handle(),
so route construction stays cheap and the CMS-entry chain is only resolved when an actual
/files/* request is served.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
useGqlHandler declared a `plugins` param but never registered it, so tests passing legacy ContextPlugins (e.g. assignFileLifecycleEvents, which registers the File before/after create/update/delete event-handler DI instances) had no effect and the lifecycle assertions saw 0 invocations. Bridge `params.plugins` via registerLegacyPluginsViaGqlContextualSchema so each plugin's apply(ctx) runs per request (initializer phase, before resolvers) and its DI registrations are in place when the file CRUD use-cases dispatch their events. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
Member
Author
|
/vitest |
|
Vitest tests have been initiated (for more information, click here). ✨ |
…ollaborators lazily
WebsiteBuilderRedirectsRoute injected GetActiveRedirectsUseCase as a constructor dep,
whose chain pulls request-time CMS tokens (RedirectModel, ListLatestEntriesUseCase ->
EntryFromStorageTransform). Because HttpRouter eagerly constructs every route per request
to path-match, this is the same eager-construction trap fixed in AssetDeliveryRoute. It
doesn't fire today only because WebsiteBuilderFeature pairs the route with
setupWebsiteBuilderModels() (pre-registers those tokens in the request callback before
routing) and tests wire WB via the legacy createWebsiteBuilder() plugin instead. Harden
it regardless: inject RequestContainer and resolve IdentityContext + GetActiveRedirectsUseCase
inside handle(), so it can't regress if that setup ordering ever changes.
Also add a TODO at HttpRouter documenting the systemic root cause — eager construction of
all routes via [HttpRoute, { multiple: true }] — and the proper fix (construct only the
matched route lazily), so the per-route workaround can eventually be removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg3MRCToopzWSTPWqU9Lga
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.
What changed
This PR finishes the rollout of the DI-native (
createHandler/createFeature) approach across allapi-*packages. Every package that previously used the legacycreateLambdaHandler + ApiGatewayFeatureorcreateRawHandlerpatterns has been migrated to wire up through the DI container instead. That includes both the production entry-points (stream handlers, GraphQL handlers, task runners) and the test helpers that exercise them.A few correctness issues surfaced during the migration and are fixed here:
ApiCoreContextEnhancerordering: The core enhancer (which sets upctx.security,ctx.tenancy,ctx.wcp) is now registered as an instance so thatresolveAll(GraphQLContextEnhancer)always places it before other enhancers that depend on those context fields.ModelCacherace condition in@webiny/testing:RegisterExtensionPlugininstances are pre-registered into the DI container beforeHeadlessCmsFeatureenhancers run, so private models (e.g.wbyWorkflow) reachModelCachebeforeAcoContextEnhancerpopulates it.@webiny/testing: Non-apply plugins (CmsModelPlugin,CmsGroupPlugin, etc.) are forwarded asextraPluginstoHeadlessCmsFeature.registerso thatPluginModelsProvidercan find them viactx.plugins.useGraphQLHandler.invokeinapi-headless-cmswas posting to/graphql, but the full CMS schema (createContentModelGroupand all model-specific mutations) is only served at/cms/<type>. Requests now go to the correct endpoint.GraphQLEngineImplbase types: Basetype Query/type Mutationroot types are now injected automatically withassumeValidSDL: true, so individual features can useextend type Mutationwithout needing to define the root themselves.api-dynamodb-to-elasticsearch: AddedcreateDdbToEsStreamHandlerfactory that wires the DynamoDB stream handler through the DI container, replacing the ad-hoc construction in call-sites.Changelog
Title line: Migrate all api-* packages to DI-native handler architecture
Body: All backend packages now use a single, consistent dependency-injection pattern for bootstrapping Lambda handlers, GraphQL engines, and background task runners. Previously each package had its own ad-hoc wiring; they now all go through the same
createHandler/createFeaturepipeline. Several bugs in context-enhancer ordering and model registration were fixed as part of this work.Squash Merge Commit