@aws/durable-execution-sdk-js

中文文档
3.0.0发布于 2026/10/26Apache-2.0TypeScriptESM + CJS构建来源已验证近 30 天下载 390.7万

AWS Durable Execution Language SDK for TypeScript

原始使用文档
文档目录31 节
  1. Features
  2. Installation
  3. Quick Start
  4. Documentation
  5. Core Concepts
  6. DurableContext
  7. Step Operations
  8. Child Contexts
  9. Invoking Other Functions
  10. Wait Operations
  11. Conditional Waiting
  12. External Callbacks
  13. Batch Operations
  14. Map
  15. Parallel
  16. Early completion
  17. Promise Combinators
  18. Operation Subtypes
  19. Configuration
  20. Dynamic Instrumentation Plugins
  21. Instrumentation Operation Status and Errors
  22. Retry Strategies
  23. Step Semantics
  24. Jitter Strategies
  25. Custom Transport
  26. Logging
  27. Powertools for AWS Lambda (TypeScript)
  28. Runtime requirements
  29. Runtimes without AsyncLocalStorage
  30. License
  31. SDK 3.x plugin migration

AWS Durable Execution SDK for JavaScript

TypeScript SDK for building stateful, long-running AWS Lambda functions with automatic state persistence, retry logic, and workflow orchestration.

Features

  • Durable Execution: Automatically persists state and resumes from checkpoints
  • Automatic Retries: Configurable retry strategies with exponential backoff and jitter
  • Workflow Orchestration: Compose complex workflows with steps, child contexts, and parallel execution
  • External Integration: Wait for callbacks from external systems
  • Batch Operations: Process arrays with concurrency control and completion policies
  • Type Safety: Full TypeScript support with comprehensive type definitions

Installation

npm install @aws/durable-execution-sdk-js

Quick Start

import {
  withDurableExecution,
  DurableContext,
} from "@aws/durable-execution-sdk-js";

const handler = async (event: any, context: DurableContext) => {
  // Execute a durable step with automatic retry
  const userData = await context.step("fetch-user", async () =>
    fetchUserFromDB(event.userId),
  );

  // Wait for 5 seconds
  await context.wait({ seconds: 5 });

  // Process data in another step
  const result = await context.step("process-user", async () =>
    processUser(userData),
  );

  return result;
};

export const lambdaHandler = withDurableExecution(handler);

Documentation

This README provides a quick reference for the SDK's main features. For more detailed information:

  • API Reference - Complete technical reference with detailed type definitions and operation specifications
  • Concepts and Use Cases - Learn about the replay model, best practices, and real-world examples including GenAI agents, human-in-the-loop workflows, and saga patterns
  • Contributing - Learn about contributing to the AWS Durable Execution SDK for JavaScript

Core Concepts

DurableContext

The DurableContext is the main interface for building durable workflows. It provides methods for executing steps, waiting, invoking other functions, and coordinating parallel operations.

Step Operations

Execute atomic operations with automatic retry and state persistence:

// Named step with retry configuration
const result = await context.step(
  "fetch-data",
  async (ctx) => {
    return await fetchFromAPI();
  },
  {
    retryStrategy: (error, attempt) => ({
      shouldRetry: attempt < 3,
      delay: { seconds: Math.pow(2, attempt) },
    }),
  },
);

// Anonymous step
const data = await context.step(async () => processData());

Important: step() is for single atomic operations only. To group multiple durable operations, use runInChildContext().

Child Contexts

Group multiple durable operations with isolated state tracking:

const orderResult = await context.runInChildContext(
  "process-order",
  async (childCtx) => {
    // Child context has its own step counter and state
    const validated = await childCtx.step("validate", async () =>
      validateOrder(order),
    );
    await childCtx.wait({ seconds: 1 });
    const charged = await childCtx.step("charge", async () =>
      chargePayment(validated),
    );
    return charged;
  },
);

Invoking Other Functions

Call another AWS Lambda function and wait for it complete:

