diff --git a/apps/dashboard/src/components/workflow-editor/channel-preferences-form.tsx b/apps/dashboard/src/components/workflow-editor/channel-preferences-form.tsx
index 48f07f2abc6..ac4a5f07094 100644
--- a/apps/dashboard/src/components/workflow-editor/channel-preferences-form.tsx
+++ b/apps/dashboard/src/components/workflow-editor/channel-preferences-form.tsx
@@ -242,7 +242,6 @@ export const ChannelPreferencesForm = (props: ConfigureWorkflowFormProps) => {
new_status: checked,
});
}}
- disabled={isReadOnly}
/>
diff --git a/apps/dashboard/src/components/workflow-editor/channel-preferences.tsx b/apps/dashboard/src/components/workflow-editor/channel-preferences.tsx
index a947d36e852..c28c05269b5 100644
--- a/apps/dashboard/src/components/workflow-editor/channel-preferences.tsx
+++ b/apps/dashboard/src/components/workflow-editor/channel-preferences.tsx
@@ -11,8 +11,7 @@ export function ChannelPreferences() {
return null;
}
- const isReadOnly =
- workflow.origin === ResourceOriginEnum.EXTERNAL || currentEnvironment?.type !== EnvironmentTypeEnum.DEV;
+ const isReadOnly = currentEnvironment?.type !== EnvironmentTypeEnum.DEV;
return ;
}
diff --git a/enterprise/workers/step-resolver/.gitignore b/enterprise/workers/step-resolver/.gitignore
new file mode 100644
index 00000000000..fb964fad361
--- /dev/null
+++ b/enterprise/workers/step-resolver/.gitignore
@@ -0,0 +1,168 @@
+# Logs
+
+logs
+_.log
+npm-debug.log_
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+
+report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
+
+# Runtime data
+
+pids
+_.pid
+_.seed
+\*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+
+lib-cov
+
+# Coverage directory used by tools like istanbul
+
+coverage
+\*.lcov
+
+# nyc test coverage
+
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+
+bower_components
+
+# node-waf configuration
+
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+
+build/Release
+
+# Dependency directories
+
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+
+web_modules/
+
+# TypeScript cache
+
+\*.tsbuildinfo
+
+# Optional npm cache directory
+
+.npm
+
+# Optional stylelint cache
+
+.stylelintcache
+
+# Microbundle cache
+
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+
+.node_repl_history
+
+# Output of 'npm pack'
+
+\*.tgz
+
+# Yarn Integrity file
+
+.yarn-integrity
+
+# dotenv environment variable files
+
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+
+.cache
+.parcel-cache
+
+# Next.js build output
+
+.next
+out
+
+# Nuxt.js build / generate output
+
+.nuxt
+dist
+
+# Gatsby files
+
+.cache/
+
+# Comment in the public line in if your project uses Gatsby and not Next.js
+
+# https://nextjs.org/blog/next-9-1#public-directory-support
+
+# public
+
+# vuepress build output
+
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+
+.temp
+.cache
+
+# Docusaurus cache and generated files
+
+.docusaurus
+
+# Serverless directories
+
+.serverless/
+
+# FuseBox cache
+
+.fusebox/
+
+# DynamoDB Local files
+
+.dynamodb/
+
+# TernJS port file
+
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+
+.vscode-test
+
+# yarn v2
+
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.\*
+
+# wrangler project
+
+.dev.vars
+.wrangler/
diff --git a/enterprise/workers/step-resolver/README.md b/enterprise/workers/step-resolver/README.md
new file mode 100644
index 00000000000..66a4d7f54bd
--- /dev/null
+++ b/enterprise/workers/step-resolver/README.md
@@ -0,0 +1,188 @@
+# Step Resolver Dispatch Worker
+
+Cloudflare Workers for Platforms dispatch worker for Step Resolver resolution.
+
+## Repository structure
+
+```text
+enterprise/workers/step-resolver/
+ src/index.ts # HTTP routes + dispatch logic
+ src/auth/hmac.ts # request signature validation
+ src/utils/worker-id.ts # worker id mapping
+ wrangler.jsonc # worker + namespace config
+```
+
+This package is part of the pnpm workspace via `enterprise/workers/*`.
+
+## What the worker does
+
+- Exposes a public dispatch endpoint for resolving step output.
+- Validates HMAC auth header (`X-Novu-Signature` in format `t={timestamp},v1={hmac}`).
+- Maps tenant worker id as `sr-${organizationId}-${stepResolverHash}`.
+- Dispatches into a Workers for Platforms namespace (`DISPATCHER` binding).
+- Preserves downstream response status/body and adds `x-request-id`.
+
+## API contract
+
+### `GET /health`
+
+- Returns `200` with JSON status payload.
+- Any method other than `GET` returns `405`.
+
+### `POST /resolve/:organizationId/:stepResolverHash/:stepId`
+
+Route validation (strict):
+
+- `organizationId`: lowercase hex, exactly 24 chars (`[a-f0-9]{24}`)
+- `stepResolverHash`: format `sr-xxxxx-xxxxx` (e.g., `sr-abc12-def34`)
+- `stepId`: one URL path segment (`[^/]+`)
+- `Content-Type`: must be `application/json`
+- Body size: max `1MB`
+
+Auth headers:
+
+- `X-Novu-Signature`: Signature header in format `t={timestamp},v1={hmac}`
+
+On success, request is forwarded as:
+
+- method: `POST`
+- path: original `/resolve/...` path
+- query param: `step=`
+- stripped headers before forwarding: `x-novu-signature`, `authorization`, `x-internal-auth`
+
+## HMAC signing format
+
+Uses the same signature format as `@novu/framework` Bridge authentication, but with a **different secret** for different trust boundaries:
+
+- **Framework Bridge**: Uses per-customer `NOVU_SECRET_KEY` to authenticate Novu Cloud → Customer's Bridge Endpoint
+- **Step Resolver Worker**: Uses platform-level `STEP_RESOLVER_HMAC_SECRET` to authenticate Novu API → Novu's Cloudflare Workers
+
+This separation ensures customer secrets protect their infrastructure while platform secrets protect Novu's worker infrastructure, without requiring per-customer secret lookups in workers.
+
+Signature format:
+
+```text
+X-Novu-Signature: t={timestamp},v1={hmac}
+```
+
+HMAC computed over:
+
+```text
+${timestamp}.${rawRequestBody}
+```
+
+Note: The HMAC is computed over the raw request body bytes (UTF-8 decoded string), not a re-serialized JSON object. This ensures canonical validation against the exact bytes received.
+
+Validation notes:
+
+- allowed clock skew: `300` seconds (5 minutes)
+- signature comparison is constant-time
+- replay protection is timestamp-window only (no nonce store)
+
+### Node signing example
+
+```ts
+import { createHmac } from 'node:crypto';
+
+const secret = process.env.STEP_RESOLVER_HMAC_SECRET!;
+const payload = {
+ payload: { firstName: 'Ada' },
+ subscriber: { email: 'ada@example.com' },
+ context: {},
+ steps: {},
+};
+
+const timestamp = Date.now();
+const bodyString = JSON.stringify(payload);
+const data = `${timestamp}.${bodyString}`;
+const hmac = createHmac('sha256', secret).update(data).digest('hex');
+const signature = `t=${timestamp},v1=${hmac}`;
+
+// Send as headers:
+// X-Novu-Signature: t=1234567890,v1=abc123...
+// Body: (same string used in HMAC computation)
+```
+
+## Local development
+
+Install dependencies from repo root:
+
+```bash
+pnpm install
+```
+
+Run with workspace filter from repo root:
+
+```bash
+pnpm --filter @novu/step-resolver-worker dev
+```
+
+Or run directly from this folder:
+
+```bash
+pnpm run dev
+```
+
+For local `wrangler dev`, provide the secret (for example via `.dev.vars`):
+
+```bash
+STEP_RESOLVER_HMAC_SECRET=local-dev-secret
+```
+
+## Cloudflare setup and deploy
+
+From `enterprise/workers/step-resolver`:
+
+1. Create dispatch namespaces (one-time):
+
+```bash
+pnpm run namespace:create:staging
+pnpm run namespace:create:production
+```
+
+2. Deploy worker service:
+
+```bash
+pnpm run deploy:staging
+pnpm run deploy:production
+```
+
+3. Set secrets per environment:
+
+```bash
+pnpm run secret:staging
+pnpm run secret:production
+```
+
+4. Deploy updates:
+
+```bash
+pnpm run deploy:staging
+pnpm run deploy:production
+```
+
+If namespace names differ from your Cloudflare account, update `wrangler.jsonc`.
+
+## Curl smoke test
+
+```bash
+DISPATCH_URL="https://step-resolver-dispatch-staging..workers.dev"
+ORGANIZATION_ID="696a21b632ef1f83460d584d"
+STEP_RESOLVER_HASH="abc12-def34"
+STEP_ID="welcome-email"
+SECRET="${STEP_RESOLVER_HMAC_SECRET:?set STEP_RESOLVER_HMAC_SECRET}"
+
+PATHNAME="/resolve/${ORGANIZATION_ID}/sr-${STEP_RESOLVER_HASH}/${STEP_ID}"
+BODY='{"payload":{"firstName":"Ada"},"subscriber":{"email":"ada@example.com"},"context":{},"steps":{}}'
+
+# Create HMAC signature using Framework format
+TIMESTAMP="$(node -e 'console.log(Date.now())')"
+DATA="${TIMESTAMP}.${BODY}"
+HMAC="$(printf '%s' "$DATA" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')"
+SIGNATURE="t=${TIMESTAMP},v1=${HMAC}"
+
+curl -i -X POST "${DISPATCH_URL}${PATHNAME}" \
+ -H "Content-Type: application/json" \
+ -H "X-Novu-Signature: ${SIGNATURE}" \
+ -d "$BODY"
+```
diff --git a/enterprise/workers/step-resolver/package.json b/enterprise/workers/step-resolver/package.json
new file mode 100644
index 00000000000..10bdf6d525a
--- /dev/null
+++ b/enterprise/workers/step-resolver/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "@novu/step-resolver-worker",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "deploy": "wrangler deploy",
+ "dev": "wrangler dev",
+ "start": "wrangler dev",
+ "cf-typegen": "wrangler types",
+ "namespace:create:staging": "wrangler dispatch-namespace create novu-step-resolvers-staging",
+ "namespace:create:production": "wrangler dispatch-namespace create novu-step-resolvers-production",
+ "secret:staging": "wrangler secret put STEP_RESOLVER_HMAC_SECRET --env staging",
+ "secret:production": "wrangler secret put STEP_RESOLVER_HMAC_SECRET --env production",
+ "deploy:staging": "wrangler deploy --env staging",
+ "deploy:production": "wrangler deploy --env production"
+ },
+ "devDependencies": {
+ "typescript": "^5.5.2",
+ "wrangler": "^4.49.0"
+ }
+}
diff --git a/enterprise/workers/step-resolver/src/auth/hmac.ts b/enterprise/workers/step-resolver/src/auth/hmac.ts
new file mode 100644
index 00000000000..02904f4b715
--- /dev/null
+++ b/enterprise/workers/step-resolver/src/auth/hmac.ts
@@ -0,0 +1,91 @@
+/**
+ * HMAC validation utilities for Step Resolver Worker.
+ *
+ * Uses the same signature format as @novu/framework Bridge authentication,
+ * but with a different secret for different trust boundaries:
+ *
+ * - Framework Bridge: Uses per-customer NOVU_SECRET_KEY to authenticate
+ * requests from Novu Cloud to customer's Bridge endpoint
+ *
+ * - Step Resolver Worker: Uses platform-level STEP_RESOLVER_HMAC_SECRET to authenticate
+ * requests from Novu API to Novu's Cloudflare Workers infrastructure
+ *
+ * Signature format: X-Novu-Signature: t={timestamp},v1={hmac}
+ * HMAC computed over: ${timestamp}.${rawPayloadString}
+ */
+
+const DEFAULT_TIMESTAMP_TOLERANCE_MS = 300_000; // 5 minutes
+
+/**
+ * Create HMAC using subtle crypto.
+ * Compatible with Web Crypto API available in Cloudflare Workers.
+ */
+async function createHmacSubtle(secretKey: string, data: string): Promise {
+ const encoder = new TextEncoder();
+ const keyData = encoder.encode(secretKey);
+ const dataBuffer = encoder.encode(data);
+
+ const cryptoKey = await crypto.subtle.importKey(
+ 'raw',
+ keyData,
+ {
+ name: 'HMAC',
+ hash: { name: 'SHA-256' },
+ },
+ false,
+ ['sign']
+ );
+
+ const signature = await crypto.subtle.sign('HMAC', cryptoKey, dataBuffer);
+
+ return Array.from(new Uint8Array(signature))
+ .map((byte) => byte.toString(16).padStart(2, '0'))
+ .join('');
+}
+
+/**
+ * Validate HMAC signature from X-Novu-Signature header.
+ * Uses the same format as Framework Bridge: t={timestamp},v1={hmac}
+ */
+export async function validateHmacSignature(
+ signatureHeader: string,
+ secretKey: string,
+ payloadString: string,
+ toleranceMs: number = DEFAULT_TIMESTAMP_TOLERANCE_MS
+): Promise<{ valid: boolean; error?: string }> {
+ const parts = signatureHeader.split(',');
+ const timestampPart = parts.find((p) => p.startsWith('t='));
+ const signaturePart = parts.find((p) => p.startsWith('v1='));
+
+ if (!timestampPart || !signaturePart) {
+ return { valid: false, error: 'Invalid signature format' };
+ }
+
+ const timestamp = Number(timestampPart.split('=')[1]);
+ const providedSignature = signaturePart.split('=')[1];
+
+ if (!Number.isFinite(timestamp)) {
+ return { valid: false, error: 'Invalid timestamp' };
+ }
+
+ if (Math.abs(Date.now() - timestamp) > toleranceMs) {
+ return { valid: false, error: 'Signature expired' };
+ }
+
+ const expectedSignature = await createHmacSubtle(secretKey, `${timestamp}.${payloadString}`);
+
+ const encoder = new TextEncoder();
+ const expectedBuffer = encoder.encode(expectedSignature);
+ const providedBuffer = encoder.encode(providedSignature);
+
+ const lengthsMatch = expectedBuffer.byteLength === providedBuffer.byteLength;
+ const isEqual = lengthsMatch
+ ? crypto.subtle.timingSafeEqual(expectedBuffer, providedBuffer)
+ : !crypto.subtle.timingSafeEqual(providedBuffer, providedBuffer);
+
+ if (!isEqual) {
+ return { valid: false, error: 'Signature mismatch' };
+ }
+
+ return { valid: true };
+}
diff --git a/enterprise/workers/step-resolver/src/index.ts b/enterprise/workers/step-resolver/src/index.ts
new file mode 100644
index 00000000000..5e30ef80d56
--- /dev/null
+++ b/enterprise/workers/step-resolver/src/index.ts
@@ -0,0 +1,248 @@
+import { validateHmacSignature } from './auth/hmac';
+import type { Env } from './types';
+import { generateStepResolverWorkerId } from './utils/worker-id';
+
+const AUTH_HEADERS_TO_REMOVE = ['x-novu-signature', 'authorization', 'x-internal-auth'];
+const RESOLVE_ROUTE_REGEX = /^\/resolve\/([a-f0-9]{24})\/sr-([a-z0-9]{5}-[a-z0-9]{5})\/([^/]+)$/;
+const REQUEST_ID_HEADER = 'x-request-id';
+const JSON_CONTENT_TYPE = 'application/json';
+const MAX_REQUEST_BODY_BYTES = 1024 * 1024; // 1MB
+
+function jsonResponse(body: unknown, status: number, requestId: string, headers?: Record): Response {
+ return new Response(JSON.stringify(body), {
+ status,
+ headers: {
+ 'Content-Type': 'application/json',
+ [REQUEST_ID_HEADER]: requestId,
+ ...headers,
+ },
+ });
+}
+
+function methodNotAllowed(allow: string, requestId: string): Response {
+ return jsonResponse(
+ {
+ error: 'Method not allowed',
+ },
+ 405,
+ requestId,
+ { Allow: allow }
+ );
+}
+
+function decodePathParam(value: string): string {
+ try {
+ return decodeURIComponent(value);
+ } catch {
+ throw new Error('Invalid path parameter encoding');
+ }
+}
+
+function stripAuthHeaders(headers: Headers): Headers {
+ const sanitizedHeaders = new Headers(headers);
+ for (const headerName of AUTH_HEADERS_TO_REMOVE) {
+ sanitizedHeaders.delete(headerName);
+ }
+ return sanitizedHeaders;
+}
+
+function getRequestId(request: Request): string {
+ return request.headers.get(REQUEST_ID_HEADER) || request.headers.get('cf-ray') || crypto.randomUUID();
+}
+
+function isJsonContentType(contentType: string | null): boolean {
+ if (!contentType) {
+ return false;
+ }
+
+ return contentType.split(';', 1)[0].trim().toLowerCase() === JSON_CONTENT_TYPE;
+}
+
+function parseContentLength(contentLengthHeader: string | null): number | undefined {
+ if (!contentLengthHeader) {
+ return undefined;
+ }
+
+ const contentLength = Number(contentLengthHeader);
+ return Number.isFinite(contentLength) ? contentLength : Number.NaN;
+}
+
+function logInfo(message: string, context: Record): void {
+ console.info(JSON.stringify({ level: 'info', message, ...context }));
+}
+
+function logWarn(message: string, context: Record): void {
+ console.warn(JSON.stringify({ level: 'warn', message, ...context }));
+}
+
+function logError(message: string, context: Record): void {
+ console.error(JSON.stringify({ level: 'error', message, ...context }));
+}
+
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ const url = new URL(request.url);
+ const requestId = getRequestId(request);
+ const startedAt = Date.now();
+
+ if (url.pathname === '/health') {
+ if (request.method !== 'GET') {
+ return methodNotAllowed('GET', requestId);
+ }
+
+ return jsonResponse({ status: 'healthy', timestamp: new Date().toISOString() }, 200, requestId);
+ }
+
+ const resolveMatch = url.pathname.match(RESOLVE_ROUTE_REGEX);
+ if (!resolveMatch) {
+ logWarn('Route not found', { requestId, path: url.pathname, method: request.method });
+ return jsonResponse({ error: 'Not found' }, 404, requestId);
+ }
+
+ const organizationId = resolveMatch[1];
+ const stepResolverHash = resolveMatch[2];
+
+ if (request.method !== 'POST') {
+ return methodNotAllowed('POST', requestId);
+ }
+
+ if (!isJsonContentType(request.headers.get('content-type'))) {
+ return jsonResponse(
+ {
+ error: 'Unsupported media type',
+ message: `Expected ${JSON_CONTENT_TYPE} content type`,
+ },
+ 415,
+ requestId
+ );
+ }
+
+ const declaredContentLength = parseContentLength(request.headers.get('content-length'));
+ if (Number.isNaN(declaredContentLength)) {
+ return jsonResponse({ error: 'Invalid Content-Length header' }, 400, requestId);
+ }
+
+ if (declaredContentLength !== undefined && declaredContentLength > MAX_REQUEST_BODY_BYTES) {
+ return jsonResponse(
+ { error: 'Payload too large', message: `Maximum allowed body size is ${MAX_REQUEST_BODY_BYTES} bytes` },
+ 413,
+ requestId
+ );
+ }
+
+ if (!env.STEP_RESOLVER_HMAC_SECRET) {
+ logError('Dispatch worker configuration missing HMAC secret', {
+ requestId,
+ organizationId,
+ stepResolverHash,
+ });
+ return jsonResponse({ error: 'Server configuration error' }, 500, requestId);
+ }
+
+ const bodyBytes = new Uint8Array(await request.arrayBuffer());
+ if (bodyBytes.byteLength > MAX_REQUEST_BODY_BYTES) {
+ return jsonResponse(
+ { error: 'Payload too large', message: `Maximum allowed body size is ${MAX_REQUEST_BODY_BYTES} bytes` },
+ 413,
+ requestId
+ );
+ }
+
+ const signatureHeader = request.headers.get('X-Novu-Signature');
+ if (!signatureHeader) {
+ logWarn('Missing HMAC signature header', {
+ requestId,
+ organizationId,
+ stepResolverHash,
+ });
+ return jsonResponse({ error: 'Unauthorized', message: 'Missing signature' }, 401, requestId);
+ }
+
+ const bodyString = new TextDecoder().decode(bodyBytes);
+
+ const hmacValidation = await validateHmacSignature(signatureHeader, env.STEP_RESOLVER_HMAC_SECRET, bodyString);
+
+ if (!hmacValidation.valid) {
+ logWarn('Rejected request due to invalid HMAC signature', {
+ requestId,
+ organizationId,
+ stepResolverHash,
+ reason: hmacValidation.error,
+ });
+ return jsonResponse({ error: 'Unauthorized', message: hmacValidation.error }, 401, requestId);
+ }
+
+ let bodyJson: Record;
+ try {
+ bodyJson = JSON.parse(bodyString);
+ } catch (error) {
+ return jsonResponse({ error: 'Invalid JSON', message: 'Request body must be valid JSON' }, 400, requestId);
+ }
+
+ let stepId: string;
+
+ try {
+ stepId = decodePathParam(resolveMatch[3]);
+ } catch (error) {
+ return jsonResponse(
+ {
+ error: 'Invalid request path',
+ message: error instanceof Error ? error.message : 'Invalid path parameters',
+ },
+ 400,
+ requestId
+ );
+ }
+
+ const workerId = generateStepResolverWorkerId(organizationId, stepResolverHash);
+ const workerUrl = new URL(request.url);
+ workerUrl.searchParams.set('step', stepId);
+
+ const forwardedRequest = new Request(workerUrl.toString(), {
+ method: 'POST',
+ headers: stripAuthHeaders(request.headers),
+ body: bodyBytes,
+ });
+
+ try {
+ const workerResponse = await env.DISPATCHER.get(workerId).fetch(forwardedRequest);
+ logInfo('Dispatched step resolver request', {
+ requestId,
+ organizationId,
+ stepResolverHash,
+ stepId,
+ workerId,
+ statusCode: workerResponse.status,
+ durationMs: Date.now() - startedAt,
+ });
+
+ const responseHeaders = new Headers(workerResponse.headers);
+ responseHeaders.set(REQUEST_ID_HEADER, requestId);
+
+ return new Response(workerResponse.body, {
+ status: workerResponse.status,
+ statusText: workerResponse.statusText,
+ headers: responseHeaders,
+ });
+ } catch (error) {
+ logError('Failed dispatching request to step resolver worker', {
+ requestId,
+ organizationId,
+ stepResolverHash,
+ stepId,
+ workerId,
+ error: error instanceof Error ? error.message : 'Unknown dispatch error',
+ });
+
+ return jsonResponse(
+ {
+ error: 'Dispatch error',
+ message: 'Internal dispatch error',
+ workerId,
+ },
+ 502,
+ requestId
+ );
+ }
+ },
+};
diff --git a/enterprise/workers/step-resolver/src/types.ts b/enterprise/workers/step-resolver/src/types.ts
new file mode 100644
index 00000000000..8dea14896bc
--- /dev/null
+++ b/enterprise/workers/step-resolver/src/types.ts
@@ -0,0 +1,12 @@
+export interface DispatchNamespaceWorker {
+ fetch(request: Request): Promise;
+}
+
+export interface DispatchNamespaceBinding {
+ get(name: string): DispatchNamespaceWorker;
+}
+
+export interface Env {
+ DISPATCHER: DispatchNamespaceBinding;
+ STEP_RESOLVER_HMAC_SECRET: string;
+}
diff --git a/enterprise/workers/step-resolver/src/utils/worker-id.ts b/enterprise/workers/step-resolver/src/utils/worker-id.ts
new file mode 100644
index 00000000000..9eb36a9fb4a
--- /dev/null
+++ b/enterprise/workers/step-resolver/src/utils/worker-id.ts
@@ -0,0 +1,3 @@
+export function generateStepResolverWorkerId(organizationId: string, stepResolverHash: string): string {
+ return `sr-${organizationId}-${stepResolverHash}`;
+}
diff --git a/enterprise/workers/step-resolver/tsconfig.json b/enterprise/workers/step-resolver/tsconfig.json
new file mode 100644
index 00000000000..2e6d3939320
--- /dev/null
+++ b/enterprise/workers/step-resolver/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "es2021",
+ "lib": ["es2021"],
+ "module": "es2022",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "allowJs": true,
+ "checkJs": false,
+ "noEmit": true,
+ "isolatedModules": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "types": ["./worker-configuration.d.ts"]
+ },
+ "include": ["worker-configuration.d.ts", "src/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/enterprise/workers/step-resolver/worker-configuration.d.ts b/enterprise/workers/step-resolver/worker-configuration.d.ts
new file mode 100644
index 00000000000..4f56c8c4575
--- /dev/null
+++ b/enterprise/workers/step-resolver/worker-configuration.d.ts
@@ -0,0 +1,10923 @@
+/* eslint-disable */
+// Generated by Wrangler by running `wrangler types` (hash: 7440a217c32b492601b1f32a9cd4a5ea)
+// Runtime types generated with workerd@1.20260205.0 2025-11-18 global_fetch_strictly_public
+declare namespace Cloudflare {
+ interface GlobalProps {
+ mainModule: typeof import("./src/index");
+ }
+ interface StagingEnv {
+ DISPATCHER: DispatchNamespace;
+ }
+ interface ProductionEnv {
+ DISPATCHER: DispatchNamespace;
+ }
+ interface Env {
+ DISPATCHER: DispatchNamespace;
+ }
+}
+interface Env extends Cloudflare.Env {}
+
+// Begin runtime types
+/*! *****************************************************************************
+Copyright (c) Cloudflare. All rights reserved.
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of the
+License at http://www.apache.org/licenses/LICENSE-2.0
+THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
+WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+MERCHANTABLITY OR NON-INFRINGEMENT.
+See the Apache Version 2.0 License for specific language governing permissions
+and limitations under the License.
+***************************************************************************** */
+/* eslint-disable */
+// noinspection JSUnusedGlobalSymbols
+declare var onmessage: never;
+/**
+ * The **`DOMException`** interface represents an abnormal event (called an **exception**) that occurs as a result of calling a method or accessing a property of a web API.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException)
+ */
+declare class DOMException extends Error {
+ constructor(message?: string, name?: string);
+ /**
+ * The **`message`** read-only property of the a message or description associated with the given error name.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message)
+ */
+ readonly message: string;
+ /**
+ * The **`name`** read-only property of the one of the strings associated with an error name.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name)
+ */
+ readonly name: string;
+ /**
+ * The **`code`** read-only property of the DOMException interface returns one of the legacy error code constants, or `0` if none match.
+ * @deprecated
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code)
+ */
+ readonly code: number;
+ static readonly INDEX_SIZE_ERR: number;
+ static readonly DOMSTRING_SIZE_ERR: number;
+ static readonly HIERARCHY_REQUEST_ERR: number;
+ static readonly WRONG_DOCUMENT_ERR: number;
+ static readonly INVALID_CHARACTER_ERR: number;
+ static readonly NO_DATA_ALLOWED_ERR: number;
+ static readonly NO_MODIFICATION_ALLOWED_ERR: number;
+ static readonly NOT_FOUND_ERR: number;
+ static readonly NOT_SUPPORTED_ERR: number;
+ static readonly INUSE_ATTRIBUTE_ERR: number;
+ static readonly INVALID_STATE_ERR: number;
+ static readonly SYNTAX_ERR: number;
+ static readonly INVALID_MODIFICATION_ERR: number;
+ static readonly NAMESPACE_ERR: number;
+ static readonly INVALID_ACCESS_ERR: number;
+ static readonly VALIDATION_ERR: number;
+ static readonly TYPE_MISMATCH_ERR: number;
+ static readonly SECURITY_ERR: number;
+ static readonly NETWORK_ERR: number;
+ static readonly ABORT_ERR: number;
+ static readonly URL_MISMATCH_ERR: number;
+ static readonly QUOTA_EXCEEDED_ERR: number;
+ static readonly TIMEOUT_ERR: number;
+ static readonly INVALID_NODE_TYPE_ERR: number;
+ static readonly DATA_CLONE_ERR: number;
+ get stack(): any;
+ set stack(value: any);
+}
+type WorkerGlobalScopeEventMap = {
+ fetch: FetchEvent;
+ scheduled: ScheduledEvent;
+ queue: QueueEvent;
+ unhandledrejection: PromiseRejectionEvent;
+ rejectionhandled: PromiseRejectionEvent;
+};
+declare abstract class WorkerGlobalScope extends EventTarget {
+ EventTarget: typeof EventTarget;
+}
+/* The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox). *
+ * The **`console`** object provides access to the debugging console (e.g., the Web console in Firefox).
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console)
+ */
+interface Console {
+ "assert"(condition?: boolean, ...data: any[]): void;
+ /**
+ * The **`console.clear()`** static method clears the console if possible.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static)
+ */
+ clear(): void;
+ /**
+ * The **`console.count()`** static method logs the number of times that this particular call to `count()` has been called.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static)
+ */
+ count(label?: string): void;
+ /**
+ * The **`console.countReset()`** static method resets counter used with console/count_static.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countReset_static)
+ */
+ countReset(label?: string): void;
+ /**
+ * The **`console.debug()`** static method outputs a message to the console at the 'debug' log level.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static)
+ */
+ debug(...data: any[]): void;
+ /**
+ * The **`console.dir()`** static method displays a list of the properties of the specified JavaScript object.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static)
+ */
+ dir(item?: any, options?: any): void;
+ /**
+ * The **`console.dirxml()`** static method displays an interactive tree of the descendant elements of the specified XML/HTML element.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static)
+ */
+ dirxml(...data: any[]): void;
+ /**
+ * The **`console.error()`** static method outputs a message to the console at the 'error' log level.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static)
+ */
+ error(...data: any[]): void;
+ /**
+ * The **`console.group()`** static method creates a new inline group in the Web console log, causing any subsequent console messages to be indented by an additional level, until console/groupEnd_static is called.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static)
+ */
+ group(...data: any[]): void;
+ /**
+ * The **`console.groupCollapsed()`** static method creates a new inline group in the console.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupCollapsed_static)
+ */
+ groupCollapsed(...data: any[]): void;
+ /**
+ * The **`console.groupEnd()`** static method exits the current inline group in the console.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupEnd_static)
+ */
+ groupEnd(): void;
+ /**
+ * The **`console.info()`** static method outputs a message to the console at the 'info' log level.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static)
+ */
+ info(...data: any[]): void;
+ /**
+ * The **`console.log()`** static method outputs a message to the console.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
+ */
+ log(...data: any[]): void;
+ /**
+ * The **`console.table()`** static method displays tabular data as a table.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static)
+ */
+ table(tabularData?: any, properties?: string[]): void;
+ /**
+ * The **`console.time()`** static method starts a timer you can use to track how long an operation takes.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static)
+ */
+ time(label?: string): void;
+ /**
+ * The **`console.timeEnd()`** static method stops a timer that was previously started by calling console/time_static.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static)
+ */
+ timeEnd(label?: string): void;
+ /**
+ * The **`console.timeLog()`** static method logs the current value of a timer that was previously started by calling console/time_static.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeLog_static)
+ */
+ timeLog(label?: string, ...data: any[]): void;
+ timeStamp(label?: string): void;
+ /**
+ * The **`console.trace()`** static method outputs a stack trace to the console.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static)
+ */
+ trace(...data: any[]): void;
+ /**
+ * The **`console.warn()`** static method outputs a warning message to the console at the 'warning' log level.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static)
+ */
+ warn(...data: any[]): void;
+}
+declare const console: Console;
+type BufferSource = ArrayBufferView | ArrayBuffer;
+type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array;
+declare namespace WebAssembly {
+ class CompileError extends Error {
+ constructor(message?: string);
+ }
+ class RuntimeError extends Error {
+ constructor(message?: string);
+ }
+ type ValueType = "anyfunc" | "externref" | "f32" | "f64" | "i32" | "i64" | "v128";
+ interface GlobalDescriptor {
+ value: ValueType;
+ mutable?: boolean;
+ }
+ class Global {
+ constructor(descriptor: GlobalDescriptor, value?: any);
+ value: any;
+ valueOf(): any;
+ }
+ type ImportValue = ExportValue | number;
+ type ModuleImports = Record;
+ type Imports = Record;
+ type ExportValue = Function | Global | Memory | Table;
+ type Exports = Record;
+ class Instance {
+ constructor(module: Module, imports?: Imports);
+ readonly exports: Exports;
+ }
+ interface MemoryDescriptor {
+ initial: number;
+ maximum?: number;
+ shared?: boolean;
+ }
+ class Memory {
+ constructor(descriptor: MemoryDescriptor);
+ readonly buffer: ArrayBuffer;
+ grow(delta: number): number;
+ }
+ type ImportExportKind = "function" | "global" | "memory" | "table";
+ interface ModuleExportDescriptor {
+ kind: ImportExportKind;
+ name: string;
+ }
+ interface ModuleImportDescriptor {
+ kind: ImportExportKind;
+ module: string;
+ name: string;
+ }
+ abstract class Module {
+ static customSections(module: Module, sectionName: string): ArrayBuffer[];
+ static exports(module: Module): ModuleExportDescriptor[];
+ static imports(module: Module): ModuleImportDescriptor[];
+ }
+ type TableKind = "anyfunc" | "externref";
+ interface TableDescriptor {
+ element: TableKind;
+ initial: number;
+ maximum?: number;
+ }
+ class Table {
+ constructor(descriptor: TableDescriptor, value?: any);
+ readonly length: number;
+ get(index: number): any;
+ grow(delta: number, value?: any): number;
+ set(index: number, value?: any): void;
+ }
+ function instantiate(module: Module, imports?: Imports): Promise;
+ function validate(bytes: BufferSource): boolean;
+}
+/**
+ * The **`ServiceWorkerGlobalScope`** interface of the Service Worker API represents the global execution context of a service worker.
+ * Available only in secure contexts.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope)
+ */
+interface ServiceWorkerGlobalScope extends WorkerGlobalScope {
+ DOMException: typeof DOMException;
+ WorkerGlobalScope: typeof WorkerGlobalScope;
+ btoa(data: string): string;
+ atob(data: string): string;
+ setTimeout(callback: (...args: any[]) => void, msDelay?: number): number;
+ setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number;
+ clearTimeout(timeoutId: number | null): void;
+ setInterval(callback: (...args: any[]) => void, msDelay?: number): number;
+ setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number;
+ clearInterval(timeoutId: number | null): void;
+ queueMicrotask(task: Function): void;
+ structuredClone(value: T, options?: StructuredSerializeOptions): T;
+ reportError(error: any): void;
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise;
+ self: ServiceWorkerGlobalScope;
+ crypto: Crypto;
+ caches: CacheStorage;
+ scheduler: Scheduler;
+ performance: Performance;
+ Cloudflare: Cloudflare;
+ readonly origin: string;
+ Event: typeof Event;
+ ExtendableEvent: typeof ExtendableEvent;
+ CustomEvent: typeof CustomEvent;
+ PromiseRejectionEvent: typeof PromiseRejectionEvent;
+ FetchEvent: typeof FetchEvent;
+ TailEvent: typeof TailEvent;
+ TraceEvent: typeof TailEvent;
+ ScheduledEvent: typeof ScheduledEvent;
+ MessageEvent: typeof MessageEvent;
+ CloseEvent: typeof CloseEvent;
+ ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader;
+ ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader;
+ ReadableStream: typeof ReadableStream;
+ WritableStream: typeof WritableStream;
+ WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter;
+ TransformStream: typeof TransformStream;
+ ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy;
+ CountQueuingStrategy: typeof CountQueuingStrategy;
+ ErrorEvent: typeof ErrorEvent;
+ MessageChannel: typeof MessageChannel;
+ MessagePort: typeof MessagePort;
+ EventSource: typeof EventSource;
+ ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest;
+ ReadableStreamDefaultController: typeof ReadableStreamDefaultController;
+ ReadableByteStreamController: typeof ReadableByteStreamController;
+ WritableStreamDefaultController: typeof WritableStreamDefaultController;
+ TransformStreamDefaultController: typeof TransformStreamDefaultController;
+ CompressionStream: typeof CompressionStream;
+ DecompressionStream: typeof DecompressionStream;
+ TextEncoderStream: typeof TextEncoderStream;
+ TextDecoderStream: typeof TextDecoderStream;
+ Headers: typeof Headers;
+ Body: typeof Body;
+ Request: typeof Request;
+ Response: typeof Response;
+ WebSocket: typeof WebSocket;
+ WebSocketPair: typeof WebSocketPair;
+ WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair;
+ AbortController: typeof AbortController;
+ AbortSignal: typeof AbortSignal;
+ TextDecoder: typeof TextDecoder;
+ TextEncoder: typeof TextEncoder;
+ navigator: Navigator;
+ Navigator: typeof Navigator;
+ URL: typeof URL;
+ URLSearchParams: typeof URLSearchParams;
+ URLPattern: typeof URLPattern;
+ Blob: typeof Blob;
+ File: typeof File;
+ FormData: typeof FormData;
+ Crypto: typeof Crypto;
+ SubtleCrypto: typeof SubtleCrypto;
+ CryptoKey: typeof CryptoKey;
+ CacheStorage: typeof CacheStorage;
+ Cache: typeof Cache;
+ FixedLengthStream: typeof FixedLengthStream;
+ IdentityTransformStream: typeof IdentityTransformStream;
+ HTMLRewriter: typeof HTMLRewriter;
+}
+declare function addEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetAddEventListenerOptions | boolean): void;
+declare function removeEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetEventListenerOptions | boolean): void;
+/**
+ * The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order.
+ *
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)
+ */
+declare function dispatchEvent(event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap]): boolean;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */
+declare function btoa(data: string): string;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */
+declare function atob(data: string): string;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */
+declare function setTimeout(callback: (...args: any[]) => void, msDelay?: number): number;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */
+declare function setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout) */
+declare function clearTimeout(timeoutId: number | null): void;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */
+declare function setInterval(callback: (...args: any[]) => void, msDelay?: number): number;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */
+declare function setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval) */
+declare function clearInterval(timeoutId: number | null): void;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/queueMicrotask) */
+declare function queueMicrotask(task: Function): void;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/structuredClone) */
+declare function structuredClone(value: T, options?: StructuredSerializeOptions): T;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/reportError) */
+declare function reportError(error: any): void;
+/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) */
+declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise;
+declare const self: ServiceWorkerGlobalScope;
+/**
+* The Web Crypto API provides a set of low-level functions for common cryptographic tasks.
+* The Workers runtime implements the full surface of this API, but with some differences in
+* the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms)
+* compared to those implemented in most browsers.
+*
+* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/)
+*/
+declare const crypto: Crypto;
+/**
+* The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache.
+*
+* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/)
+*/
+declare const caches: CacheStorage;
+declare const scheduler: Scheduler;
+/**
+* The Workers runtime supports a subset of the Performance API, used to measure timing and performance,
+* as well as timing of subrequests and other operations.
+*
+* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/)
+*/
+declare const performance: Performance;
+declare const Cloudflare: Cloudflare;
+declare const origin: string;
+declare const navigator: Navigator;
+interface TestController {
+}
+interface ExecutionContext {
+ waitUntil(promise: Promise): void;
+ passThroughOnException(): void;
+ readonly exports: Cloudflare.Exports;
+ readonly props: Props;
+}
+type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise;
+type ExportedHandlerTailHandler = (events: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise;
+type ExportedHandlerTraceHandler = (traces: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise;
+type ExportedHandlerTailStreamHandler = (event: TailStream.TailEvent, env: Env, ctx: ExecutionContext) => TailStream.TailEventHandlerType | Promise;
+type ExportedHandlerScheduledHandler = (controller: ScheduledController, env: Env, ctx: ExecutionContext) => void | Promise;
+type ExportedHandlerQueueHandler = (batch: MessageBatch, env: Env, ctx: ExecutionContext) => void | Promise;
+type ExportedHandlerTestHandler = (controller: TestController, env: Env, ctx: ExecutionContext) => void | Promise;
+interface ExportedHandler {
+ fetch?: ExportedHandlerFetchHandler;
+ tail?: ExportedHandlerTailHandler;
+ trace?: ExportedHandlerTraceHandler;
+ tailStream?: ExportedHandlerTailStreamHandler;
+ scheduled?: ExportedHandlerScheduledHandler;
+ test?: ExportedHandlerTestHandler;
+ email?: EmailExportedHandler;
+ queue?: ExportedHandlerQueueHandler;
+}
+interface StructuredSerializeOptions {
+ transfer?: any[];
+}
+declare abstract class Navigator {
+ sendBeacon(url: string, body?: BodyInit): boolean;
+ readonly userAgent: string;
+ readonly hardwareConcurrency: number;
+ readonly language: string;
+ readonly languages: string[];
+}
+interface AlarmInvocationInfo {
+ readonly isRetry: boolean;
+ readonly retryCount: number;
+}
+interface Cloudflare {
+ readonly compatibilityFlags: Record;
+}
+interface DurableObject {
+ fetch(request: Request): Response | Promise;
+ alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise;
+ webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise;
+ webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise;
+ webSocketError?(ws: WebSocket, error: unknown): void | Promise;
+}
+type DurableObjectStub = Fetcher & {
+ readonly id: DurableObjectId;
+ readonly name?: string;
+};
+interface DurableObjectId {
+ toString(): string;
+ equals(other: DurableObjectId): boolean;
+ readonly name?: string;
+}
+declare abstract class DurableObjectNamespace {
+ newUniqueId(options?: DurableObjectNamespaceNewUniqueIdOptions): DurableObjectId;
+ idFromName(name: string): DurableObjectId;
+ idFromString(id: string): DurableObjectId;
+ get(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub;
+ getByName(name: string, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub;
+ jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace;
+}
+type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high";
+interface DurableObjectNamespaceNewUniqueIdOptions {
+ jurisdiction?: DurableObjectJurisdiction;
+}
+type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me";
+type DurableObjectRoutingMode = "primary-only";
+interface DurableObjectNamespaceGetDurableObjectOptions {
+ locationHint?: DurableObjectLocationHint;
+ routingMode?: DurableObjectRoutingMode;
+}
+interface DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> {
+}
+interface DurableObjectState {
+ waitUntil(promise: Promise): void;
+ readonly exports: Cloudflare.Exports;
+ readonly props: Props;
+ readonly id: DurableObjectId;
+ readonly storage: DurableObjectStorage;
+ container?: Container;
+ blockConcurrencyWhile(callback: () => Promise): Promise;
+ acceptWebSocket(ws: WebSocket, tags?: string[]): void;
+ getWebSockets(tag?: string): WebSocket[];
+ setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void;
+ getWebSocketAutoResponse(): WebSocketRequestResponsePair | null;
+ getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null;
+ setHibernatableWebSocketEventTimeout(timeoutMs?: number): void;
+ getHibernatableWebSocketEventTimeout(): number | null;
+ getTags(ws: WebSocket): string[];
+ abort(reason?: string): void;
+}
+interface DurableObjectTransaction {
+ get(key: string, options?: DurableObjectGetOptions): Promise;
+ get(keys: string[], options?: DurableObjectGetOptions): Promise