Skip to main content

webiny-dependency-injection

The universal createImplementation DI pattern and all injectable services. Use this skill when the developer is writing any Webiny extension and needs to understand dependency injection, constructor injection, how to access Logger/BuildParams/IdentityContext, how to inject CMS use-cases (list/get/create/update/delete entries), or how the dependencies array works. This is the connective tissue across all extension types -- API, Admin, CLI, and Infrastructure.

来源信息

仓库
webiny/webiny-js
最近来源活动
2026年7月24日 12:20
检测到的 SKILL.md 语言
英语
星标
8,049
分支
682

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
webiny-dependency-injection
description
The universal createImplementation DI pattern and all injectable services. Use this skill when the developer is writing any Webiny extension and needs to understand dependency injection, constructor injection, how to access Logger/BuildParams/IdentityContext, how to inject CMS use-cases (list/get/create/update/delete entries), or how the dependencies array works. This is the connective tissue across all extension types -- API, Admin, CLI, and Infrastructure.
# Dependency Injection Patterns ## TL;DR Every Webiny extension type uses the same DI pattern: define a class implementing `*.Interface`, declare dependencies in the constructor, and export via `*.createImplementation({ implementation, dependencies })`. The DI container automatically provides the required services, ensures type safety, and validates at compile time. This pattern is the connective tissue across all extension types -- API, Admin, CLI, and Infrastructure. ## Working Context This skill applies to both **extension developers** (working in `extensions/`) and **core developers** (working in `packages/`). The DI pattern is universal — only the import paths differ. | | Extensions (`extensions/`) | Core (`packages/`) | | ----------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | **`createAbstraction`** | `from "webiny/api"` or `from "webiny/admin"` | `from "@webiny/feature/api"` or `from "@webiny/feature/admin"` | | **`createFeature`** | `from "webiny/api"` or `from "webiny/admin"` | `from "@webiny/feature/api"` or `from "@webiny/feature/admin"` | | **Factory imports** | `from "webiny/api/graphql"`, `from "webiny/api/cms/model"`, etc. | Direct package paths: `from "@webiny/handler-graphql/..."`, `from "@webiny/api-headless-cms/..."` | | **Catalog paths** | Use the `Import:` path | Use the `Source:` path | Detect which context you're in by checking the file path: `extensions/` → extension mode, `packages/` → core mode. The `dependencies` array, constructor injection, scoping rules, and all other patterns are identical in both contexts. ## The Universal Pattern ```typescript import { SomeFactory } from "webiny/some/path"; import { Logger, BuildParams } from "webiny/api"; class MyImplementation implements SomeFactory.Interface { constructor( private logger: Logger.Interface, private buildParams: BuildParams.Interface ) {} execute(/* factory-specific params */) { this.logger.info("Doing something..."); // buildParams.get() returns T | null — always account for null. const value = this.buildParams.get<string>("MY_PARAM"); } } export default SomeFactory.createImplementation({ implementation: MyImplementation, dependencies: [Logger, BuildParams] }); ``` Key rules: 1. **One class per file** -- each extension file exports a single implementation. 2. **Constructor injection** -- dependencies are received as constructor parameters, in the same order as the `dependencies` array. 3. **Dependencies array** -- must exactly match the constructor parameter order and types. 4. **Interface types** -- always type constructor params as `Feature.Interface`. ## Where This Pattern Appears | Extension Type | Factory | Import Path | | --------------- | ---------------------- | ------------------------ | | Content Models | `ModelFactory` | `"webiny/api/cms/model"` | | GraphQL Schemas | `GraphQLSchemaFactory` | `"webiny/api/graphql"` | | API Keys | `ApiKeyFactory` | `"webiny/api/security"` | | CLI Commands | `CliCommandFactory` | `"webiny/cli/command"` | | Pulumi Handlers | `CorePulumi` | `"webiny/infra/core"` | > **Event handlers** use the same `createImplementation` pattern but are not injectable dependencies. ## Examples Across Extension Types ### API Extension (GraphQL Schema with DI) GraphQL schemas use the **builder pattern**. The `execute` method receives a `builder` and uses `addTypeDefs` and `addResolver` to define the schema. Resolver-level DI is declared per-resolver via `dependencies` in `addResolver`, resolved at request time from the request-scoped container. ```typescript import { GraphQLSchemaFactory } from "webiny/api/graphql"; import { IdentityContext } from "webiny/api/security"; class WhoAmISchema implements GraphQLSchemaFactory.Interface { async execute( builder: GraphQLSchemaFactory.SchemaBuilder ): Promise<GraphQLSchemaFactory.SchemaBuilder> { builder.addTypeDefs(/* GraphQL */ ` extend type Query { whoAmI: String } `); builder.addResolver({ path: "Query.whoAmI", dependencies: [IdentityContext], resolver: (identityContext: IdentityContext.Interface) => { return () => { const identity = identityContext.getIdentity(); return `Hello, ${identity.displayName}!`; }; } }); return builder; } } export default GraphQLSchemaFactory.createImplementation({ implementation: WhoAmISchema, dependencies: [] }); ``` Note: `GraphQLSchemaFactory` implementations typically have `dependencies: []` because DI happens at the resolver level via `addResolver({ dependencies })`, not at the class constructor level. ### CLI Command with DI ```typescript import { Ui } from "webiny/cli"; import { CliCommandFactory } from "webiny/cli/command"; class MyCommandImpl implements CliCommandFactory.Interface<{ name: string }> { constructor(private ui: Ui.Interface) {} execute(): CliCommandFactory.CommandDefinition<{ name: string }> { return { name: "greet", description: "Greet someone", params: [{ name: "name", description: "Name", type: "string" }], handler: async params => { this.ui.success(`Hello, ${params.name}!`); } }; } } export default CliCommandFactory.createImplementation({ implementation: MyCommandImpl, dependencies: [Ui] }); ``` ### Pulumi Handler with DI ```typescript import { Ui } from "webiny/infra"; import { CorePulumi } from "webiny/infra/core"; class MyPulumiImpl implements CorePulumi.Interface { constructor(private ui: Ui.Interface) {} execute(app: any) { this.ui.info("Deploying with environment:", app.env); } } export default CorePulumi.createImplementation({ implementation: MyPulumiImpl, dependencies: [Ui] }); ``` ## Advanced Dependency Options The `dependencies` array supports three forms per entry: | Form | Meaning | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `Abstraction` | Single required dependency (shorthand) | | `[Abstraction, { optional: true }]` | Single optional dependency — injects `undefined` if not registered | | `[Abstraction, { multiple: true }]` | Multi-injection — injects **all** registered implementations as `T[]` | | `[Abstraction, { multiple: true, optional: true }]` | Multi-injection, optional — injects `undefined` if none registered (vs empty `[]` with just `multiple`) | ### Multi-injection (`{ multiple: true }`) Use when a class needs all registered implementations of an abstraction. The container calls `resolveAll()` internally and injects the results as an array. **Abstraction:** ```ts interface IPageType { name: string; label: string; modify(form: IFormModel): void; } export const PageType = createAbstraction<IPageType>("PageType"); export namespace PageType { export type Interface = IPageType; } ``` **Multiple implementations registered separately:** ```ts // StaticPageType.ts class StaticPageTypeImpl implements PageType.Interface { name = "static"; label = "Static Page"; modify(form: IFormModel) { /* no-op — base form is sufficient */ } } export const StaticPageType = PageType.createImplementation({ implementation: StaticPageTypeImpl, dependencies: [] }); // ProductPageType.ts (in another package/extension) class ProductPageTypeImpl implements PageType.Interface { name = "product"; label = "Product Page"; modify(form: IFormModel) { form.fields(fields => ({ product: fields.select().label("Product").required("Product is required") })); form.field("title").disabled(true); form.field("path").disabled(true); } } export const ProductPageType = PageType.createImplementation({ implementation: ProductPageTypeImpl, dependencies: [] }); ``` **Consumer injects the array:** ```ts class CreatePagePresenterImpl implements CreatePagePresenter.Interface { constructor( private factory: FormModelFactory.Interface, private pageTypes: PageType.Interface[], private modifiers: CreatePageFormModifier.Interface[] ) {} } export const CreatePagePresenter = PresenterAbstraction.createImplementation({ implementation: CreatePagePresenterImpl, dependencies: [ FormModelFactory, [PageType, { multiple: true }], [CreatePageFormModifier, { multiple: true }] ] }); ``` **Registration — each implementation is a separate `container.register()` call:** ```ts export const CreatePageFeature = createFeature({ name: "CreatePage", register(container) { container.register(StaticPageType); // first PageType impl container.register(ProductPageType); // second PageType impl container.register(CreatePagePresenter); } }); ``` When `CreatePagePresenter` is resolved, `pageTypes` receives `[StaticPageTypeImpl, ProductPageTypeImpl]` in registration order. ### Optional dependency (`{ optional: true }`) Use when a dependency may not be registered. The container injects `undefined` instead of throwing. ```ts class MyPresenterImpl { constructor( private required: RequiredService.Interface, private analytics: AnalyticsService.Interface | undefined ) {} } export const MyPresenter = Abstraction.createImplementation({ implementation: MyPresenterImpl, dependencies: [RequiredService, [AnalyticsService, { optional: true }]] }); ``` ## Container API Reference ### Registration | Method | Description |
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看