const result = await context.invoke(
  "process-payment",
  "arn:aws:lambda:us-east-1:123456789012:function:payment-processor",
  { amount: 100, currency: "USD" },
);

Wait Operations

Pause execution for a specified duration:

// Wait 30 seconds
await context.wait({ seconds: 30 });

// Named wait for tracking
await context.wait("rate-limit-delay", { seconds: 5 });

Conditional Waiting

Wait until a condition is met by periodically checking state:

const finalState = await context.waitForCondition(
  "wait-for-job",
  async (currentState, ctx) => {
    const status = await checkJobStatus(currentState.jobId);
    return { ...currentState, status };
  },
  {
    initialState: { jobId: "job-123", status: "pending" },
    waitStrategy: (state, attempt) => {
      if (state.status === "completed") {
        return { shouldContinue: false };
      }
      return {
        shouldContinue: true,
        delay: { seconds: Math.min(attempt * 2, 60) },
      };
    },
  },
);

External Callbacks

Wait for external systems to complete operations:

// Create a callback and send ID to external system
const [callbackPromise, callbackId] = await context.createCallback(
  "external-approval",
  { timeout: { minutes: 3 } },
);

await sendApprovalRequest(callbackId, requestData);
const approvalResult = await callbackPromise;

// Or use waitForCallback with submitter function
const result = await context.waitForCallback(
  "wait-for-webhook",
  async (callbackId, ctx) => {
    await submitToExternalAPI(callbackId);
  },
  { timeout: { minutes: 5 } },
);

Batch Operations

Map

Process arrays of items, applying durable operations to each with concurrency control:

const results = await context.map(
  "process-users",
  users,
  async (ctx, user, index) => {
    return await ctx.step(`process-${user.id}`, async () => processUser(user));
  },
  {
    maxConcurrency: 5,
    completionConfig: {
      minSuccessful: 8,
      toleratedFailureCount: 2,
    },
    itemNamer: (user, index) => `User-${user.id}`,
  },
);

// Check results
console.log(
  `Succeeded: ${results.successCount}, Failed: ${results.failureCount}`,
);
results.throwIfError(); // Throws if any failures

Note: map() executes durable operations (steps, waits, etc.) within the same Lambda invocation using child contexts for isolation. It does not spawn separate Lambda functions.

Parallel

Execute multiple branches with durable operations in parallel:

const results = await context.parallel(
  "parallel-tasks",
  [
    { name: "task1", func: async (ctx) => ctx.step(async () => fetchData1()) },
    { name: "task2", func: async (ctx) => ctx.step(async () => fetchData2()) },
    async (ctx) => ctx.step(async () => fetchData3()),
  ],
  {
    maxConcurrency: 2,
    completionConfig: { minSuccessful: 2 },
  },
);

Note: parallel() executes durable operations within the same Lambda invocation. Each branch runs in its own child context with isolated state tracking.

Early completion

A map() or parallel() completes early when its completion rule is met before every item has finished. With no completionConfig, the rule is fail-fast: the first failure completes the batch. minSuccessful, toleratedFailureCount, toleratedFailurePercentage and shouldComplete set other rules. Setting any of them replaces fail-fast.

The batch checks its rule each time an item's result reaches it. Once the rule is met, the remaining items are abandoned:

  • An item that is running is reported as STARTED. It is not counted in successCount or failureCount.
  • An item that had not started, because of maxConcurrency, does not appear in all.
  • The batch does not wait for abandoned items and does not stop them. Once the batch's context finishes, the item's checkpoints are dropped. An abandoned item stops only when it awaits a dropped checkpoint. Until then its code can keep running while the invocation is active, and its side effects can happen. Nothing waits for it, so the invocation can end first:
    • A step whose function is running keeps running while the invocation is active. It is not guaranteed to finish. If it finishes after the batch's context, its result is not recorded.
    • A new step with the default StepSemantics.AtLeastOncePerRetry runs its function once, and the history has no record of it. A new waitForCondition runs its check function once. The item then stops.
    • runInChildContext does not await its own checkpoints. So its function runs, and the operations inside it follow these rules.
    • A new wait, invoke, createCallback, waitForCallback, or step with StepSemantics.AtMostOncePerRetry stops the item before it takes effect. "New" means the item starts it after the batch's context finished. An operation started earlier may already have recorded its start and taken effect.

