Agent Skillswebiny/webiny-js › webiny-dependency-injection

webiny-dependency-injection

GitHub

提供 Webiny 框架统一的依赖注入模式,指导如何定义实现类、声明构造函数依赖及导出服务。适用于 API、Admin、CLI 等扩展开发中理解 DI 容器、类型安全及服务访问。

skills/user-skills/dependency-injection/SKILL.md webiny/webiny-js

Trigger Scenarios

编写 Webiny 扩展时 需要理解或实现依赖注入 配置构造函数参数与 dependencies 数组

Install

npx skills add webiny/webiny-js --skill webiny-dependency-injection -g -y
More Options

Non-standard path

npx skills add https://github.com/webiny/webiny-js/tree/next/skills/user-skills/dependency-injection -g -y

Use without installing

npx skills use webiny/webiny-js@webiny-dependency-injection

指定 Agent (Claude Code)

npx skills add webiny/webiny-js --skill webiny-dependency-injection -a claude-code -g -y

安装 repo 全部 skill

npx skills add webiny/webiny-js --all -g -y

预览 repo 内 skill

npx skills add webiny/webiny-js --list

SKILL.md

Frontmatter
{
    "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

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.

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

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

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:

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:

// 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:

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:

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.

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
container.register(Impl) Register a class implementation. Returns RegistrationBuilder with .inSingletonScope(). Multiple registrations of the same abstraction accumulate — resolve() returns the last, resolveAll() returns all.
container.registerInstance(Abstraction, instance) Register a pre-built instance (no constructor resolution).
container.registerFactory(Abstraction, factory) Register a factory function. Called on every resolve().
container.registerDecorator(Decorator) Register a decorator that wraps resolved instances. Applied in registration order.
container.registerComposite(Composite) Register a composite that aggregates all implementations behind a single resolve().

Resolution

Method Description
container.resolve(Abstraction) Resolve single instance (last registered wins). Throws if not registered.
container.resolveAll(Abstraction) Resolve all registered implementations as T[]. Returns empty array if none.
container.createChildContainer() Create a child container that inherits parent registrations.

Lifetime Scopes

  • Transient (default): New instance on every resolve().
  • Singleton (.inSingletonScope()): Cached after first resolution, one instance per container.

Convention: Use cases = transient. Repositories, gateways, services, registries = singleton.

Decorators

Decorators wrap resolved instances. The decoratee is always the last constructor parameter. The dependencies array does NOT include the decoratee.

class LoggingServiceDecorator implements MyService.Interface {
  constructor(
    private logger: Logger.Interface,
    private decoratee: MyService.Interface // LAST param — injected automatically
  ) {}

  execute() {
    this.logger.info("Before");
    this.decoratee.execute();
  }
}

export const MyServiceLoggingDecorator = MyService.createDecorator({
  decorator: LoggingServiceDecorator,
  dependencies: [Logger] // decoratee is NOT listed
});

// Registration:
container.registerDecorator(MyServiceLoggingDecorator);

Composites

Composites aggregate multiple implementations behind a single resolve() call. Created via Abstraction.createComposite():

class AllValidatorsComposite implements Validator.Interface {
  constructor(private validators: Validator.Interface[]) {}

  validate(input: unknown) {
    for (const v of this.validators) v.validate(input);
  }
}

export const ValidatorComposite = Validator.createComposite({
  implementation: AllValidatorsComposite,
  dependencies: [[Validator, { multiple: true }]]
});

// Registration:
container.registerComposite(ValidatorComposite);

Key Rules

  1. Always import from the feature path, not the package root.
  2. Use Feature.Interface for constructor parameter types.
  3. The dependencies array order must match the constructor parameter order.
  4. Read the abstractions.ts file in the feature folder to see available methods.
  5. Extensions with no dependencies use dependencies: [].
  6. BuildParams.get<T>(name) returns T | null — always type the receiving property/variable as nullable (e.g. string | null) and handle the null case.
  7. BuildParam declarations belong inside the extension's Extension.tsx, not in webiny.config.tsx. Expose required params as React props on the extension component so the consumer decides where values come from (see webiny-full-stack-architect skill for the full pattern).
  8. For multi-injection, type the constructor param as T[] and use [Abstraction, { multiple: true }] in the dependencies array.
  9. Each implementation of a multi-bound abstraction is a separate container.register() call — they accumulate.

Related Skills

  • webiny-custom-graphql-api -- DI in GraphQL schema extensions
  • webiny-cli-extensions -- DI in CLI command extensions
  • webiny-full-stack-architect -- Full-stack extension skeleton and registration pattern
  • webiny-api-architect -- API-side architecture using DI
  • webiny-admin-architect -- Admin-side architecture using DI

Version History

  • 80eb1c5 Current 2026-08-20 10:07

Same Skill Collection

.claude/skills/grill-me/SKILL.md
.claude/skills/prd-to-plan/SKILL.md
.claude/skills/preflight/SKILL.md
.claude/skills/tester/SKILL.md
.claude/skills/write-a-prd/SKILL.md
skills/repo-skills/add-feature-flag/SKILL.md
skills/user-skills/admin/admin-architect/SKILL.md
skills/user-skills/admin/admin-permissions/SKILL.md
skills/user-skills/admin/form-model/SKILL.md
skills/user-skills/admin/new-entry-wizard/SKILL.md
skills/user-skills/admin/website-builder/page-settings/SKILL.md
skills/user-skills/admin/website-builder/wb-preview-url-modifier/SKILL.md
skills/user-skills/api-bundle-size-limit/SKILL.md
skills/user-skills/api/api-architect/SKILL.md
skills/user-skills/api/cms-bulk-actions/SKILL.md
skills/user-skills/api/custom-field-type/SKILL.md
skills/user-skills/api/event-handler-pattern/SKILL.md
skills/user-skills/api/graphql-api/SKILL.md
skills/user-skills/api/http-route/SKILL.md
skills/user-skills/api/permissions/SKILL.md
skills/user-skills/api/use-case-pattern/SKILL.md
skills/user-skills/api/v5-to-v6-migration/SKILL.md
skills/user-skills/api/websocket-notifications/SKILL.md
skills/user-skills/cli-extensions/SKILL.md
skills/user-skills/configure-auth0/SKILL.md
skills/user-skills/configure-entraid/SKILL.md
skills/user-skills/configure-okta/SKILL.md
skills/user-skills/full-stack-architect/SKILL.md
skills/user-skills/generated/api/aco/SKILL.md
skills/user-skills/generated/api/cms/SKILL.md
skills/user-skills/generated/api/file-manager/SKILL.md
skills/user-skills/generated/api/scheduler/SKILL.md
skills/user-skills/generated/api/security/SKILL.md
skills/user-skills/generated/api/system/SKILL.md
skills/user-skills/generated/api/tenancy/SKILL.md
skills/user-skills/generated/api/tenant-manager/SKILL.md
skills/user-skills/generated/api/website-builder/SKILL.md
skills/user-skills/generated/infra/SKILL.md
skills/user-skills/infrastructure-extensions/SKILL.md
skills/user-skills/local-development/SKILL.md
skills/user-skills/mailer-smtp/SKILL.md
skills/user-skills/project-structure/SKILL.md
.claude/skills/webiny-skill-creator/SKILL.md
skills/user-skills/admin/ui-extensions/SKILL.md
skills/user-skills/api/ai-powerups-content/SKILL.md
skills/user-skills/cognito-federation/SKILL.md
skills/user-skills/content-models/SKILL.md
skills/user-skills/generated/admin/aco/SKILL.md
skills/user-skills/generated/admin/ai-powerups/SKILL.md

Metadata

Files
0
Version
80eb1c5
Hash
4ff083f0
Indexed
2026-08-20 10:07

inicio - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-22 05:41
浙ICP备14020137号-1 $mapa de visitantes$