What happens next depends on what the handler does after the batch:

  • If the handler returns, the invocation can end while abandoned code still runs. That code is then not guaranteed to finish.
  • If the handler waits, for example on a wait or a callback, the invocation cannot suspend while an abandoned step function or waitForCondition check is still running. Such an operation counts as running until its function returns and its dropped checkpoint releases it. So a slow abandoned function keeps the invocation active, which adds to its duration and cost.

An abandoned item can record its outcome before the batch's context finishes. This includes an item that recorded its outcome before the item that completed the batch, but whose result reached the batch later. The execution history then shows its child context as SUCCEEDED or FAILED, while the BatchResult reports it as STARTED. With NestingType.FLAT, the item has no child context, and the history shows its operations instead.

The handler receives only the BatchResult. Replay returns the same completed items and the same completionReason. Replay can omit the STARTED items, so started(), startedCount and totalCount can differ after replay. So base decisions on the completed items, and do not reconcile the BatchResult with the execution history.

To get the result of every item, use a rule that cannot complete early. Set toleratedFailurePercentage: 100, and leave minSuccessful, toleratedFailureCount and shouldComplete unset. The batch then waits for every item. Failed items appear in failed(). The call does not throw, so call throwIfError() if a failure must stop the handler:

const results = await context.map("process-all", items, processItem, {
  completionConfig: { toleratedFailurePercentage: 100 },
});
const failures = results.failed(); // every failed item, with its error

Promise Combinators

For fast, in-memory operations (use map() or parallel() for durable operations):

// Wait for all promises
const [user, posts, comments] = await context.promise.all([
  fetchUser(userId),
  fetchPosts(userId),
  fetchComments(userId),
]);

// Race promises
const fastest = await context.promise.race([
  fetchFromPrimary(),
  fetchFromSecondary(),
]);

// Wait for first success
const result = await context.promise.any([
  fetchFromSource1(),
  fetchFromSource2(),
  fetchFromSource3(),
]);

// Wait for all to settle
const results = await context.promise.allSettled([operation1(), operation2()]);

Note: Promise combinators accept already-executing promises and cannot provide concurrency control or durability. Use map() or parallel() for durable, controlled execution.

Operation Subtypes

runInChildContext, step, and createCallback accept a subType option. A function that composes several operations can use it to label its parts. The SDK records the subtype in the checkpoint and passes it to plugins. So execution history and plugins can filter the parts without parsing names.

ctx.runInChildContext(
  name,
  async (child) => {
    const receipt = await child.step("charge", async () => charge(orderId), {
      subType: "OrderCharge",
    });
    await child.step("notify", async () => notify(receipt), {
      subType: "OrderNotify",
    });
    return receipt;
  },
  { subType: "ChargeAndNotify" },
);

The service accepts 1 to 32 characters from [a-zA-Z0-9-_]. An empty string selects the default subtype, such as Step or Callback. Replay compares the subtype with the checkpoint. So changing a subtype is a breaking change for executions in flight.

@aws/durable-execution-sdk-js-extras ships ready-made operations built this way, such as microvm.

Configuration

Dynamic Instrumentation Plugins

Instrumentation plugins can be selected at Lambda cold start without importing them in the function artifact. Install a provider package in the function or a Lambda layer, then set an ordered list of package or module specifiers:

DURABLE_EXECUTION_PLUGINS=@example/durable-otel,@example/durable-audit/provider

The SDK imports each module once when withDurableExecution(...) initializes the wrapped handler. An unset or blank variable preserves the existing behavior. Plugins passed through DurableExecutionConfig.plugins run first in their configured order; environment-selected plugins follow in the order listed in DURABLE_EXECUTION_PLUGINS. Both sources are additive, including when they create the same plugin type.

Provider modules export a factory named durableExecutionPluginProvider. A factory is an object with a createPlugin(info) method; a bare function is not one and is rejected. The SDK calls createPlugin once per invocation, with that invocation's InvocationInfo, so the instance it returns serves exactly one invocation:

import type {
  DurableInstrumentationPlugin,
  DurableInstrumentationPluginFactory,
} from "@aws/durable-execution-sdk-js";

class AuditPlugin implements DurableInstrumentationPlugin {
  constructor(
    private readonly exporter: Exporter,
    private readonly executionArn: string,
  ) {}
  // Implement the lifecycle hooks needed by this plugin.
}

// Built once for the execution environment and shared by every instance.
const exporter = new Exporter();

export const durableExecutionPluginProvider: DurableInstrumentationPluginFactory<AuditPlugin> =
  {
    createPlugin: (info) => new AuditPlugin(exporter, info.executionArn),
  };

Because an instance is dropped when its invocation returns, a plugin never has to key per-execution state by execution ARN, and two executions running concurrently in one execution environment cannot observe each other's state. State that belongs to the execution environment — an exporter, a tracer provider, a scheduler — belongs outside the factory, as above.

The module specifier must be resolvable through normal application module resolution or Node.js module paths. For a Lambda layer, package the provider and its dependencies under nodejs/node_modules:

plugin-layer.zip
`-- nodejs
    `-- node_modules
        `-- @example
            `-- durable-audit

Malformed configuration, missing modules or exports, a durableExecutionPluginProvider without a createPlugin method, and an entry in DurableExecutionConfig.plugins without one are reported as PluginLoadError failures before execution state is read — the two sources are checked by the same rule, because an entry with no createPlugin could never produce a plugin. A createPlugin that throws when it runs, or returns nothing, is contained like any other plugin failure: that plugin sits out the invocation and the execution is unaffected.

Instrumentation Operation Status and Errors

Use OperationInfo.status to identify terminal operation states such as FAILED, CANCELLED, TIMED_OUT, and STOPPED. OperationInfo.error contains optional error details; a failed operation can have error === undefined, including in replay and onOperationChange metadata. Missing or empty checkpointed error data does not invent an error. Supplied partial details, including explicitly empty strings or an empty stack trace, remain available.

This also applies to API-v1 plugins: code that uses only the presence of error as its failure signal should inspect status instead. For an attempt-end hook, use AttemptEndInfo.outcome to determine whether that attempt failed. An external callback failure without error details still rejects the callback promise and retains its failed operation status; optional instrumentation details do not change the caller's outcome or persisted state.

Retry Strategies

Custom retry strategy:

await context.step("custom-retry", async () => riskyOperation(), {
  retryStrategy: (error, attempt) => ({
    shouldRetry: attempt < 5 && error.message.includes("timeout"),
    delay: { seconds: attempt * 2 },
  }),
});

Step Semantics

Control execution guarantees:

import { StepSemantics } from "@aws/durable-execution-sdk-js";

// At-least-once per retry (default)
await context.step("retriable-operation", async () => sendNotification(), {
  semantics: StepSemantics.AtLeastOncePerRetry,
});

// At-most-once per retry
await context.step("idempotent-operation", async () => updateDatabase(), {
  semantics: StepSemantics.AtMostOncePerRetry,
});

Important: These semantics apply per retry, not per overall execution:

  • AtLeastOncePerRetry: The step will execute at least once on each retry attempt. If the step succeeds but the checkpoint fails (e.g., sandbox crash), the step will re-execute on replay.
  • AtMostOncePerRetry: The step will execute at most once per retry attempt. A checkpoint is created before execution, so if a failure occurs after the checkpoint but before step completion, the previous step retry attempt is skipped on replay.

To achieve at-most-once semantics on a step-level, use a custom retry strategy:

await context.step(
  "truly-once-only",
  async () => callThatCannotTolerateDuplicates(),
  {
    semantics: StepSemantics.AtMostOncePerRetry,
    retryStrategy: () => ({ shouldRetry: false }), // No retries
  },
);

Without this, a step using AtMostOncePerRetry with retries enabled could still execute multiple times across different retry attempts.

Jitter Strategies

Prevent thundering herd:

import {
  JitterStrategy,
  createRetryStrategy,
} from "@aws/durable-execution-sdk-js";

const retryStrategy = createRetryStrategy({
  maxAttempts: 5,
  initialDelay: { seconds: 1 },
  maxDelay: { seconds: 60 },
  exponentialDelayFactor: 2,
  jitterStrategy: JitterStrategy.FULL,
});

Custom Transport

By default the SDK talks to the Lambda durable execution APIs. You can replace that transport with your own by implementing DurableExecutionClient, which has two operations and names no AWS types:

import {
  CheckpointDurableExecutionRequest,
  CheckpointDurableExecutionResponse,
  DurableExecutionClient,
  DurableExecutionClientError,
  DurableExecutionClientErrorScope,
  GetDurableExecutionStateRequest,
  GetDurableExecutionStateResponse,
  withDurableExecution,
} from "@aws/durable-execution-sdk-js";

class HttpDurableExecutionClient implements DurableExecutionClient {
  constructor(private readonly endpoint: string) {}

  async getExecutionState(
    params: GetDurableExecutionStateRequest,
  ): Promise<GetDurableExecutionStateResponse> {
    const response = await fetch(`${this.endpoint}/state`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(params),
    });

    if (!response.ok) {
      throw this.toClientError(
        "Failed to read execution state",
        response.status,
      );
    }

    return response.json() as Promise<GetDurableExecutionStateResponse>;
  }

  async checkpoint(
    params: CheckpointDurableExecutionRequest,
  ): Promise<CheckpointDurableExecutionResponse> {
    const response = await fetch(`${this.endpoint}/checkpoint`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(params),
    });

    if (!response.ok) {
      throw this.toClientError("Checkpoint failed", response.status);
    }

    return response.json() as Promise<CheckpointDurableExecutionResponse>;
  }

  // Tell the SDK whether to end just this invocation or the whole execution.
  private toClientError(
    message: string,
    status: number,
  ): DurableExecutionClientError {
    return new DurableExecutionClientError(`${message}: ${status}`, {
      scope:
        status >= 400 && status < 500 && status !== 429
          ? // A rejected request will not succeed on retry.
            DurableExecutionClientErrorScope.EXECUTION
          : // Throttling and server-side errors may.
            DurableExecutionClientErrorScope.INVOCATION,
    });
  }
}

export const handler = withDurableExecution(myHandler, {
  durableExecutionClient: new HttpDurableExecutionClient(
    process.env.DURABLE_ENDPOINT!,
  ),
});

Without a stated scope, every failure is treated as transient, so a permanent one is retried until the execution times out. Note that a stated scope is believed ahead of the SDK's own error inspection, so a transport that wraps another one takes over responsibility for classifying the errors it wraps.

To keep the Lambda transport but configure the underlying client yourself, pass it to DurableExecutionApiClient rather than using the deprecated client option:

import { DurableExecutionApiClient } from "@aws/durable-execution-sdk-js";
import { LambdaClient } from "@aws-sdk/client-lambda";

export const handler = withDurableExecution(myHandler, {
  durableExecutionClient: new DurableExecutionApiClient(
    new LambdaClient({ region: "us-west-2", maxAttempts: 5 }),
  ),
});

Logging

Access enriched logger:

const handler = async (event: any, context: DurableContext) => {
  context.logger.info("Processing started", { userId: event.userId });

  try {
    const result = await context.step("process", async (ctx) => {
      ctx.logger.debug("Step executing");
      return processData();
    });

    context.logger.info("Processing completed", { result });
    return result;
  } catch (error) {
    context.logger.error("Processing failed", error);
    throw error;
  }
};

Custom logger:

context.configureLogger({
  customLogger: {
    log: (level, message, data, error) =>
      console.log(`[${level}] ${message}`, data),
    error: (message, error, data) => console.error(message, error, data),
    warn: (message, data) => console.warn(message, data),
    info: (message, data) => console.info(message, data),
    debug: (message, data) => console.debug(message, data),
  },
  modeAware: false, // Optional: show logs during replay (default: true)
});

Tip for local development: Set modeAware: false to see all logs during replay, which can be helpful for debugging:

context.configureLogger({ modeAware: false });

Powertools for AWS Lambda (TypeScript)

createPowertoolsLogger wraps a Powertools Logger. Every log entry then contains the durable execution metadata: executionArn, requestId, tenantId, operationId, operationName, and attempt. Keys without a value are omitted. Every entry also contains the fields that the configured plugins add, such as the OTel plugin's traceId and spanId, as entries of the default logger do.

import { Logger } from "@aws-lambda-powertools/logger";
import {
  createPowertoolsLogger,
  DurableContext,
  PowertoolsDurableLogger,
  withDurableExecution,
} from "@aws/durable-execution-sdk-js";

const logger = new Logger({ serviceName: "my-service" });

export const handler = withDurableExecution(
  async (
    event: { orderId: string },
    context: DurableContext<PowertoolsDurableLogger<Logger>>,
  ) => {
    context.configureLogger({ customLogger: createPowertoolsLogger(logger) });
    context.logger.info("workflow-started", { orderId: event.orderId });
  },
);
  • Declare context as DurableContext<PowertoolsDurableLogger<Logger>>, as above. Then context.logger and the step loggers accept the Powertools argument types. Without the declaration, context.logger accepts any arguments.
  • The keys use the same names as the default logger and the Python SDK.
  • A key passed in the log call replaces the metadata key with the same name. The metadata replaces keys set with appendKeys or persistentKeys, because Powertools applies per-call keys after those.
  • The adapter reads the metadata on every call. So one adapter instance logs the correct operationId and attempt for concurrent steps and child contexts.
  • Powertools reads only message from an Error passed as the first argument, and writes no stack. To log the stack, pass the Error as a later argument: context.logger.error("charge failed", error).
  • The SDK does not depend on @aws-lambda-powertools/logger. Install it in your function.
  • logger.addContext(context.lambdaContext) adds the Powertools Lambda context fields, such as cold_start and function_name. It also adds function_request_id, which repeats requestId. For a tenant-scoped execution it adds tenant_id, which repeats tenantId. Powertools stores these fields on the Logger instance. So when several invocations run at the same time in one process, as on Lambda Managed Instances, they carry the values of the invocation that called addContext last. The durable metadata fields do not have this problem.

Leave the POWERTOOLS_DEV environment variable unset in deployed functions. When it is set, Powertools writes through the global console. With Lambda's JSON log format, Lambda wraps each console line in a JSON object. So each Powertools entry then appears as a string inside the message field of a second JSON object.

The powertools-logger example used to define its own adapter class, which wrote snake_case keys (execution_arn, request_id, operation_id). createPowertoolsLogger writes camelCase keys. If you copied that example, update log queries and metric filters to the camelCase names.

Runtime requirements

The SDK targets the Lambda managed Node.js runtimes (nodejs22.x, nodejs24.x), where everything below is satisfied automatically. It also runs on a container image carrying your own JavaScript runtime, which is how durable functions can be deployed on a runtime Lambda does not manage.

At load, the SDK imports these Node builtins: async_hooks, crypto, events, node:console, node:crypto, node:fs/promises (mkdir, readFile, writeFile), node:path, node:url and node:util. Note that a named ESM import of a missing export is fatal at module load, not at first use — so a runtime that provides a module but not the member the SDK imports from it will fail with a SyntaxError before your handler runs.

It also loads @aws-sdk/client-lambda on first checkpoint, via a dynamic import. If you bundle with @aws-sdk marked external, the runtime must provide that client.

Runtimes without AsyncLocalStorage

Some lightweight runtimes — LLRT, for example — implement only part of the Node API. The SDK detects and works around the two gaps that would otherwise be fatal:

  • async_hooks.AsyncLocalStorage is used to track which durable operation is active across await boundaries. Without it the SDK falls back to tracking only within synchronous code.
  • util.formatWithOptions is used by the default logger. Without it the SDK falls back to util.format.

Checkpointing and replay are unaffected, and the checkpoint data is byte-for-byte the same. What degrades is observability, and the SDK emits one warning per execution environment when it does:

Effect on a runtime without AsyncLocalStorage
Log records emitted after an awaitlose operationId, operationName and attempt
Replay-aware loggingcannot suppress replayed records, so a log statement behind the replay frontier is re-emitted on every replay — this adds CloudWatch cost on long executions
Using a parent or sibling context inside runInChildContextonly detected when it happens synchronously

That last item is a guard against a real determinism bug, so if you deploy on such a runtime, enable the no-nested-durable-operations rule from @aws/durable-execution-sdk-js-eslint-plugin, which catches the same mistake at build time.

Performance is a separate question from support. Durable executions replay, and replay cost grows with the number of completed operations, so a runtime without a JIT can be slower overall for workflows with large operation counts or compute-heavy steps — even where it starts faster. Measure your own workload before switching.

License

This project is licensed under the Apache-2.0 License.

SDK 3.x plugin migration

See the 2.x to 3.0 migration guide for package versions, before/after examples, provider packaging, and validation.

SDK 3.x requires an explicit migration from the legacy v1 provider contract. Providers declaring pluginApiVersion are rejected with PluginLoadError in both plugins and DURABLE_EXECUTION_PLUGINS registration. They are not silently called with a different lifetime: v1 createPlugin() ran once per handler, whereas a 3.x factory's createPlugin(info) runs once per invocation.

Replace legacy provider/instance registrations with a factory object:

import { withDurableExecution, type InvocationInfo } from "@aws/durable-execution-sdk-js";

const factory = {
  createPlugin(info: InvocationInfo) {
    return new MyPlugin(info);
  },
};
const handler = withDurableExecution(myHandler, { plugins: [factory] });

Return a fresh plugin instance for each invocation. Keep intentionally shared resources, such as a tracer provider/exporter, outside the invocation instance; keep invocation-specific spans, operation maps and buffers inside it. Remove legacy pluginApiVersion and pluginType metadata after migrating the factory. Environment-loaded modules still export durableExecutionPluginProvider, whose value is now this factory shape. Upgrade bundled OTel layers together with core 3.x and use their factory builders; an old 1.x layer is rejected rather than silently reinterpreted. Core 2.x's supported v1 providers are unaffected by this major-only policy.

版本与发布记录

@aws/durable-execution-sdk-js 共发布 26 个版本,首次发布于 2025/11/25;过去 12 个月发布了 26 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
3.0.0latest2026/10/2612 天前
2.7.12026/10/2019 天前
2.7.02026/10/1722 天前
2.6.02026/10/021 个月前
2.5.02026/09/301 个月前
2.4.02026/09/091 个月前

关于 @aws/durable-execution-sdk-js 的常见问题

@aws/durable-execution-sdk-js 是什么?

AWS Durable Execution Language SDK for TypeScript

如何安装 @aws/durable-execution-sdk-js?

在项目目录运行 npm install @aws/durable-execution-sdk-js,或使用 pnpm add / yarn add / bun add。

@aws/durable-execution-sdk-js 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@aws/durable-execution-sdk-js 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@aws/durable-execution-sdk-js 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@aws/durable-execution-sdk-js 可以商用吗?

@aws/durable-execution-sdk-js 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@aws/durable-execution-sdk-js 的下载量有多少?

根据 npm 官方下载统计,@aws/durable-execution-sdk-js 在 2026/10/08 – 2026/11/06 这 30 天内被下载 3,907,208 次,日均约 130,240 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@aws/durable-execution-sdk-js 还在维护吗?

最新版本 3.0.0 发布于 2026/10/26(12 天前);至今共发布 26 个版本,其中过去 12 个月发布了 26 个;首次发布于 2025/11/25。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。