dotdo 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +446 -315
- package/cli/README.md +238 -0
- package/cli/agent.ts +72 -0
- package/cli/bin.js +44 -0
- package/cli/bin.ts +38 -0
- package/cli/build.ts +157 -0
- package/cli/commands/auth/login.ts +14 -0
- package/cli/commands/auth/logout.ts +6 -0
- package/cli/commands/auth/whoami.ts +16 -0
- package/cli/commands/deploy-multi.ts +245 -0
- package/cli/commands/dev/deploy.ts +100 -0
- package/cli/commands/dev/dev.ts +95 -0
- package/cli/commands/dev/logs.ts +91 -0
- package/cli/commands/dev-local.ts +88 -0
- package/cli/commands/do-ops.ts +314 -0
- package/cli/commands/index.ts +100 -0
- package/cli/commands/init.ts +247 -0
- package/cli/commands/introspect/emitter.ts +315 -0
- package/cli/commands/introspect/index.ts +193 -0
- package/cli/commands/link.ts +598 -0
- package/cli/commands/snippets.ts +415 -0
- package/cli/commands/tunnel.ts +239 -0
- package/cli/device-auth.ts +289 -0
- package/cli/fallback.ts +12 -0
- package/cli/index.ts +121 -0
- package/cli/main.ts +246 -0
- package/cli/mcp-stdio.ts +790 -0
- package/cli/package.json +62 -0
- package/cli/runtime/do-registry.ts +193 -0
- package/cli/runtime/embedded-db.ts +344 -0
- package/cli/runtime/index.ts +9 -0
- package/cli/runtime/miniflare-adapter.ts +162 -0
- package/cli/sandbox.ts +82 -0
- package/cli/src/args.ts +174 -0
- package/cli/src/auth.ts +55 -0
- package/cli/src/commands/call.ts +84 -0
- package/cli/src/commands/charge.ts +96 -0
- package/cli/src/commands/config.ts +115 -0
- package/cli/src/commands/email.ts +112 -0
- package/cli/src/commands/llm.ts +115 -0
- package/cli/src/commands/queue.ts +134 -0
- package/cli/src/commands/text.ts +86 -0
- package/cli/src/config.ts +185 -0
- package/cli/src/output.ts +246 -0
- package/cli/src/rpc.ts +192 -0
- package/cli/utils/config.ts +282 -0
- package/cli/utils/detect.ts +73 -0
- package/cli/utils/index.ts +15 -0
- package/cli/utils/logger.ts +232 -0
- package/dist/ai/index.js +19 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/template-literals.js +852 -0
- package/dist/ai/template-literals.js.map +1 -0
- package/dist/api/middleware/auth-federation.js +573 -0
- package/dist/api/middleware/auth-federation.js.map +1 -0
- package/dist/api/middleware/auth.js +545 -0
- package/dist/api/middleware/auth.js.map +1 -0
- package/dist/db/actions.js +212 -0
- package/dist/db/actions.js.map +1 -0
- package/dist/db/auth.js +506 -0
- package/dist/db/auth.js.map +1 -0
- package/dist/db/branches.js +65 -0
- package/dist/db/branches.js.map +1 -0
- package/dist/db/clickhouse.js +1074 -0
- package/dist/db/clickhouse.js.map +1 -0
- package/dist/db/dlq.js +39 -0
- package/dist/db/dlq.js.map +1 -0
- package/dist/db/events.js +28 -0
- package/dist/db/events.js.map +1 -0
- package/dist/db/exec.js +64 -0
- package/dist/db/exec.js.map +1 -0
- package/dist/db/files.js +85 -0
- package/dist/db/files.js.map +1 -0
- package/dist/db/flags.js +24 -0
- package/dist/db/flags.js.map +1 -0
- package/dist/db/git.js +116 -0
- package/dist/db/git.js.map +1 -0
- package/dist/db/iceberg/inverted-index.js +862 -0
- package/dist/db/iceberg/inverted-index.js.map +1 -0
- package/dist/db/iceberg/puffin.js +878 -0
- package/dist/db/iceberg/puffin.js.map +1 -0
- package/dist/db/iceberg/search-manifest.js +422 -0
- package/dist/db/iceberg/search-manifest.js.map +1 -0
- package/dist/db/iceberg/types.js +8 -0
- package/dist/db/iceberg/types.js.map +1 -0
- package/dist/db/index.js +121 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/integrations.js +368 -0
- package/dist/db/integrations.js.map +1 -0
- package/dist/db/json-indexes.js +332 -0
- package/dist/db/json-indexes.js.map +1 -0
- package/dist/db/linked-accounts.js +287 -0
- package/dist/db/linked-accounts.js.map +1 -0
- package/dist/db/nouns.js +183 -0
- package/dist/db/nouns.js.map +1 -0
- package/dist/db/objects.js +170 -0
- package/dist/db/objects.js.map +1 -0
- package/dist/db/primitives/dag-scheduler/index.js +869 -0
- package/dist/db/primitives/dag-scheduler/index.js.map +1 -0
- package/dist/db/primitives/exactly-once-context.js +237 -0
- package/dist/db/primitives/exactly-once-context.js.map +1 -0
- package/dist/db/primitives/index.js +62 -0
- package/dist/db/primitives/index.js.map +1 -0
- package/dist/db/primitives/keyed-router.js +145 -0
- package/dist/db/primitives/keyed-router.js.map +1 -0
- package/dist/db/primitives/observability.js +162 -0
- package/dist/db/primitives/observability.js.map +1 -0
- package/dist/db/primitives/schema-evolution.js +643 -0
- package/dist/db/primitives/schema-evolution.js.map +1 -0
- package/dist/db/primitives/stateful-operator/index.js +770 -0
- package/dist/db/primitives/stateful-operator/index.js.map +1 -0
- package/dist/db/primitives/temporal-store.js +306 -0
- package/dist/db/primitives/temporal-store.js.map +1 -0
- package/dist/db/primitives/typed-column-store.js +1229 -0
- package/dist/db/primitives/typed-column-store.js.map +1 -0
- package/dist/db/primitives/utils/duration.js +162 -0
- package/dist/db/primitives/utils/duration.js.map +1 -0
- package/dist/db/primitives/utils/murmur3.js +116 -0
- package/dist/db/primitives/utils/murmur3.js.map +1 -0
- package/dist/db/primitives/watermark-service.js +136 -0
- package/dist/db/primitives/watermark-service.js.map +1 -0
- package/dist/db/primitives/window-manager.js +764 -0
- package/dist/db/primitives/window-manager.js.map +1 -0
- package/dist/db/relationships.js +66 -0
- package/dist/db/relationships.js.map +1 -0
- package/dist/db/schema-minimal.js +61 -0
- package/dist/db/schema-minimal.js.map +1 -0
- package/dist/db/search.js +28 -0
- package/dist/db/search.js.map +1 -0
- package/dist/db/stores.js +1665 -0
- package/dist/db/stores.js.map +1 -0
- package/dist/db/things.js +297 -0
- package/dist/db/things.js.map +1 -0
- package/dist/db/vault.js +171 -0
- package/dist/db/vault.js.map +1 -0
- package/dist/db/verbs.js +102 -0
- package/dist/db/verbs.js.map +1 -0
- package/dist/do/base.js +48 -0
- package/dist/do/base.js.map +1 -0
- package/dist/do/tiny.js +31 -0
- package/dist/do/tiny.js.map +1 -0
- package/dist/lib/DOAuth.js +261 -0
- package/dist/lib/DOAuth.js.map +1 -0
- package/dist/lib/DODispatcher.js +72 -0
- package/dist/lib/DODispatcher.js.map +1 -0
- package/dist/lib/Modifier.js +189 -0
- package/dist/lib/Modifier.js.map +1 -0
- package/dist/lib/StateStorage.js +403 -0
- package/dist/lib/StateStorage.js.map +1 -0
- package/dist/lib/TypeRegistry.js +122 -0
- package/dist/lib/TypeRegistry.js.map +1 -0
- package/dist/lib/ai/gateway.js +247 -0
- package/dist/lib/ai/gateway.js.map +1 -0
- package/dist/lib/ai/tool-loop-agent.js +591 -0
- package/dist/lib/ai/tool-loop-agent.js.map +1 -0
- package/dist/lib/auto-wiring.js +439 -0
- package/dist/lib/auto-wiring.js.map +1 -0
- package/dist/lib/browse/browserbase.js +163 -0
- package/dist/lib/browse/browserbase.js.map +1 -0
- package/dist/lib/browse/cloudflare.js +144 -0
- package/dist/lib/browse/cloudflare.js.map +1 -0
- package/dist/lib/browse/index.js +62 -0
- package/dist/lib/browse/index.js.map +1 -0
- package/dist/lib/browse/types.js +13 -0
- package/dist/lib/browse/types.js.map +1 -0
- package/dist/lib/cache/index.js +37 -0
- package/dist/lib/cache/index.js.map +1 -0
- package/dist/lib/cache/visibility.js +638 -0
- package/dist/lib/cache/visibility.js.map +1 -0
- package/dist/lib/capabilities.js +268 -0
- package/dist/lib/capabilities.js.map +1 -0
- package/dist/lib/channels/base.js +106 -0
- package/dist/lib/channels/base.js.map +1 -0
- package/dist/lib/channels/discord.js +94 -0
- package/dist/lib/channels/discord.js.map +1 -0
- package/dist/lib/channels/email.js +204 -0
- package/dist/lib/channels/email.js.map +1 -0
- package/dist/lib/channels/index.js +90 -0
- package/dist/lib/channels/index.js.map +1 -0
- package/dist/lib/channels/mdxui-chat.js +95 -0
- package/dist/lib/channels/mdxui-chat.js.map +1 -0
- package/dist/lib/channels/slack-blockkit.js +121 -0
- package/dist/lib/channels/slack-blockkit.js.map +1 -0
- package/dist/lib/channels/types.js +7 -0
- package/dist/lib/channels/types.js.map +1 -0
- package/dist/lib/cloudflare/ai.js +654 -0
- package/dist/lib/cloudflare/ai.js.map +1 -0
- package/dist/lib/cloudflare/index.js +88 -0
- package/dist/lib/cloudflare/index.js.map +1 -0
- package/dist/lib/cloudflare/kv.js +342 -0
- package/dist/lib/cloudflare/kv.js.map +1 -0
- package/dist/lib/cloudflare/queues.js +434 -0
- package/dist/lib/cloudflare/queues.js.map +1 -0
- package/dist/lib/cloudflare/r2.js +604 -0
- package/dist/lib/cloudflare/r2.js.map +1 -0
- package/dist/lib/cloudflare/vectorize.js +494 -0
- package/dist/lib/cloudflare/vectorize.js.map +1 -0
- package/dist/lib/cloudflare/workflows.js +569 -0
- package/dist/lib/cloudflare/workflows.js.map +1 -0
- package/dist/lib/colo/caching.js +196 -0
- package/dist/lib/colo/caching.js.map +1 -0
- package/dist/lib/colo/detection.js +194 -0
- package/dist/lib/colo/detection.js.map +1 -0
- package/dist/lib/colo/external-data.js +219 -0
- package/dist/lib/colo/external-data.js.map +1 -0
- package/dist/lib/colo/globe-data.js +179 -0
- package/dist/lib/colo/globe-data.js.map +1 -0
- package/dist/lib/colo/index.js +16 -0
- package/dist/lib/colo/index.js.map +1 -0
- package/dist/lib/decorators.js +37 -0
- package/dist/lib/decorators.js.map +1 -0
- package/dist/lib/discovery.js +81 -0
- package/dist/lib/discovery.js.map +1 -0
- package/dist/lib/executors/AgenticFunctionExecutor.js +619 -0
- package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/BaseFunctionExecutor.js +328 -0
- package/dist/lib/executors/BaseFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/CascadeExecutor.js +418 -0
- package/dist/lib/executors/CascadeExecutor.js.map +1 -0
- package/dist/lib/executors/CodeFunctionExecutor.js +904 -0
- package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/GenerativeFunctionExecutor.js +904 -0
- package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/HumanFunctionExecutor.js +884 -0
- package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -0
- package/dist/lib/executors/ParallelStepExecutor.js +308 -0
- package/dist/lib/executors/ParallelStepExecutor.js.map +1 -0
- package/dist/lib/executors/types.js +12 -0
- package/dist/lib/executors/types.js.map +1 -0
- package/dist/lib/experiments.js +89 -0
- package/dist/lib/experiments.js.map +1 -0
- package/dist/lib/flags/store.js +262 -0
- package/dist/lib/flags/store.js.map +1 -0
- package/dist/lib/functions/FunctionComposition.js +467 -0
- package/dist/lib/functions/FunctionComposition.js.map +1 -0
- package/dist/lib/functions/FunctionMiddleware.js +457 -0
- package/dist/lib/functions/FunctionMiddleware.js.map +1 -0
- package/dist/lib/functions/FunctionRegistry.js +426 -0
- package/dist/lib/functions/FunctionRegistry.js.map +1 -0
- package/dist/lib/functions/createFunction.js +1048 -0
- package/dist/lib/functions/createFunction.js.map +1 -0
- package/dist/lib/humans/index.js +68 -0
- package/dist/lib/humans/index.js.map +1 -0
- package/dist/lib/humans/templates.js +117 -0
- package/dist/lib/humans/templates.js.map +1 -0
- package/dist/lib/identity.js +98 -0
- package/dist/lib/identity.js.map +1 -0
- package/dist/lib/index.js +9 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logging/error-logger.js +163 -0
- package/dist/lib/logging/error-logger.js.map +1 -0
- package/dist/lib/logging/index.js +160 -0
- package/dist/lib/logging/index.js.map +1 -0
- package/dist/lib/mixins/bash.js +753 -0
- package/dist/lib/mixins/bash.js.map +1 -0
- package/dist/lib/mixins/fs.js +648 -0
- package/dist/lib/mixins/fs.js.map +1 -0
- package/dist/lib/mixins/git.js +1006 -0
- package/dist/lib/mixins/git.js.map +1 -0
- package/dist/lib/mixins/npm.js +662 -0
- package/dist/lib/mixins/npm.js.map +1 -0
- package/dist/lib/noun-id.js +278 -0
- package/dist/lib/noun-id.js.map +1 -0
- package/dist/lib/rate-limit/sliding-window.js +148 -0
- package/dist/lib/rate-limit/sliding-window.js.map +1 -0
- package/dist/lib/rate-limit.js +110 -0
- package/dist/lib/rate-limit.js.map +1 -0
- package/dist/lib/rpc/bindings.js +548 -0
- package/dist/lib/rpc/bindings.js.map +1 -0
- package/dist/lib/rpc/index.js +64 -0
- package/dist/lib/rpc/index.js.map +1 -0
- package/dist/lib/safe-stringify.js +223 -0
- package/dist/lib/safe-stringify.js.map +1 -0
- package/dist/lib/sandbox/miniflare-sandbox.js +1007 -0
- package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -0
- package/dist/lib/sqids.js +110 -0
- package/dist/lib/sqids.js.map +1 -0
- package/dist/lib/sql/adapters/index.js +10 -0
- package/dist/lib/sql/adapters/index.js.map +1 -0
- package/dist/lib/sql/adapters/node-sql-parser.js +552 -0
- package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -0
- package/dist/lib/sql/adapters/pgsql-parser.js +1190 -0
- package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -0
- package/dist/lib/sql/index.js +277 -0
- package/dist/lib/sql/index.js.map +1 -0
- package/dist/lib/sql/types.js +56 -0
- package/dist/lib/sql/types.js.map +1 -0
- package/dist/lib/type-classifier.js +126 -0
- package/dist/lib/type-classifier.js.map +1 -0
- package/dist/lib/utils/html.js +47 -0
- package/dist/lib/utils/html.js.map +1 -0
- package/dist/lib/validation.js +48 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/lib/vault/store.js +411 -0
- package/dist/lib/vault/store.js.map +1 -0
- package/dist/metrics/hunch.js +739 -0
- package/dist/metrics/hunch.js.map +1 -0
- package/dist/objects/API.js +302 -0
- package/dist/objects/API.js.map +1 -0
- package/dist/objects/Agent.js +179 -0
- package/dist/objects/Agent.js.map +1 -0
- package/dist/objects/AgenticFunctionExecutor.js +8 -0
- package/dist/objects/AgenticFunctionExecutor.js.map +1 -0
- package/dist/objects/App.js +83 -0
- package/dist/objects/App.js.map +1 -0
- package/dist/objects/Browser.js +884 -0
- package/dist/objects/Browser.js.map +1 -0
- package/dist/objects/Business.js +107 -0
- package/dist/objects/Business.js.map +1 -0
- package/dist/objects/CLI.js +221 -0
- package/dist/objects/CLI.js.map +1 -0
- package/dist/objects/CodeFunctionExecutor.js +8 -0
- package/dist/objects/CodeFunctionExecutor.js.map +1 -0
- package/dist/objects/Collection.js +161 -0
- package/dist/objects/Collection.js.map +1 -0
- package/dist/objects/DO.js +41 -0
- package/dist/objects/DO.js.map +1 -0
- package/dist/objects/DOBase.js +2309 -0
- package/dist/objects/DOBase.js.map +1 -0
- package/dist/objects/DOCache.js +153 -0
- package/dist/objects/DOCache.js.map +1 -0
- package/dist/objects/DOFull.js +1676 -0
- package/dist/objects/DOFull.js.map +1 -0
- package/dist/objects/DOTiny.js +207 -0
- package/dist/objects/DOTiny.js.map +1 -0
- package/dist/objects/Directory.js +199 -0
- package/dist/objects/Directory.js.map +1 -0
- package/dist/objects/Entity.js +413 -0
- package/dist/objects/Entity.js.map +1 -0
- package/dist/objects/Function.js +116 -0
- package/dist/objects/Function.js.map +1 -0
- package/dist/objects/Human.js +231 -0
- package/dist/objects/Human.js.map +1 -0
- package/dist/objects/HumanFunctionExecutor.js +8 -0
- package/dist/objects/HumanFunctionExecutor.js.map +1 -0
- package/dist/objects/IcebergMetadataDO.js +938 -0
- package/dist/objects/IcebergMetadataDO.js.map +1 -0
- package/dist/objects/IntegrationsDO.js +1174 -0
- package/dist/objects/IntegrationsDO.js.map +1 -0
- package/dist/objects/ObservabilityBroadcaster.js +149 -0
- package/dist/objects/ObservabilityBroadcaster.js.map +1 -0
- package/dist/objects/Package.js +154 -0
- package/dist/objects/Package.js.map +1 -0
- package/dist/objects/Product.js +193 -0
- package/dist/objects/Product.js.map +1 -0
- package/dist/objects/SDK.js +152 -0
- package/dist/objects/SDK.js.map +1 -0
- package/dist/objects/SaaS.js +235 -0
- package/dist/objects/SaaS.js.map +1 -0
- package/dist/objects/SandboxDO.js +759 -0
- package/dist/objects/SandboxDO.js.map +1 -0
- package/dist/objects/Service.js +337 -0
- package/dist/objects/Service.js.map +1 -0
- package/dist/objects/Site.js +80 -0
- package/dist/objects/Site.js.map +1 -0
- package/dist/objects/Startup.js +479 -0
- package/dist/objects/Startup.js.map +1 -0
- package/dist/objects/ThingsDO.js +170 -0
- package/dist/objects/ThingsDO.js.map +1 -0
- package/dist/objects/VectorShardDO.js +650 -0
- package/dist/objects/VectorShardDO.js.map +1 -0
- package/dist/objects/Worker.js +144 -0
- package/dist/objects/Worker.js.map +1 -0
- package/dist/objects/Workflow.js +196 -0
- package/dist/objects/Workflow.js.map +1 -0
- package/dist/objects/WorkflowFactory.js +313 -0
- package/dist/objects/WorkflowFactory.js.map +1 -0
- package/dist/objects/WorkflowRuntime.js +863 -0
- package/dist/objects/WorkflowRuntime.js.map +1 -0
- package/dist/objects/circuit-breaker-bulkhead.js +178 -0
- package/dist/objects/circuit-breaker-bulkhead.js.map +1 -0
- package/dist/objects/createFunction.js +934 -0
- package/dist/objects/createFunction.js.map +1 -0
- package/dist/objects/index.js +80 -0
- package/dist/objects/index.js.map +1 -0
- package/dist/objects/lifecycle/Branch.js +275 -0
- package/dist/objects/lifecycle/Branch.js.map +1 -0
- package/dist/objects/lifecycle/Clone.js +1499 -0
- package/dist/objects/lifecycle/Clone.js.map +1 -0
- package/dist/objects/lifecycle/Compact.js +237 -0
- package/dist/objects/lifecycle/Compact.js.map +1 -0
- package/dist/objects/lifecycle/Promote.js +476 -0
- package/dist/objects/lifecycle/Promote.js.map +1 -0
- package/dist/objects/lifecycle/Shard.js +560 -0
- package/dist/objects/lifecycle/Shard.js.map +1 -0
- package/dist/objects/lifecycle/index.js +15 -0
- package/dist/objects/lifecycle/index.js.map +1 -0
- package/dist/objects/lifecycle/types.js +33 -0
- package/dist/objects/lifecycle/types.js.map +1 -0
- package/dist/objects/mixins/infrastructure.js +171 -0
- package/dist/objects/mixins/infrastructure.js.map +1 -0
- package/dist/objects/modules/StoresModule.js +153 -0
- package/dist/objects/modules/StoresModule.js.map +1 -0
- package/dist/objects/persistence/checkpoint-manager.js +606 -0
- package/dist/objects/persistence/checkpoint-manager.js.map +1 -0
- package/dist/objects/persistence/index.js +72 -0
- package/dist/objects/persistence/index.js.map +1 -0
- package/dist/objects/persistence/migration-runner.js +562 -0
- package/dist/objects/persistence/migration-runner.js.map +1 -0
- package/dist/objects/persistence/replication-manager.js +501 -0
- package/dist/objects/persistence/replication-manager.js.map +1 -0
- package/dist/objects/persistence/tiered-storage-manager.js +595 -0
- package/dist/objects/persistence/tiered-storage-manager.js.map +1 -0
- package/dist/objects/persistence/types.js +14 -0
- package/dist/objects/persistence/types.js.map +1 -0
- package/dist/objects/persistence/wal-manager.js +653 -0
- package/dist/objects/persistence/wal-manager.js.map +1 -0
- package/dist/objects/presets/index.js +20 -0
- package/dist/objects/presets/index.js.map +1 -0
- package/dist/objects/presets/primitives.js +188 -0
- package/dist/objects/presets/primitives.js.map +1 -0
- package/dist/objects/primitives/alarm-adapter.js +141 -0
- package/dist/objects/primitives/alarm-adapter.js.map +1 -0
- package/dist/objects/primitives/index.js +337 -0
- package/dist/objects/primitives/index.js.map +1 -0
- package/dist/objects/primitives/storage-adapter.js +182 -0
- package/dist/objects/primitives/storage-adapter.js.map +1 -0
- package/dist/objects/primitives/with-primitives.js +102 -0
- package/dist/objects/primitives/with-primitives.js.map +1 -0
- package/dist/objects/services/StoreManager.js +227 -0
- package/dist/objects/services/StoreManager.js.map +1 -0
- package/dist/objects/services/index.js +13 -0
- package/dist/objects/services/index.js.map +1 -0
- package/dist/objects/transport/auth-layer.js +1451 -0
- package/dist/objects/transport/auth-layer.js.map +1 -0
- package/dist/objects/transport/capnweb-target.js +355 -0
- package/dist/objects/transport/capnweb-target.js.map +1 -0
- package/dist/objects/transport/chain.js +441 -0
- package/dist/objects/transport/chain.js.map +1 -0
- package/dist/objects/transport/handler.js +58 -0
- package/dist/objects/transport/handler.js.map +1 -0
- package/dist/objects/transport/index.js +53 -0
- package/dist/objects/transport/index.js.map +1 -0
- package/dist/objects/transport/mcp-server.js +691 -0
- package/dist/objects/transport/mcp-server.js.map +1 -0
- package/dist/objects/transport/rest-autowire.js +1508 -0
- package/dist/objects/transport/rest-autowire.js.map +1 -0
- package/dist/objects/transport/rest-router.js +440 -0
- package/dist/objects/transport/rest-router.js.map +1 -0
- package/dist/objects/transport/rpc-server.js +1539 -0
- package/dist/objects/transport/rpc-server.js.map +1 -0
- package/dist/objects/transport/shared.js +576 -0
- package/dist/objects/transport/shared.js.map +1 -0
- package/dist/objects/transport/sync-engine.js +291 -0
- package/dist/objects/transport/sync-engine.js.map +1 -0
- package/dist/objects/transport/types.js +8 -0
- package/dist/objects/transport/types.js.map +1 -0
- package/dist/sandbox/index.js +258 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/snippets/artifacts-config.js +241 -0
- package/dist/snippets/artifacts-config.js.map +1 -0
- package/dist/snippets/artifacts-ingest.js +832 -0
- package/dist/snippets/artifacts-ingest.js.map +1 -0
- package/dist/snippets/artifacts-serve.js +1035 -0
- package/dist/snippets/artifacts-serve.js.map +1 -0
- package/dist/snippets/artifacts-types.js +161 -0
- package/dist/snippets/artifacts-types.js.map +1 -0
- package/dist/snippets/cache-probe.js +376 -0
- package/dist/snippets/cache-probe.js.map +1 -0
- package/dist/snippets/cache.js +10 -0
- package/dist/snippets/cache.js.map +1 -0
- package/dist/snippets/events.js +469 -0
- package/dist/snippets/events.js.map +1 -0
- package/dist/snippets/index.js +7 -0
- package/dist/snippets/index.js.map +1 -0
- package/dist/snippets/proxy.js +495 -0
- package/dist/snippets/proxy.js.map +1 -0
- package/dist/snippets/search.js +1759 -0
- package/dist/snippets/search.js.map +1 -0
- package/dist/streams/index.js +30 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams/observability.js +68 -0
- package/dist/streams/observability.js.map +1 -0
- package/dist/types/AI.js +92 -0
- package/dist/types/AI.js.map +1 -0
- package/dist/types/AIFunction.js +171 -0
- package/dist/types/AIFunction.js.map +1 -0
- package/dist/types/BrowseVerb.js +89 -0
- package/dist/types/BrowseVerb.js.map +1 -0
- package/dist/types/Browser.js +31 -0
- package/dist/types/Browser.js.map +1 -0
- package/dist/types/Chaos.js +15 -0
- package/dist/types/Chaos.js.map +1 -0
- package/dist/types/CloudflareBindings.js +109 -0
- package/dist/types/CloudflareBindings.js.map +1 -0
- package/dist/types/Collection.js +50 -0
- package/dist/types/Collection.js.map +1 -0
- package/dist/types/DO.js +2 -0
- package/dist/types/DO.js.map +1 -0
- package/dist/types/DOLocation.js +63 -0
- package/dist/types/DOLocation.js.map +1 -0
- package/dist/types/EventHandler.js +57 -0
- package/dist/types/EventHandler.js.map +1 -0
- package/dist/types/Experiment.js +33 -0
- package/dist/types/Experiment.js.map +1 -0
- package/dist/types/Flag.js +57 -0
- package/dist/types/Flag.js.map +1 -0
- package/dist/types/Lifecycle.js +13 -0
- package/dist/types/Lifecycle.js.map +1 -0
- package/dist/types/Location.js +169 -0
- package/dist/types/Location.js.map +1 -0
- package/dist/types/Noun.js +66 -0
- package/dist/types/Noun.js.map +1 -0
- package/dist/types/SessionEvent.js +194 -0
- package/dist/types/SessionEvent.js.map +1 -0
- package/dist/types/Thing.js +55 -0
- package/dist/types/Thing.js.map +1 -0
- package/dist/types/ThingDO.js +153 -0
- package/dist/types/ThingDO.js.map +1 -0
- package/dist/types/Things.js +2 -0
- package/dist/types/Things.js.map +1 -0
- package/dist/types/Verb.js +119 -0
- package/dist/types/Verb.js.map +1 -0
- package/dist/types/WorkflowContext.js +70 -0
- package/dist/types/WorkflowContext.js.map +1 -0
- package/dist/types/analytics-api.js +13 -0
- package/dist/types/analytics-api.js.map +1 -0
- package/dist/types/capabilities.js +135 -0
- package/dist/types/capabilities.js.map +1 -0
- package/dist/types/drizzle.js +12 -0
- package/dist/types/drizzle.js.map +1 -0
- package/dist/types/event.js +201 -0
- package/dist/types/event.js.map +1 -0
- package/dist/types/fn.js +12 -0
- package/dist/types/fn.js.map +1 -0
- package/dist/types/iceberg.js +48 -0
- package/dist/types/iceberg.js.map +1 -0
- package/dist/types/ids.js +170 -0
- package/dist/types/ids.js.map +1 -0
- package/dist/types/index.js +41 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/introspect.js +54 -0
- package/dist/types/introspect.js.map +1 -0
- package/dist/types/observability.js +124 -0
- package/dist/types/observability.js.map +1 -0
- package/dist/types/sync-protocol.js +175 -0
- package/dist/types/sync-protocol.js.map +1 -0
- package/dist/types/vector.js +13 -0
- package/dist/types/vector.js.map +1 -0
- package/dist/workflows/ScheduleManager.js +473 -0
- package/dist/workflows/ScheduleManager.js.map +1 -0
- package/dist/workflows/StepDOBridge.js +149 -0
- package/dist/workflows/StepDOBridge.js.map +1 -0
- package/dist/workflows/StepResultStorage.js +232 -0
- package/dist/workflows/StepResultStorage.js.map +1 -0
- package/dist/workflows/WaitForEventManager.js +461 -0
- package/dist/workflows/WaitForEventManager.js.map +1 -0
- package/dist/workflows/analyzer.js +332 -0
- package/dist/workflows/analyzer.js.map +1 -0
- package/dist/workflows/compat/activity-router.js +484 -0
- package/dist/workflows/compat/activity-router.js.map +1 -0
- package/dist/workflows/compat/backends/cloudflare-workflows.js +431 -0
- package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -0
- package/dist/workflows/compat/backends/index.js +14 -0
- package/dist/workflows/compat/backends/index.js.map +1 -0
- package/dist/workflows/compat/errors/index.js +375 -0
- package/dist/workflows/compat/errors/index.js.map +1 -0
- package/dist/workflows/compat/index.js +79 -0
- package/dist/workflows/compat/index.js.map +1 -0
- package/dist/workflows/compat/inngest/index.js +989 -0
- package/dist/workflows/compat/inngest/index.js.map +1 -0
- package/dist/workflows/compat/qstash/index.js +1263 -0
- package/dist/workflows/compat/qstash/index.js.map +1 -0
- package/dist/workflows/compat/temporal/activities.js +739 -0
- package/dist/workflows/compat/temporal/activities.js.map +1 -0
- package/dist/workflows/compat/temporal/child-workflows.js +154 -0
- package/dist/workflows/compat/temporal/child-workflows.js.map +1 -0
- package/dist/workflows/compat/temporal/client.js +381 -0
- package/dist/workflows/compat/temporal/client.js.map +1 -0
- package/dist/workflows/compat/temporal/context.js +309 -0
- package/dist/workflows/compat/temporal/context.js.map +1 -0
- package/dist/workflows/compat/temporal/determinism.js +216 -0
- package/dist/workflows/compat/temporal/determinism.js.map +1 -0
- package/dist/workflows/compat/temporal/errors.js +128 -0
- package/dist/workflows/compat/temporal/errors.js.map +1 -0
- package/dist/workflows/compat/temporal/index.js +2464 -0
- package/dist/workflows/compat/temporal/index.js.map +1 -0
- package/dist/workflows/compat/temporal/saga.js +504 -0
- package/dist/workflows/compat/temporal/saga.js.map +1 -0
- package/dist/workflows/compat/temporal/signals.js +364 -0
- package/dist/workflows/compat/temporal/signals.js.map +1 -0
- package/dist/workflows/compat/temporal/storage.js +271 -0
- package/dist/workflows/compat/temporal/storage.js.map +1 -0
- package/dist/workflows/compat/temporal/timers.js +347 -0
- package/dist/workflows/compat/temporal/timers.js.map +1 -0
- package/dist/workflows/compat/temporal/types.js +7 -0
- package/dist/workflows/compat/temporal/types.js.map +1 -0
- package/dist/workflows/compat/temporal/unified-primitives.js +339 -0
- package/dist/workflows/compat/temporal/unified-primitives.js.map +1 -0
- package/dist/workflows/compat/trigger/index.js +468 -0
- package/dist/workflows/compat/trigger/index.js.map +1 -0
- package/dist/workflows/compat/utils/index.js +69 -0
- package/dist/workflows/compat/utils/index.js.map +1 -0
- package/dist/workflows/context/correlation-capability.js +266 -0
- package/dist/workflows/context/correlation-capability.js.map +1 -0
- package/dist/workflows/context/correlation.js +484 -0
- package/dist/workflows/context/correlation.js.map +1 -0
- package/dist/workflows/context/experiment.js +289 -0
- package/dist/workflows/context/experiment.js.map +1 -0
- package/dist/workflows/context/flag.js +244 -0
- package/dist/workflows/context/flag.js.map +1 -0
- package/dist/workflows/context/foundation.js +648 -0
- package/dist/workflows/context/foundation.js.map +1 -0
- package/dist/workflows/context/human-base.js +106 -0
- package/dist/workflows/context/human-base.js.map +1 -0
- package/dist/workflows/context/human.js +368 -0
- package/dist/workflows/context/human.js.map +1 -0
- package/dist/workflows/context/measure.js +354 -0
- package/dist/workflows/context/measure.js.map +1 -0
- package/dist/workflows/context/rate-limit.js +358 -0
- package/dist/workflows/context/rate-limit.js.map +1 -0
- package/dist/workflows/context/user.js +117 -0
- package/dist/workflows/context/user.js.map +1 -0
- package/dist/workflows/context/vault.js +360 -0
- package/dist/workflows/context/vault.js.map +1 -0
- package/dist/workflows/data/entity-events/entity-events.js +489 -0
- package/dist/workflows/data/entity-events/entity-events.js.map +1 -0
- package/dist/workflows/data/experiment/index.js +599 -0
- package/dist/workflows/data/experiment/index.js.map +1 -0
- package/dist/workflows/data/goal/context.js +558 -0
- package/dist/workflows/data/goal/context.js.map +1 -0
- package/dist/workflows/data/goal/index.js +32 -0
- package/dist/workflows/data/goal/index.js.map +1 -0
- package/dist/workflows/data/measure/index.js +840 -0
- package/dist/workflows/data/measure/index.js.map +1 -0
- package/dist/workflows/data/stream/index.js +1149 -0
- package/dist/workflows/data/stream/index.js.map +1 -0
- package/dist/workflows/data/track/context.js +883 -0
- package/dist/workflows/data/track/context.js.map +1 -0
- package/dist/workflows/data/track/index.js +15 -0
- package/dist/workflows/data/track/index.js.map +1 -0
- package/dist/workflows/data/view/context.js +864 -0
- package/dist/workflows/data/view/context.js.map +1 -0
- package/dist/workflows/domain.js +93 -0
- package/dist/workflows/domain.js.map +1 -0
- package/dist/workflows/flag.js +176 -0
- package/dist/workflows/flag.js.map +1 -0
- package/dist/workflows/flags.js +217 -0
- package/dist/workflows/flags.js.map +1 -0
- package/dist/workflows/hash.js +209 -0
- package/dist/workflows/hash.js.map +1 -0
- package/dist/workflows/index.js +50 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/on.js +378 -0
- package/dist/workflows/on.js.map +1 -0
- package/dist/workflows/pipeline-promise.js +481 -0
- package/dist/workflows/pipeline-promise.js.map +1 -0
- package/dist/workflows/pipeline-types.js +20 -0
- package/dist/workflows/pipeline-types.js.map +1 -0
- package/dist/workflows/proxy.js +76 -0
- package/dist/workflows/proxy.js.map +1 -0
- package/dist/workflows/runtime.js +310 -0
- package/dist/workflows/runtime.js.map +1 -0
- package/dist/workflows/schedule-builder.js +327 -0
- package/dist/workflows/schedule-builder.js.map +1 -0
- package/dist/workflows/visibility/index.js +146 -0
- package/dist/workflows/visibility/index.js.map +1 -0
- package/dist/workflows/visibility/query-parser.js +150 -0
- package/dist/workflows/visibility/query-parser.js.map +1 -0
- package/dist/workflows/visibility/store.js +223 -0
- package/dist/workflows/visibility/store.js.map +1 -0
- package/dist/workflows/visibility/types.js +30 -0
- package/dist/workflows/visibility/types.js.map +1 -0
- package/dist/workflows/workflow.js +53 -0
- package/dist/workflows/workflow.js.map +1 -0
- package/package.json +294 -46
|
@@ -0,0 +1,2464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal Compat Layer - 100% API Compatible with @temporalio/workflow
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacement for Temporal that runs on dotdo's
|
|
5
|
+
* durable execution infrastructure.
|
|
6
|
+
*
|
|
7
|
+
* ## Determinism Requirements
|
|
8
|
+
*
|
|
9
|
+
* Workflows MUST be deterministic to support replay. This means that given the
|
|
10
|
+
* same inputs and history, a workflow must produce the same sequence of commands.
|
|
11
|
+
*
|
|
12
|
+
* ### Non-Deterministic Operations to Avoid:
|
|
13
|
+
*
|
|
14
|
+
* | Avoid | Use Instead |
|
|
15
|
+
* |--------------------------|--------------------------------------|
|
|
16
|
+
* | `Date.now()` | `workflowNow()` - deterministic time |
|
|
17
|
+
* | `new Date()` | `workflowNow()` - returns Date |
|
|
18
|
+
* | `Math.random()` | `random()` - deterministic random |
|
|
19
|
+
* | `crypto.randomUUID()` | `uuid4()` - deterministic UUID |
|
|
20
|
+
* | `fetch()` / HTTP calls | Activities (via `proxyActivities`) |
|
|
21
|
+
* | `setTimeout/setInterval` | `sleep()` or `createTimer()` |
|
|
22
|
+
* | File I/O | Activities |
|
|
23
|
+
* | Database queries | Activities |
|
|
24
|
+
*
|
|
25
|
+
* ### Why Determinism Matters:
|
|
26
|
+
*
|
|
27
|
+
* When a workflow fails and restarts, it replays from the beginning using
|
|
28
|
+
* stored history. If your workflow makes different decisions on replay
|
|
29
|
+
* (e.g., because `Math.random()` returns a different value), the replay
|
|
30
|
+
* will diverge from history and fail.
|
|
31
|
+
*
|
|
32
|
+
* ### Development Mode Warnings:
|
|
33
|
+
*
|
|
34
|
+
* Set `warnOnNonDeterministic: true` in configuration to get console warnings
|
|
35
|
+
* when non-deterministic patterns are detected. This is enabled by default
|
|
36
|
+
* in development mode (`NODE_ENV !== 'production'`).
|
|
37
|
+
*
|
|
38
|
+
* ```typescript
|
|
39
|
+
* import { configureDeterminism, enableDeterminismDetection } from '@dotdo/temporal'
|
|
40
|
+
*
|
|
41
|
+
* // Configure warnings
|
|
42
|
+
* configureDeterminism({ warnOnNonDeterministic: true })
|
|
43
|
+
*
|
|
44
|
+
* // Enable detection (patches global Date.now, Math.random, fetch)
|
|
45
|
+
* enableDeterminismDetection()
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* import { proxyActivities, defineSignal, setHandler, sleep, condition, workflowNow, random } from '@dotdo/temporal'
|
|
51
|
+
*
|
|
52
|
+
* const { sendEmail, chargeCard } = proxyActivities<typeof activities>({
|
|
53
|
+
* startToCloseTimeout: '10s',
|
|
54
|
+
* retry: { maximumAttempts: 3 },
|
|
55
|
+
* })
|
|
56
|
+
*
|
|
57
|
+
* export async function orderWorkflow(order: Order) {
|
|
58
|
+
* const approved = defineSignal<[boolean]>('approve')
|
|
59
|
+
* let isApproved = false
|
|
60
|
+
*
|
|
61
|
+
* // Use deterministic alternatives
|
|
62
|
+
* const orderTime = workflowNow() // instead of new Date()
|
|
63
|
+
* const shouldDiscount = random() < 0.1 // instead of Math.random()
|
|
64
|
+
*
|
|
65
|
+
* setHandler(approved, (approval) => {
|
|
66
|
+
* isApproved = approval
|
|
67
|
+
* })
|
|
68
|
+
*
|
|
69
|
+
* await condition(() => isApproved, '7d')
|
|
70
|
+
*
|
|
71
|
+
* await chargeCard(order.cardToken, order.amount)
|
|
72
|
+
* await sendEmail(order.email, 'Order confirmed!')
|
|
73
|
+
*
|
|
74
|
+
* return { status: 'completed', orderTime }
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
79
|
+
import { WaitForEventManager, WaitCancelledError } from '../../WaitForEventManager';
|
|
80
|
+
import { DurableWorkflowRuntime, InMemoryStepStorage } from '../../runtime';
|
|
81
|
+
import { parseDuration, ensureError } from '../utils';
|
|
82
|
+
import { WorkerActivityRouter, ActivityTimeoutError, TaskQueueNotRegisteredError, } from '../activity-router';
|
|
83
|
+
// Re-export error classes for backward compatibility
|
|
84
|
+
export { ActivityTimeoutError, TaskQueueNotRegisteredError };
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// CFWORKFLOWS STORAGE STRATEGY - Uses native CF Workflows APIs
|
|
87
|
+
// ============================================================================
|
|
88
|
+
/**
|
|
89
|
+
* CFWorkflowsStorageStrategy uses Cloudflare Workflows native APIs for durable execution.
|
|
90
|
+
*
|
|
91
|
+
* Benefits:
|
|
92
|
+
* - sleep() is FREE - you don't pay for wall-clock time
|
|
93
|
+
* - step.do() is DURABLE - survives worker restarts, automatic retries
|
|
94
|
+
* - Replay is automatic - completed steps return cached results
|
|
95
|
+
*
|
|
96
|
+
* This strategy is automatically selected when a WorkflowStep is available
|
|
97
|
+
* in the current workflow context.
|
|
98
|
+
*/
|
|
99
|
+
export class CFWorkflowsStorageStrategy {
|
|
100
|
+
step;
|
|
101
|
+
stepResults = new Map();
|
|
102
|
+
constructor(step) {
|
|
103
|
+
this.step = step;
|
|
104
|
+
}
|
|
105
|
+
async executeStep(name, fn, options) {
|
|
106
|
+
// Check for cached result (replay)
|
|
107
|
+
const cached = this.stepResults.get(name);
|
|
108
|
+
if (cached) {
|
|
109
|
+
if (cached.status === 'error') {
|
|
110
|
+
throw cached.error;
|
|
111
|
+
}
|
|
112
|
+
return cached.value;
|
|
113
|
+
}
|
|
114
|
+
// Build step.do() options from execution options
|
|
115
|
+
const stepOptions = this.buildStepDoOptions(options);
|
|
116
|
+
try {
|
|
117
|
+
// Execute via step.do() for durability
|
|
118
|
+
const result = stepOptions
|
|
119
|
+
? await this.step.do(name, stepOptions, async () => fn())
|
|
120
|
+
: await this.step.do(name, async () => fn());
|
|
121
|
+
// Cache the result with discriminated union
|
|
122
|
+
this.stepResults.set(name, { status: 'success', value: result });
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
// Cache errors for deterministic replay
|
|
127
|
+
const err = ensureError(error);
|
|
128
|
+
this.stepResults.set(name, { status: 'error', error: err });
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async sleep(name, _durationMs, durationStr) {
|
|
133
|
+
// Check for cached completion (replay)
|
|
134
|
+
if (this.stepResults.has(name)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Use CF Workflows native step.sleep() - FREE, doesn't consume wall-clock time
|
|
138
|
+
await this.step.sleep(name, durationStr);
|
|
139
|
+
// Cache completion
|
|
140
|
+
this.stepResults.set(name, { status: 'success', value: true });
|
|
141
|
+
}
|
|
142
|
+
async isStepCompleted(name) {
|
|
143
|
+
return this.stepResults.has(name);
|
|
144
|
+
}
|
|
145
|
+
async getStepResult(name) {
|
|
146
|
+
const cached = this.stepResults.get(name);
|
|
147
|
+
if (!cached)
|
|
148
|
+
return undefined;
|
|
149
|
+
if (cached.status === 'error') {
|
|
150
|
+
throw cached.error;
|
|
151
|
+
}
|
|
152
|
+
return cached.value;
|
|
153
|
+
}
|
|
154
|
+
async setStepResult(name, result) {
|
|
155
|
+
if (result instanceof Error) {
|
|
156
|
+
this.stepResults.set(name, { status: 'error', error: result });
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this.stepResults.set(name, { status: 'success', value: result });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
buildStepDoOptions(options) {
|
|
163
|
+
if (!options)
|
|
164
|
+
return undefined;
|
|
165
|
+
const stepOptions = {};
|
|
166
|
+
if (options.retries) {
|
|
167
|
+
stepOptions.retries = options.retries;
|
|
168
|
+
}
|
|
169
|
+
if (options.timeout) {
|
|
170
|
+
stepOptions.timeout = options.timeout;
|
|
171
|
+
}
|
|
172
|
+
// Only return if we have something to configure
|
|
173
|
+
if (stepOptions.retries || stepOptions.timeout) {
|
|
174
|
+
return stepOptions;
|
|
175
|
+
}
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// ============================================================================
|
|
180
|
+
// INMEMORY STORAGE STRATEGY - Fallback for testing and non-CF environments
|
|
181
|
+
// ============================================================================
|
|
182
|
+
/**
|
|
183
|
+
* InMemoryStorageStrategy provides fallback behavior when no WorkflowStep is available.
|
|
184
|
+
*
|
|
185
|
+
* This is used in:
|
|
186
|
+
* - Testing environments
|
|
187
|
+
* - Development without CF Workflows runtime
|
|
188
|
+
* - Direct workflow execution outside of CF Workflows
|
|
189
|
+
*
|
|
190
|
+
* Cost: sleep() uses setTimeout which is BILLABLE (consumes DO wall-clock time)
|
|
191
|
+
*/
|
|
192
|
+
export class InMemoryStorageStrategy {
|
|
193
|
+
stepResults = new Map();
|
|
194
|
+
durableStorage;
|
|
195
|
+
constructor(storage) {
|
|
196
|
+
this.durableStorage = storage ?? new InMemoryStepStorage();
|
|
197
|
+
}
|
|
198
|
+
async executeStep(name, fn, _options) {
|
|
199
|
+
// Check in-memory cache first
|
|
200
|
+
const cached = this.stepResults.get(name);
|
|
201
|
+
if (cached) {
|
|
202
|
+
if (cached.status === 'error') {
|
|
203
|
+
throw cached.error;
|
|
204
|
+
}
|
|
205
|
+
return cached.value;
|
|
206
|
+
}
|
|
207
|
+
// Check durable storage for completed steps (survives worker restarts)
|
|
208
|
+
const durableResult = await this.durableStorage.get(name);
|
|
209
|
+
if (durableResult?.status === 'completed') {
|
|
210
|
+
this.stepResults.set(name, { status: 'success', value: durableResult.result });
|
|
211
|
+
return durableResult.result;
|
|
212
|
+
}
|
|
213
|
+
// Execute the function
|
|
214
|
+
try {
|
|
215
|
+
const result = await fn();
|
|
216
|
+
// Store in both in-memory and durable storage
|
|
217
|
+
this.stepResults.set(name, { status: 'success', value: result });
|
|
218
|
+
await this.durableStorage.set(name, {
|
|
219
|
+
stepId: name,
|
|
220
|
+
status: 'completed',
|
|
221
|
+
result,
|
|
222
|
+
attempts: 1,
|
|
223
|
+
createdAt: Date.now(),
|
|
224
|
+
completedAt: Date.now(),
|
|
225
|
+
});
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
const err = ensureError(error);
|
|
230
|
+
this.stepResults.set(name, { status: 'error', error: err });
|
|
231
|
+
await this.durableStorage.set(name, {
|
|
232
|
+
stepId: name,
|
|
233
|
+
status: 'failed',
|
|
234
|
+
error: err.message,
|
|
235
|
+
attempts: 1,
|
|
236
|
+
createdAt: Date.now(),
|
|
237
|
+
});
|
|
238
|
+
throw err;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async sleep(name, durationMs, _durationStr) {
|
|
242
|
+
// Check for cached completion
|
|
243
|
+
if (this.stepResults.has(name)) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Check durable storage
|
|
247
|
+
const durableResult = await this.durableStorage.get(name);
|
|
248
|
+
if (durableResult?.status === 'completed') {
|
|
249
|
+
this.stepResults.set(name, { status: 'success', value: true });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
// Persist pending state before sleeping
|
|
253
|
+
await this.durableStorage.set(name, {
|
|
254
|
+
stepId: name,
|
|
255
|
+
status: 'pending',
|
|
256
|
+
attempts: 1,
|
|
257
|
+
createdAt: Date.now(),
|
|
258
|
+
});
|
|
259
|
+
// Fallback to setTimeout - BILLABLE
|
|
260
|
+
await new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
261
|
+
// Persist completed state
|
|
262
|
+
await this.durableStorage.set(name, {
|
|
263
|
+
stepId: name,
|
|
264
|
+
status: 'completed',
|
|
265
|
+
result: true,
|
|
266
|
+
attempts: 1,
|
|
267
|
+
createdAt: Date.now(),
|
|
268
|
+
completedAt: Date.now(),
|
|
269
|
+
});
|
|
270
|
+
this.stepResults.set(name, { status: 'success', value: true });
|
|
271
|
+
}
|
|
272
|
+
async isStepCompleted(name) {
|
|
273
|
+
if (this.stepResults.has(name)) {
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
const durableResult = await this.durableStorage.get(name);
|
|
277
|
+
return durableResult?.status === 'completed';
|
|
278
|
+
}
|
|
279
|
+
async getStepResult(name) {
|
|
280
|
+
// Check in-memory first
|
|
281
|
+
const cached = this.stepResults.get(name);
|
|
282
|
+
if (cached) {
|
|
283
|
+
if (cached.status === 'error') {
|
|
284
|
+
throw cached.error;
|
|
285
|
+
}
|
|
286
|
+
return cached.value;
|
|
287
|
+
}
|
|
288
|
+
// Check durable storage
|
|
289
|
+
const durableResult = await this.durableStorage.get(name);
|
|
290
|
+
if (durableResult?.status === 'completed') {
|
|
291
|
+
return durableResult.result;
|
|
292
|
+
}
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
async setStepResult(name, result) {
|
|
296
|
+
if (result instanceof Error) {
|
|
297
|
+
this.stepResults.set(name, { status: 'error', error: result });
|
|
298
|
+
await this.durableStorage.set(name, {
|
|
299
|
+
stepId: name,
|
|
300
|
+
status: 'failed',
|
|
301
|
+
error: result.message,
|
|
302
|
+
attempts: 1,
|
|
303
|
+
createdAt: Date.now(),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
this.stepResults.set(name, { status: 'success', value: result });
|
|
308
|
+
await this.durableStorage.set(name, {
|
|
309
|
+
stepId: name,
|
|
310
|
+
status: 'completed',
|
|
311
|
+
result,
|
|
312
|
+
attempts: 1,
|
|
313
|
+
createdAt: Date.now(),
|
|
314
|
+
completedAt: Date.now(),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Clear all cached results (for testing)
|
|
320
|
+
*/
|
|
321
|
+
clear() {
|
|
322
|
+
this.stepResults.clear();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// ============================================================================
|
|
326
|
+
// STRATEGY FACTORY - Creates the appropriate strategy based on context
|
|
327
|
+
// ============================================================================
|
|
328
|
+
/**
|
|
329
|
+
* Get the appropriate storage strategy for the current workflow execution.
|
|
330
|
+
*
|
|
331
|
+
* Selection logic:
|
|
332
|
+
* 1. If WorkflowStep is available in context, use CFWorkflowsStorageStrategy (FREE sleeping, durable)
|
|
333
|
+
* 2. Otherwise, use InMemoryStorageStrategy (fallback, BILLABLE sleeping)
|
|
334
|
+
*
|
|
335
|
+
* @param workflowStep - Optional WorkflowStep from CF Workflows runtime
|
|
336
|
+
* @param storage - Optional StepStorage for InMemory fallback
|
|
337
|
+
* @returns The appropriate storage strategy
|
|
338
|
+
*/
|
|
339
|
+
export function createStorageStrategy(workflowStep, storage) {
|
|
340
|
+
if (workflowStep) {
|
|
341
|
+
return new CFWorkflowsStorageStrategy(workflowStep);
|
|
342
|
+
}
|
|
343
|
+
return new InMemoryStorageStrategy(storage);
|
|
344
|
+
}
|
|
345
|
+
// Default configuration - warn in development, silent in production
|
|
346
|
+
let determinismConfig = {
|
|
347
|
+
warnOnNonDeterministic: typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production',
|
|
348
|
+
};
|
|
349
|
+
/**
|
|
350
|
+
* Configure determinism enforcement settings
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* import { configureDeterminism } from '@dotdo/temporal'
|
|
355
|
+
*
|
|
356
|
+
* // Disable warnings in tests
|
|
357
|
+
* configureDeterminism({ warnOnNonDeterministic: false })
|
|
358
|
+
*
|
|
359
|
+
* // Enable warnings in production for debugging
|
|
360
|
+
* configureDeterminism({ warnOnNonDeterministic: true })
|
|
361
|
+
* ```
|
|
362
|
+
*/
|
|
363
|
+
export function configureDeterminism(config) {
|
|
364
|
+
determinismConfig = { ...determinismConfig, ...config };
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Warning class for tracking determinism violations.
|
|
368
|
+
* These are logged in development mode to help identify potential replay issues.
|
|
369
|
+
*/
|
|
370
|
+
export class WorkflowDeterminismWarning {
|
|
371
|
+
/** Type of non-deterministic operation detected */
|
|
372
|
+
type;
|
|
373
|
+
/** Human-readable message describing the violation */
|
|
374
|
+
message;
|
|
375
|
+
/** Stack trace showing where the violation occurred */
|
|
376
|
+
stack;
|
|
377
|
+
/** Workflow ID where the violation was detected (if available) */
|
|
378
|
+
workflowId;
|
|
379
|
+
/** Suggested alternative to use instead */
|
|
380
|
+
suggestion;
|
|
381
|
+
/** Timestamp when the warning was created */
|
|
382
|
+
timestamp;
|
|
383
|
+
constructor(type, message, suggestion, workflowId) {
|
|
384
|
+
this.type = type;
|
|
385
|
+
this.message = message;
|
|
386
|
+
this.suggestion = suggestion;
|
|
387
|
+
this.workflowId = workflowId;
|
|
388
|
+
this.timestamp = new Date();
|
|
389
|
+
this.stack = new Error().stack;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Format the warning for console output
|
|
393
|
+
*/
|
|
394
|
+
toString() {
|
|
395
|
+
const workflowInfo = this.workflowId ? ` in workflow ${this.workflowId}` : '';
|
|
396
|
+
return `[WorkflowDeterminismWarning]${workflowInfo}: ${this.message}\n Suggestion: ${this.suggestion}`;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Track warnings for analysis (limited to prevent memory leaks)
|
|
401
|
+
*/
|
|
402
|
+
const MAX_WARNINGS = 100;
|
|
403
|
+
const determinismWarnings = [];
|
|
404
|
+
/**
|
|
405
|
+
* Get all recorded determinism warnings
|
|
406
|
+
*/
|
|
407
|
+
export function getDeterminismWarnings() {
|
|
408
|
+
return determinismWarnings;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Clear all recorded determinism warnings
|
|
412
|
+
*/
|
|
413
|
+
export function clearDeterminismWarnings() {
|
|
414
|
+
determinismWarnings.length = 0;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Record a determinism violation warning
|
|
418
|
+
*/
|
|
419
|
+
function warnNonDeterministic(type, message, suggestion) {
|
|
420
|
+
// Only warn if enabled and we're in a workflow context
|
|
421
|
+
const workflow = getCurrentWorkflow();
|
|
422
|
+
if (!determinismConfig.warnOnNonDeterministic || !workflow) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const warning = new WorkflowDeterminismWarning(type, message, suggestion, workflow.workflowId);
|
|
426
|
+
// Store warning (with limit to prevent memory leaks)
|
|
427
|
+
if (determinismWarnings.length < MAX_WARNINGS) {
|
|
428
|
+
determinismWarnings.push(warning);
|
|
429
|
+
}
|
|
430
|
+
// Log to console in development
|
|
431
|
+
console.warn(warning.toString());
|
|
432
|
+
}
|
|
433
|
+
// AsyncLocalStorage provides execution-scoped context
|
|
434
|
+
// This works in both Node.js and Cloudflare Workers runtime
|
|
435
|
+
const workflowContextStorage = new AsyncLocalStorage();
|
|
436
|
+
/**
|
|
437
|
+
* Get the current workflow context from AsyncLocalStorage.
|
|
438
|
+
* Returns null if not executing within a workflow.
|
|
439
|
+
*/
|
|
440
|
+
function getCurrentContext() {
|
|
441
|
+
return workflowContextStorage.getStore() ?? null;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Get the current workflow state for backward compatibility.
|
|
445
|
+
* This function bridges the old global state approach with the new context-based approach.
|
|
446
|
+
*/
|
|
447
|
+
function getCurrentWorkflow() {
|
|
448
|
+
const ctx = getCurrentContext();
|
|
449
|
+
return ctx?.workflow ?? null;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Get the current patch state from context.
|
|
453
|
+
*/
|
|
454
|
+
function getCurrentPatchState() {
|
|
455
|
+
const ctx = getCurrentContext();
|
|
456
|
+
return ctx?.patchState ?? null;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Set the current patch state within the context.
|
|
460
|
+
*/
|
|
461
|
+
function setCurrentPatchState(patchState) {
|
|
462
|
+
const ctx = getCurrentContext();
|
|
463
|
+
if (ctx) {
|
|
464
|
+
ctx.patchState = patchState;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Execute a workflow function within a context.
|
|
469
|
+
* This ensures all workflow operations have access to the correct context.
|
|
470
|
+
*/
|
|
471
|
+
function runWithContext(context, fn) {
|
|
472
|
+
return workflowContextStorage.run(context, fn);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Get the storage strategy for the current workflow context.
|
|
476
|
+
*
|
|
477
|
+
* This function creates the appropriate storage strategy based on the current context:
|
|
478
|
+
* - If a WorkflowStep is available, returns CFWorkflowsStorageStrategy (FREE sleeping, durable)
|
|
479
|
+
* - Otherwise, returns InMemoryStorageStrategy (fallback, BILLABLE sleeping)
|
|
480
|
+
*
|
|
481
|
+
* Note: Strategies are created on-demand rather than stored in context to avoid
|
|
482
|
+
* stale strategy references when WorkflowStep is set/cleared dynamically.
|
|
483
|
+
*/
|
|
484
|
+
function getCurrentStorageStrategy() {
|
|
485
|
+
const ctx = getCurrentContext();
|
|
486
|
+
const step = ctx?.workflowStep ?? null;
|
|
487
|
+
const storage = getCurrentStorage();
|
|
488
|
+
return createStorageStrategy(step, storage);
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Get the current storage from context or fallback to global.
|
|
492
|
+
*
|
|
493
|
+
* This provides a single source of truth for storage access, consolidating
|
|
494
|
+
* the pattern of `ctx?.storage ?? globalStorage` that was previously
|
|
495
|
+
* scattered throughout the codebase.
|
|
496
|
+
*/
|
|
497
|
+
function getCurrentStorage() {
|
|
498
|
+
const ctx = getCurrentContext();
|
|
499
|
+
return ctx?.storage ?? globalStorage;
|
|
500
|
+
}
|
|
501
|
+
// ============================================================================
|
|
502
|
+
// CF WORKFLOWS STEP CONTEXT - For native Cloudflare Workflows integration
|
|
503
|
+
// ============================================================================
|
|
504
|
+
/**
|
|
505
|
+
* Set the WorkflowStep context for the current workflow execution.
|
|
506
|
+
* Call this at the beginning of a CF Workflows run() method to enable
|
|
507
|
+
* native step.do() and step.sleep() execution.
|
|
508
|
+
*
|
|
509
|
+
* The step is stored in the workflow's AsyncLocalStorage context, ensuring
|
|
510
|
+
* isolation between concurrent workflow executions.
|
|
511
|
+
*
|
|
512
|
+
* @param step - The WorkflowStep from CF Workflows runtime, or null to clear
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* ```typescript
|
|
516
|
+
* async run(event: WorkflowEvent, step: WorkflowStep) {
|
|
517
|
+
* setWorkflowStep(step)
|
|
518
|
+
* try {
|
|
519
|
+
* // Temporal compat layer will use step.sleep() and step.do()
|
|
520
|
+
* await sleep('5s')
|
|
521
|
+
* await activities.processOrder(event.payload.orderId)
|
|
522
|
+
* } finally {
|
|
523
|
+
* clearWorkflowStep()
|
|
524
|
+
* }
|
|
525
|
+
* }
|
|
526
|
+
* ```
|
|
527
|
+
*/
|
|
528
|
+
export function setWorkflowStep(step) {
|
|
529
|
+
const ctx = getCurrentContext();
|
|
530
|
+
if (ctx) {
|
|
531
|
+
ctx.workflowStep = step;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Clear the WorkflowStep context for the current workflow.
|
|
536
|
+
* Call this when exiting the CF Workflows context.
|
|
537
|
+
*/
|
|
538
|
+
export function clearWorkflowStep() {
|
|
539
|
+
const ctx = getCurrentContext();
|
|
540
|
+
if (ctx) {
|
|
541
|
+
ctx.workflowStep = null;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Get the current WorkflowStep context, or null if not in a CF Workflows context.
|
|
546
|
+
* Used internally by sleep() and proxyActivities() to route to native APIs.
|
|
547
|
+
*/
|
|
548
|
+
export function getWorkflowStep() {
|
|
549
|
+
const ctx = getCurrentContext();
|
|
550
|
+
return ctx?.workflowStep ?? null;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Format a duration in milliseconds to CF Workflows format string.
|
|
554
|
+
* CF Workflows accepts durations like '5s', '1m', '1h', etc.
|
|
555
|
+
*
|
|
556
|
+
* @param ms - Duration in milliseconds
|
|
557
|
+
* @returns Formatted duration string
|
|
558
|
+
*/
|
|
559
|
+
function formatDurationForCF(ms) {
|
|
560
|
+
if (ms < 1000) {
|
|
561
|
+
return `${ms}ms`;
|
|
562
|
+
}
|
|
563
|
+
if (ms < 60 * 1000) {
|
|
564
|
+
const seconds = Math.round(ms / 1000);
|
|
565
|
+
return `${seconds}s`;
|
|
566
|
+
}
|
|
567
|
+
if (ms < 60 * 60 * 1000) {
|
|
568
|
+
const minutes = Math.round(ms / (60 * 1000));
|
|
569
|
+
return `${minutes}m`;
|
|
570
|
+
}
|
|
571
|
+
const hours = Math.round(ms / (60 * 60 * 1000));
|
|
572
|
+
return `${hours}h`;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Generate a unique step ID for sleep operations.
|
|
576
|
+
* Uses per-workflow counter for deterministic IDs across concurrent workflows.
|
|
577
|
+
*
|
|
578
|
+
* @param ms - Duration in milliseconds
|
|
579
|
+
* @returns Unique step ID
|
|
580
|
+
*/
|
|
581
|
+
function generateSleepStepId(ms) {
|
|
582
|
+
const ctx = getCurrentContext();
|
|
583
|
+
if (!ctx?.workflow) {
|
|
584
|
+
// Fallback for non-workflow context (testing)
|
|
585
|
+
return `sleep:${formatDurationForCF(ms)}:${Date.now()}`;
|
|
586
|
+
}
|
|
587
|
+
ctx.workflow.sleepStepCounter++;
|
|
588
|
+
return `sleep:${formatDurationForCF(ms)}:${ctx.workflow.sleepStepCounter}`;
|
|
589
|
+
}
|
|
590
|
+
// ============================================================================
|
|
591
|
+
// DETERMINISTIC TIME - workflowNow() implementation
|
|
592
|
+
// ============================================================================
|
|
593
|
+
/**
|
|
594
|
+
* Per-workflow counters for deterministic workflowNow() step IDs
|
|
595
|
+
*/
|
|
596
|
+
const nowCounters = new WeakMap();
|
|
597
|
+
/**
|
|
598
|
+
* Get deterministic current time (for replay).
|
|
599
|
+
*
|
|
600
|
+
* This function returns a deterministic timestamp based on workflow start time
|
|
601
|
+
* plus an offset derived from the step count. On replay, it returns the same
|
|
602
|
+
* timestamp that was recorded during the original execution.
|
|
603
|
+
*
|
|
604
|
+
* Use this instead of `Date.now()` or `new Date()` in workflow code.
|
|
605
|
+
*
|
|
606
|
+
* @returns A Date object representing the current workflow time
|
|
607
|
+
* @throws Error if called outside a workflow context
|
|
608
|
+
*
|
|
609
|
+
* @example
|
|
610
|
+
* ```typescript
|
|
611
|
+
* import { workflowNow } from '@dotdo/temporal'
|
|
612
|
+
*
|
|
613
|
+
* export async function orderWorkflow() {
|
|
614
|
+
* // Use workflowNow() instead of new Date() or Date.now()
|
|
615
|
+
* const orderTime = workflowNow()
|
|
616
|
+
* const expiresAt = new Date(workflowNow().getTime() + 24 * 60 * 60 * 1000)
|
|
617
|
+
*
|
|
618
|
+
* return { orderTime, expiresAt }
|
|
619
|
+
* }
|
|
620
|
+
* ```
|
|
621
|
+
*/
|
|
622
|
+
export function workflowNow() {
|
|
623
|
+
const workflow = getCurrentWorkflow();
|
|
624
|
+
if (!workflow) {
|
|
625
|
+
// Outside workflow context - fall back to real time
|
|
626
|
+
// This allows usage in tests or non-workflow code
|
|
627
|
+
return new Date();
|
|
628
|
+
}
|
|
629
|
+
// Get and increment the counter for this workflow
|
|
630
|
+
const counter = nowCounters.get(workflow) ?? 0;
|
|
631
|
+
nowCounters.set(workflow, counter + 1);
|
|
632
|
+
// Create deterministic step ID based on call order
|
|
633
|
+
const stepId = `workflowNow:${counter}`;
|
|
634
|
+
// Check for existing result (replay case)
|
|
635
|
+
if (workflow.stepResults.has(stepId)) {
|
|
636
|
+
return new Date(workflow.stepResults.get(stepId));
|
|
637
|
+
}
|
|
638
|
+
// Calculate deterministic time:
|
|
639
|
+
// Start time + (counter * small increment to show progression)
|
|
640
|
+
// This ensures time appears to progress while remaining deterministic
|
|
641
|
+
// NOTE: We use the workflowNow counter (not historyLength) because historyLength
|
|
642
|
+
// can vary between replays due to conditional paths or optimizations
|
|
643
|
+
const baseTime = workflow.startTime.getTime();
|
|
644
|
+
const stepIncrement = counter + 1; // 1ms per call for minimal progression (counter is 0-indexed)
|
|
645
|
+
const deterministicTime = baseTime + stepIncrement;
|
|
646
|
+
// Store for replay
|
|
647
|
+
workflow.stepResults.set(stepId, deterministicTime);
|
|
648
|
+
workflow.historyLength++;
|
|
649
|
+
return new Date(deterministicTime);
|
|
650
|
+
}
|
|
651
|
+
// ============================================================================
|
|
652
|
+
// NON-DETERMINISTIC PATTERN DETECTION - Interceptors for common violations
|
|
653
|
+
// ============================================================================
|
|
654
|
+
// Store original functions for restoration and proxying
|
|
655
|
+
const originalDateNow = Date.now;
|
|
656
|
+
const originalMathRandom = Math.random;
|
|
657
|
+
const originalFetch = typeof fetch !== 'undefined' ? fetch : undefined;
|
|
658
|
+
const originalSetTimeout = setTimeout;
|
|
659
|
+
const originalSetInterval = setInterval;
|
|
660
|
+
/**
|
|
661
|
+
* Wrapped Date.now that warns about non-deterministic usage in workflows
|
|
662
|
+
*/
|
|
663
|
+
function wrappedDateNow() {
|
|
664
|
+
const workflow = getCurrentWorkflow();
|
|
665
|
+
if (workflow && determinismConfig.warnOnNonDeterministic) {
|
|
666
|
+
warnNonDeterministic('Date.now', 'Date.now() is non-deterministic and will return different values on replay', 'Use workflowNow() for deterministic timestamps');
|
|
667
|
+
}
|
|
668
|
+
return originalDateNow.call(Date);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Wrapped Math.random that warns about non-deterministic usage in workflows
|
|
672
|
+
*/
|
|
673
|
+
function wrappedMathRandom() {
|
|
674
|
+
const workflow = getCurrentWorkflow();
|
|
675
|
+
if (workflow && determinismConfig.warnOnNonDeterministic) {
|
|
676
|
+
warnNonDeterministic('Math.random', 'Math.random() is non-deterministic and will return different values on replay', 'Use random() for deterministic random numbers');
|
|
677
|
+
}
|
|
678
|
+
return originalMathRandom.call(Math);
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Wrapped fetch that warns about non-deterministic usage in workflows
|
|
682
|
+
*/
|
|
683
|
+
function wrappedFetch(input, init) {
|
|
684
|
+
const workflow = getCurrentWorkflow();
|
|
685
|
+
if (workflow && determinismConfig.warnOnNonDeterministic) {
|
|
686
|
+
warnNonDeterministic('fetch', 'fetch() is non-deterministic - network responses can vary between executions', 'Use activities (proxyActivities) for network calls');
|
|
687
|
+
}
|
|
688
|
+
return originalFetch(input, init);
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Wrapped setTimeout that warns about non-deterministic usage in workflows
|
|
692
|
+
*/
|
|
693
|
+
function wrappedSetTimeout(callback, ms, ...args) {
|
|
694
|
+
const workflow = getCurrentWorkflow();
|
|
695
|
+
if (workflow && determinismConfig.warnOnNonDeterministic) {
|
|
696
|
+
warnNonDeterministic('setTimeout', 'setTimeout() is non-deterministic - timing varies between executions', 'Use sleep() or createTimer() for durable delays');
|
|
697
|
+
}
|
|
698
|
+
return originalSetTimeout(callback, ms, ...args);
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Wrapped setInterval that warns about non-deterministic usage in workflows
|
|
702
|
+
*/
|
|
703
|
+
function wrappedSetInterval(callback, ms, ...args) {
|
|
704
|
+
const workflow = getCurrentWorkflow();
|
|
705
|
+
if (workflow && determinismConfig.warnOnNonDeterministic) {
|
|
706
|
+
warnNonDeterministic('setInterval', 'setInterval() is non-deterministic - timing varies between executions', 'Use sleep() in a loop or schedule recurring activities');
|
|
707
|
+
}
|
|
708
|
+
return originalSetInterval(callback, ms, ...args);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Enable determinism detection by patching global functions.
|
|
712
|
+
* This is automatically called when the module loads in development mode.
|
|
713
|
+
*
|
|
714
|
+
* Note: This patches global objects, which may affect other code.
|
|
715
|
+
* Use with caution in shared environments.
|
|
716
|
+
*/
|
|
717
|
+
export function enableDeterminismDetection() {
|
|
718
|
+
// Only patch if we're configured to warn
|
|
719
|
+
if (!determinismConfig.warnOnNonDeterministic) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
// Patch Date.now
|
|
723
|
+
Date.now = wrappedDateNow;
|
|
724
|
+
// Patch Math.random
|
|
725
|
+
Math.random = wrappedMathRandom;
|
|
726
|
+
// Patch fetch if available
|
|
727
|
+
if (originalFetch && typeof globalThis !== 'undefined') {
|
|
728
|
+
;
|
|
729
|
+
globalThis.fetch = wrappedFetch;
|
|
730
|
+
}
|
|
731
|
+
// Patch setTimeout and setInterval
|
|
732
|
+
// Note: These are intentionally not patched by default as they're used internally
|
|
733
|
+
// by the workflow runtime. Only patch if explicitly requested.
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Disable determinism detection and restore original functions.
|
|
737
|
+
*/
|
|
738
|
+
export function disableDeterminismDetection() {
|
|
739
|
+
Date.now = originalDateNow;
|
|
740
|
+
Math.random = originalMathRandom;
|
|
741
|
+
if (originalFetch && typeof globalThis !== 'undefined') {
|
|
742
|
+
;
|
|
743
|
+
globalThis.fetch = originalFetch;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Check if code is running within a workflow context.
|
|
748
|
+
* Useful for conditional behavior based on workflow vs non-workflow execution.
|
|
749
|
+
*/
|
|
750
|
+
export function inWorkflowContext() {
|
|
751
|
+
return getCurrentWorkflow() !== null;
|
|
752
|
+
}
|
|
753
|
+
// ============================================================================
|
|
754
|
+
// GLOBAL REGISTRIES - These are shared lookup tables, not per-execution state
|
|
755
|
+
// ============================================================================
|
|
756
|
+
let globalStorage = new InMemoryStepStorage();
|
|
757
|
+
let globalState = null;
|
|
758
|
+
let globalWaitManager = null;
|
|
759
|
+
let globalNamespace = 'default';
|
|
760
|
+
// Workflow registry - shared across all executions for handle lookups
|
|
761
|
+
const workflows = new Map();
|
|
762
|
+
const workflowFunctions = new Map();
|
|
763
|
+
/**
|
|
764
|
+
* Shared ActivityRouter instance used for routing activities to workers.
|
|
765
|
+
* This provides the unified routing abstraction used across all compat layers.
|
|
766
|
+
*/
|
|
767
|
+
const activityRouter = new WorkerActivityRouter();
|
|
768
|
+
/**
|
|
769
|
+
* Register a worker for a task queue.
|
|
770
|
+
*
|
|
771
|
+
* In real Temporal, workers poll task queues for work. This compat layer
|
|
772
|
+
* validates that a worker is registered before allowing workflow/activity
|
|
773
|
+
* execution on that queue.
|
|
774
|
+
*
|
|
775
|
+
* @param taskQueue - The task queue name to register
|
|
776
|
+
* @param handler - Optional handler configuration for the worker
|
|
777
|
+
* @returns A function to unregister the worker
|
|
778
|
+
*
|
|
779
|
+
* @example
|
|
780
|
+
* ```typescript
|
|
781
|
+
* import { registerWorker } from '@dotdo/temporal'
|
|
782
|
+
*
|
|
783
|
+
* // Simple registration - just validates the queue exists
|
|
784
|
+
* const unregister = registerWorker('my-task-queue')
|
|
785
|
+
*
|
|
786
|
+
* // With workflow types
|
|
787
|
+
* const unregister = registerWorker('my-task-queue', {
|
|
788
|
+
* workflowTypes: new Set(['orderWorkflow', 'paymentWorkflow']),
|
|
789
|
+
* })
|
|
790
|
+
*
|
|
791
|
+
* // Cleanup when done
|
|
792
|
+
* unregister()
|
|
793
|
+
* ```
|
|
794
|
+
*/
|
|
795
|
+
export function registerWorker(taskQueue, handler = {}) {
|
|
796
|
+
// Delegate to the shared ActivityRouter instance
|
|
797
|
+
return activityRouter.registerWorker(taskQueue, handler);
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Check if a task queue has a registered worker
|
|
801
|
+
*/
|
|
802
|
+
export function hasWorker(taskQueue) {
|
|
803
|
+
return activityRouter.hasWorker(taskQueue);
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Get the worker handler for a task queue
|
|
807
|
+
*/
|
|
808
|
+
export function getWorker(taskQueue) {
|
|
809
|
+
return activityRouter.getWorker(taskQueue);
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* List all registered task queues
|
|
813
|
+
*/
|
|
814
|
+
export function listTaskQueues() {
|
|
815
|
+
return activityRouter.listTaskQueues();
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Check if task queue routing is enabled.
|
|
819
|
+
*
|
|
820
|
+
* Task queue validation is enabled when at least one worker has been registered.
|
|
821
|
+
* This maintains backward compatibility - existing code that doesnt use
|
|
822
|
+
* registerWorker() will continue to work without changes.
|
|
823
|
+
*/
|
|
824
|
+
function isTaskQueueRoutingEnabled() {
|
|
825
|
+
return activityRouter.isRoutingEnabled();
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Validate that a task queue is registered for workflow execution.
|
|
829
|
+
* Throws TaskQueueNotRegisteredError if not registered.
|
|
830
|
+
*
|
|
831
|
+
* NOTE: Validation is only performed when task queue routing is enabled
|
|
832
|
+
* (i.e., when at least one worker has been registered via registerWorker()).
|
|
833
|
+
* This maintains backward compatibility with existing code.
|
|
834
|
+
*
|
|
835
|
+
* @param taskQueue - The task queue to validate
|
|
836
|
+
* @param workflowType - Optional workflow type for more specific validation
|
|
837
|
+
* @throws TaskQueueNotRegisteredError if no worker is registered
|
|
838
|
+
*/
|
|
839
|
+
function validateTaskQueueForWorkflow(taskQueue, workflowType) {
|
|
840
|
+
// Skip validation if no workers are registered (backward compatibility)
|
|
841
|
+
if (!isTaskQueueRoutingEnabled()) {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
const worker = activityRouter.getWorker(taskQueue);
|
|
845
|
+
if (!worker) {
|
|
846
|
+
throw new TaskQueueNotRegisteredError(taskQueue, 'workflow');
|
|
847
|
+
}
|
|
848
|
+
// If worker specifies workflow types, validate this type is registered
|
|
849
|
+
if (workflowType && worker.workflowTypes && worker.workflowTypes.size > 0) {
|
|
850
|
+
if (!worker.workflowTypes.has(workflowType)) {
|
|
851
|
+
throw new Error(`Workflow type "${workflowType}" is not registered on task queue "${taskQueue}". ` +
|
|
852
|
+
`Registered types: ${Array.from(worker.workflowTypes).join(', ')}`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Validate that a task queue is registered for activity execution.
|
|
858
|
+
* Throws TaskQueueNotRegisteredError if not registered.
|
|
859
|
+
*
|
|
860
|
+
* NOTE: Validation is only performed when task queue routing is enabled
|
|
861
|
+
* (i.e., when at least one worker has been registered via registerWorker()).
|
|
862
|
+
* This maintains backward compatibility with existing code.
|
|
863
|
+
*
|
|
864
|
+
* @param taskQueue - The task queue to validate
|
|
865
|
+
* @param activityName - Optional activity name for more specific validation
|
|
866
|
+
* @throws TaskQueueNotRegisteredError if no worker is registered
|
|
867
|
+
*/
|
|
868
|
+
function validateTaskQueueForActivity(taskQueue, activityName) {
|
|
869
|
+
// Skip validation if no workers are registered (backward compatibility)
|
|
870
|
+
if (!isTaskQueueRoutingEnabled()) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const worker = activityRouter.getWorker(taskQueue);
|
|
874
|
+
if (!worker) {
|
|
875
|
+
throw new TaskQueueNotRegisteredError(taskQueue, 'activity');
|
|
876
|
+
}
|
|
877
|
+
// If worker specifies activity types, validate this activity is registered
|
|
878
|
+
if (activityName && worker.activityTypes && worker.activityTypes.size > 0) {
|
|
879
|
+
if (!worker.activityTypes.has(activityName)) {
|
|
880
|
+
throw new Error(`Activity "${activityName}" is not registered on task queue "${taskQueue}". ` +
|
|
881
|
+
`Registered activities: ${Array.from(worker.activityTypes).join(', ')}`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
// Timer tracking - global for cancel operations by ID
|
|
886
|
+
const activeTimers = new Map();
|
|
887
|
+
// ============================================================================
|
|
888
|
+
// WORKFLOW REGISTRY CLEANUP - Memory leak prevention
|
|
889
|
+
// ============================================================================
|
|
890
|
+
// Completed workflows are kept for a short TTL to allow result retrieval
|
|
891
|
+
const WORKFLOW_COMPLETED_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
892
|
+
// Maximum number of workflows to keep in registry (LRU eviction)
|
|
893
|
+
const WORKFLOW_MAX_REGISTRY_SIZE = 10000;
|
|
894
|
+
// Cleanup interval for expired workflows
|
|
895
|
+
const WORKFLOW_CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
896
|
+
// Track completion times for TTL-based cleanup
|
|
897
|
+
const workflowCompletionTimes = new Map();
|
|
898
|
+
let workflowCleanupIntervalId = null;
|
|
899
|
+
/**
|
|
900
|
+
* Terminal states that indicate a workflow has finished execution
|
|
901
|
+
*/
|
|
902
|
+
const TERMINAL_STATES = new Set([
|
|
903
|
+
'COMPLETED',
|
|
904
|
+
'FAILED',
|
|
905
|
+
'CANCELED',
|
|
906
|
+
'TERMINATED',
|
|
907
|
+
'CONTINUED_AS_NEW',
|
|
908
|
+
'TIMED_OUT',
|
|
909
|
+
]);
|
|
910
|
+
/**
|
|
911
|
+
* Check if a workflow status is terminal (finished)
|
|
912
|
+
*/
|
|
913
|
+
function isTerminalState(status) {
|
|
914
|
+
return TERMINAL_STATES.has(status);
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Mark a workflow as completed and schedule cleanup.
|
|
918
|
+
* Called when a workflow transitions to a terminal state.
|
|
919
|
+
*/
|
|
920
|
+
function markWorkflowCompleted(workflowId) {
|
|
921
|
+
workflowCompletionTimes.set(workflowId, Date.now());
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Remove a workflow from the registry and cleanup tracking
|
|
925
|
+
*/
|
|
926
|
+
function removeWorkflow(workflowId) {
|
|
927
|
+
workflows.delete(workflowId);
|
|
928
|
+
workflowCompletionTimes.delete(workflowId);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Clean up expired workflows (past TTL) and enforce LRU eviction.
|
|
932
|
+
* This runs periodically to prevent unbounded memory growth.
|
|
933
|
+
*/
|
|
934
|
+
function cleanupExpiredWorkflows() {
|
|
935
|
+
const now = Date.now();
|
|
936
|
+
const expiredIds = [];
|
|
937
|
+
// Find workflows past their TTL
|
|
938
|
+
for (const [workflowId, completionTime] of Array.from(workflowCompletionTimes.entries())) {
|
|
939
|
+
if (now - completionTime >= WORKFLOW_COMPLETED_TTL_MS) {
|
|
940
|
+
expiredIds.push(workflowId);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
// Remove expired workflows
|
|
944
|
+
for (const workflowId of expiredIds) {
|
|
945
|
+
removeWorkflow(workflowId);
|
|
946
|
+
}
|
|
947
|
+
// LRU eviction if still over max size
|
|
948
|
+
if (workflows.size > WORKFLOW_MAX_REGISTRY_SIZE) {
|
|
949
|
+
// Sort completed workflows by completion time (oldest first)
|
|
950
|
+
const completedWorkflows = Array.from(workflowCompletionTimes.entries())
|
|
951
|
+
.sort((a, b) => a[1] - b[1]);
|
|
952
|
+
// Evict oldest completed workflows until under limit
|
|
953
|
+
const excessCount = workflows.size - WORKFLOW_MAX_REGISTRY_SIZE;
|
|
954
|
+
for (let i = 0; i < Math.min(excessCount, completedWorkflows.length); i++) {
|
|
955
|
+
removeWorkflow(completedWorkflows[i][0]);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Start the periodic workflow cleanup (for production use).
|
|
961
|
+
* This should be called once when the module is loaded in a long-running process.
|
|
962
|
+
*/
|
|
963
|
+
export function __startWorkflowCleanup() {
|
|
964
|
+
if (workflowCleanupIntervalId === null) {
|
|
965
|
+
workflowCleanupIntervalId = setInterval(cleanupExpiredWorkflows, WORKFLOW_CLEANUP_INTERVAL_MS);
|
|
966
|
+
// Unref the interval so it doesn't prevent process exit
|
|
967
|
+
if (typeof workflowCleanupIntervalId === 'object' && 'unref' in workflowCleanupIntervalId) {
|
|
968
|
+
workflowCleanupIntervalId.unref();
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Stop the periodic workflow cleanup.
|
|
974
|
+
*/
|
|
975
|
+
export function __stopWorkflowCleanup() {
|
|
976
|
+
if (workflowCleanupIntervalId !== null) {
|
|
977
|
+
clearInterval(workflowCleanupIntervalId);
|
|
978
|
+
workflowCleanupIntervalId = null;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
// ============================================================================
|
|
982
|
+
// LAZY CLEANUP INITIALIZATION - Auto-start cleanup on first usage
|
|
983
|
+
// ============================================================================
|
|
984
|
+
/**
|
|
985
|
+
* Track whether cleanup intervals have been auto-started.
|
|
986
|
+
* This enables lazy initialization - cleanup only starts when actually needed.
|
|
987
|
+
*/
|
|
988
|
+
let cleanupStarted = false;
|
|
989
|
+
/**
|
|
990
|
+
* Ensure cleanup intervals are started (lazy initialization).
|
|
991
|
+
*
|
|
992
|
+
* This function is called automatically when:
|
|
993
|
+
* - A workflow is started (WorkflowClient.start, startChild)
|
|
994
|
+
* - A timer is created (createTimer)
|
|
995
|
+
*
|
|
996
|
+
* This eliminates the need to manually call __startWorkflowCleanup() and
|
|
997
|
+
* __startTimerCleanup(), preventing memory leaks from accumulated workflows
|
|
998
|
+
* and timers even when the manual calls are forgotten.
|
|
999
|
+
*
|
|
1000
|
+
* The cleanup intervals are idempotent - calling this multiple times is safe.
|
|
1001
|
+
*/
|
|
1002
|
+
export function ensureCleanupStarted() {
|
|
1003
|
+
if (cleanupStarted)
|
|
1004
|
+
return;
|
|
1005
|
+
cleanupStarted = true;
|
|
1006
|
+
__startWorkflowCleanup();
|
|
1007
|
+
__startTimerCleanup();
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Reset the cleanup started flag (for testing only).
|
|
1011
|
+
* This allows tests to verify the lazy initialization behavior.
|
|
1012
|
+
*/
|
|
1013
|
+
export function __resetCleanupStarted() {
|
|
1014
|
+
cleanupStarted = false;
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Check if cleanup has been auto-started (for testing only).
|
|
1018
|
+
*/
|
|
1019
|
+
export function __isCleanupStarted() {
|
|
1020
|
+
return cleanupStarted;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Configure the Temporal compat layer globals.
|
|
1024
|
+
*
|
|
1025
|
+
* This is the single entry point for backend configuration. The storage
|
|
1026
|
+
* strategy (CFWorkflows vs InMemory) is auto-detected at runtime based on
|
|
1027
|
+
* whether a WorkflowStep context is available.
|
|
1028
|
+
*
|
|
1029
|
+
* ## Configuration Flow
|
|
1030
|
+
*
|
|
1031
|
+
* 1. Set `storage` for durable step persistence
|
|
1032
|
+
* 2. Set `state` for DurableObject state access (required for waitForEvent)
|
|
1033
|
+
* 3. Set `namespace` for workflow isolation
|
|
1034
|
+
*
|
|
1035
|
+
* ## Storage Strategy Selection (Automatic)
|
|
1036
|
+
*
|
|
1037
|
+
* - If WorkflowStep context is available: Uses CFWorkflowsStorageStrategy
|
|
1038
|
+
* - sleep() uses step.sleep() - FREE (no wall-clock billing)
|
|
1039
|
+
* - Activities use step.do() - DURABLE (survives restarts)
|
|
1040
|
+
*
|
|
1041
|
+
* - Otherwise: Uses InMemoryStorageStrategy (fallback)
|
|
1042
|
+
* - sleep() uses setTimeout - BILLABLE (consumes DO time)
|
|
1043
|
+
* - Activities execute directly - NOT DURABLE
|
|
1044
|
+
*
|
|
1045
|
+
* @example
|
|
1046
|
+
* ```typescript
|
|
1047
|
+
* import { configure } from '@dotdo/temporal'
|
|
1048
|
+
*
|
|
1049
|
+
* // In a Durable Object
|
|
1050
|
+
* configure({
|
|
1051
|
+
* storage: new DOStepStorage(ctx.storage),
|
|
1052
|
+
* state: ctx.state,
|
|
1053
|
+
* namespace: 'production'
|
|
1054
|
+
* })
|
|
1055
|
+
* ```
|
|
1056
|
+
*/
|
|
1057
|
+
export function configure(opts) {
|
|
1058
|
+
if (opts.storage)
|
|
1059
|
+
globalStorage = opts.storage;
|
|
1060
|
+
if (opts.state) {
|
|
1061
|
+
globalState = opts.state;
|
|
1062
|
+
globalWaitManager = new WaitForEventManager(opts.state);
|
|
1063
|
+
}
|
|
1064
|
+
if (opts.namespace)
|
|
1065
|
+
globalNamespace = opts.namespace;
|
|
1066
|
+
}
|
|
1067
|
+
// ============================================================================
|
|
1068
|
+
// UTILITIES
|
|
1069
|
+
// Note: parseDuration and ensureError are imported from '../utils'
|
|
1070
|
+
// ============================================================================
|
|
1071
|
+
function generateWorkflowId() {
|
|
1072
|
+
return `wf_${crypto.randomUUID().replace(/-/g, '')}`;
|
|
1073
|
+
}
|
|
1074
|
+
function generateRunId() {
|
|
1075
|
+
return `run_${crypto.randomUUID().replace(/-/g, '')}`;
|
|
1076
|
+
}
|
|
1077
|
+
function generateTimerId() {
|
|
1078
|
+
return `timer_${crypto.randomUUID().replace(/-/g, '')}`;
|
|
1079
|
+
}
|
|
1080
|
+
// ============================================================================
|
|
1081
|
+
// SIGNAL, QUERY, UPDATE DEFINITIONS
|
|
1082
|
+
// ============================================================================
|
|
1083
|
+
/**
|
|
1084
|
+
* Define a signal
|
|
1085
|
+
*/
|
|
1086
|
+
export function defineSignal(name) {
|
|
1087
|
+
return { name, type: 'signal' };
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Define a query
|
|
1091
|
+
*/
|
|
1092
|
+
export function defineQuery(name) {
|
|
1093
|
+
return { name, type: 'query' };
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Define an update
|
|
1097
|
+
*/
|
|
1098
|
+
export function defineUpdate(name) {
|
|
1099
|
+
return { name, type: 'update' };
|
|
1100
|
+
}
|
|
1101
|
+
export function setHandler(definition, handler) {
|
|
1102
|
+
const workflow = getCurrentWorkflow();
|
|
1103
|
+
if (!workflow) {
|
|
1104
|
+
throw new Error('setHandler can only be called within a workflow');
|
|
1105
|
+
}
|
|
1106
|
+
if (definition.type === 'signal') {
|
|
1107
|
+
workflow.signalHandlers.set(definition.name, handler);
|
|
1108
|
+
}
|
|
1109
|
+
else if (definition.type === 'query') {
|
|
1110
|
+
workflow.queryHandlers.set(definition.name, handler);
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
workflow.updateHandlers.set(definition.name, handler);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
// ============================================================================
|
|
1117
|
+
// WORKFLOW INFO - Enhanced with full context
|
|
1118
|
+
// ============================================================================
|
|
1119
|
+
/**
|
|
1120
|
+
* Get current workflow info
|
|
1121
|
+
*/
|
|
1122
|
+
export function workflowInfo() {
|
|
1123
|
+
const workflow = getCurrentWorkflow();
|
|
1124
|
+
if (!workflow) {
|
|
1125
|
+
throw new Error('workflowInfo can only be called within a workflow');
|
|
1126
|
+
}
|
|
1127
|
+
return {
|
|
1128
|
+
workflowId: workflow.workflowId,
|
|
1129
|
+
runId: workflow.runId,
|
|
1130
|
+
workflowType: workflow.workflowType,
|
|
1131
|
+
taskQueue: workflow.taskQueue,
|
|
1132
|
+
namespace: workflow.namespace,
|
|
1133
|
+
firstExecutionRunId: workflow.runId,
|
|
1134
|
+
attempt: workflow.attempt,
|
|
1135
|
+
historyLength: workflow.historyLength,
|
|
1136
|
+
startTime: workflow.startTime,
|
|
1137
|
+
runStartTime: workflow.runStartTime,
|
|
1138
|
+
memo: workflow.memo,
|
|
1139
|
+
searchAttributes: workflow.searchAttributes,
|
|
1140
|
+
parent: workflow.parent,
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
// ============================================================================
|
|
1144
|
+
// SEARCH ATTRIBUTES - Full implementation
|
|
1145
|
+
// ============================================================================
|
|
1146
|
+
/**
|
|
1147
|
+
* Set search attributes (replaces all)
|
|
1148
|
+
*/
|
|
1149
|
+
export function setSearchAttributes(attrs) {
|
|
1150
|
+
const workflow = getCurrentWorkflow();
|
|
1151
|
+
if (!workflow) {
|
|
1152
|
+
throw new Error('setSearchAttributes can only be called within a workflow');
|
|
1153
|
+
}
|
|
1154
|
+
workflow.searchAttributes = { ...attrs };
|
|
1155
|
+
workflow.historyLength++;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Upsert (merge) search attributes
|
|
1159
|
+
*/
|
|
1160
|
+
export function upsertSearchAttributes(attrs) {
|
|
1161
|
+
const workflow = getCurrentWorkflow();
|
|
1162
|
+
if (!workflow) {
|
|
1163
|
+
throw new Error('upsertSearchAttributes can only be called within a workflow');
|
|
1164
|
+
}
|
|
1165
|
+
workflow.searchAttributes = {
|
|
1166
|
+
...workflow.searchAttributes,
|
|
1167
|
+
...attrs,
|
|
1168
|
+
};
|
|
1169
|
+
workflow.historyLength++;
|
|
1170
|
+
}
|
|
1171
|
+
// ============================================================================
|
|
1172
|
+
// TIMERS - Full implementation with coalescing optimization
|
|
1173
|
+
// ============================================================================
|
|
1174
|
+
// Timer coalescing: Group timers that fire within the same 10ms window
|
|
1175
|
+
const TIMER_COALESCE_WINDOW_MS = 10;
|
|
1176
|
+
const coalescedTimerBuckets = new Map();
|
|
1177
|
+
// Store bucket timeouts separately to avoid "cancelled leader" bug
|
|
1178
|
+
// When the first timer in a bucket is cancelled, other timers still need the timeout
|
|
1179
|
+
const bucketTimeouts = new Map();
|
|
1180
|
+
// Periodic cleanup for stale timer buckets (memory leak prevention)
|
|
1181
|
+
// Timers are considered stale if they haven't fired 5 minutes past their expected time
|
|
1182
|
+
const TIMER_STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
|
|
1183
|
+
const TIMER_CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // Run cleanup every 5 minutes
|
|
1184
|
+
let timerCleanupIntervalId = null;
|
|
1185
|
+
/**
|
|
1186
|
+
* Clean up stale timer buckets that haven't fired.
|
|
1187
|
+
* This handles cases where:
|
|
1188
|
+
* - Workflows terminate without cancelling their timers
|
|
1189
|
+
* - setTimeout fails to fire for some reason
|
|
1190
|
+
* - Timers are orphaned due to errors
|
|
1191
|
+
*/
|
|
1192
|
+
function cleanupStaleTimerBuckets() {
|
|
1193
|
+
const now = Date.now();
|
|
1194
|
+
const bucketsToDelete = [];
|
|
1195
|
+
for (const [bucket, timers] of Array.from(coalescedTimerBuckets.entries())) {
|
|
1196
|
+
// Filter out stale timers from this bucket
|
|
1197
|
+
const activeTimersInBucket = timers.filter((timer) => {
|
|
1198
|
+
// Timer is stale if it's past its expected fire time + threshold
|
|
1199
|
+
const isStale = timer.pending && now > timer.expectedFireAt + TIMER_STALE_THRESHOLD_MS;
|
|
1200
|
+
if (isStale) {
|
|
1201
|
+
// Clean up the stale timer
|
|
1202
|
+
timer.pending = false;
|
|
1203
|
+
activeTimers.delete(timer.id);
|
|
1204
|
+
}
|
|
1205
|
+
return !isStale;
|
|
1206
|
+
});
|
|
1207
|
+
if (activeTimersInBucket.length === 0) {
|
|
1208
|
+
bucketsToDelete.push(bucket);
|
|
1209
|
+
}
|
|
1210
|
+
else if (activeTimersInBucket.length !== timers.length) {
|
|
1211
|
+
// Update the bucket with only active timers
|
|
1212
|
+
coalescedTimerBuckets.set(bucket, activeTimersInBucket);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
// Delete empty buckets and their timeouts
|
|
1216
|
+
for (const bucket of bucketsToDelete) {
|
|
1217
|
+
coalescedTimerBuckets.delete(bucket);
|
|
1218
|
+
const timeoutId = bucketTimeouts.get(bucket);
|
|
1219
|
+
if (timeoutId) {
|
|
1220
|
+
clearTimeout(timeoutId);
|
|
1221
|
+
bucketTimeouts.delete(bucket);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Start the periodic timer cleanup (for production use).
|
|
1227
|
+
* This should be called once when the module is loaded in a long-running process.
|
|
1228
|
+
*/
|
|
1229
|
+
export function __startTimerCleanup() {
|
|
1230
|
+
if (timerCleanupIntervalId === null) {
|
|
1231
|
+
timerCleanupIntervalId = setInterval(cleanupStaleTimerBuckets, TIMER_CLEANUP_INTERVAL_MS);
|
|
1232
|
+
// Unref the interval so it doesn't prevent process exit
|
|
1233
|
+
if (typeof timerCleanupIntervalId === 'object' && 'unref' in timerCleanupIntervalId) {
|
|
1234
|
+
timerCleanupIntervalId.unref();
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Stop the periodic timer cleanup.
|
|
1240
|
+
*/
|
|
1241
|
+
export function __stopTimerCleanup() {
|
|
1242
|
+
if (timerCleanupIntervalId !== null) {
|
|
1243
|
+
clearInterval(timerCleanupIntervalId);
|
|
1244
|
+
timerCleanupIntervalId = null;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Create a cancellable timer with optional coalescing
|
|
1249
|
+
*
|
|
1250
|
+
* OPTIMIZATION: Timers firing within 10ms of each other are coalesced
|
|
1251
|
+
* into a single setTimeout call, reducing system call overhead.
|
|
1252
|
+
*/
|
|
1253
|
+
export function createTimer(duration) {
|
|
1254
|
+
// Auto-start cleanup on first timer creation
|
|
1255
|
+
ensureCleanupStarted();
|
|
1256
|
+
const ms = parseDuration(duration);
|
|
1257
|
+
const id = generateTimerId();
|
|
1258
|
+
const now = Date.now();
|
|
1259
|
+
let resolveTimer;
|
|
1260
|
+
let rejectTimer;
|
|
1261
|
+
const promise = new Promise((resolve, reject) => {
|
|
1262
|
+
resolveTimer = resolve;
|
|
1263
|
+
rejectTimer = reject;
|
|
1264
|
+
});
|
|
1265
|
+
const timerState = {
|
|
1266
|
+
id,
|
|
1267
|
+
pending: true,
|
|
1268
|
+
resolve: resolveTimer,
|
|
1269
|
+
reject: rejectTimer,
|
|
1270
|
+
createdAt: now,
|
|
1271
|
+
expectedFireAt: now + ms,
|
|
1272
|
+
};
|
|
1273
|
+
activeTimers.set(id, timerState);
|
|
1274
|
+
// Calculate coalesce bucket (round to nearest TIMER_COALESCE_WINDOW_MS)
|
|
1275
|
+
const bucket = Math.floor(ms / TIMER_COALESCE_WINDOW_MS) * TIMER_COALESCE_WINDOW_MS;
|
|
1276
|
+
// Check if we can coalesce with an existing timer
|
|
1277
|
+
const existingBucket = coalescedTimerBuckets.get(bucket);
|
|
1278
|
+
if (existingBucket && existingBucket.length > 0) {
|
|
1279
|
+
// Coalesce: add to existing bucket
|
|
1280
|
+
existingBucket.push(timerState);
|
|
1281
|
+
}
|
|
1282
|
+
else {
|
|
1283
|
+
// Create new bucket with single timer
|
|
1284
|
+
const newBucket = [timerState];
|
|
1285
|
+
coalescedTimerBuckets.set(bucket, newBucket);
|
|
1286
|
+
// Set the actual timeout - stored on bucket, not individual timer
|
|
1287
|
+
// This prevents the "cancelled leader" bug where cancelling the first timer
|
|
1288
|
+
// would leave other timers in the bucket without a scheduled callback
|
|
1289
|
+
const timeoutId = setTimeout(() => {
|
|
1290
|
+
bucketTimeouts.delete(bucket);
|
|
1291
|
+
// Fire all timers in this bucket
|
|
1292
|
+
const timersToFire = coalescedTimerBuckets.get(bucket) || [];
|
|
1293
|
+
coalescedTimerBuckets.delete(bucket);
|
|
1294
|
+
for (const timer of timersToFire) {
|
|
1295
|
+
if (timer.pending) {
|
|
1296
|
+
timer.pending = false;
|
|
1297
|
+
timer.resolve();
|
|
1298
|
+
activeTimers.delete(timer.id);
|
|
1299
|
+
const workflow = getCurrentWorkflow();
|
|
1300
|
+
if (workflow) {
|
|
1301
|
+
workflow.historyLength++;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}, ms);
|
|
1306
|
+
bucketTimeouts.set(bucket, timeoutId);
|
|
1307
|
+
}
|
|
1308
|
+
// Create a TimerHandle with additional properties
|
|
1309
|
+
const handle = promise;
|
|
1310
|
+
Object.defineProperty(handle, 'id', { value: id, writable: false });
|
|
1311
|
+
Object.defineProperty(handle, 'pending', {
|
|
1312
|
+
get: () => timerState.pending,
|
|
1313
|
+
});
|
|
1314
|
+
return handle;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Cancel a timer
|
|
1318
|
+
*
|
|
1319
|
+
* OPTIMIZATION: Also removes from coalesced bucket to prevent
|
|
1320
|
+
* unnecessary processing of cancelled timers.
|
|
1321
|
+
*
|
|
1322
|
+
* FIX: Timeouts are now stored on the bucket (bucketTimeouts), not on individual
|
|
1323
|
+
* timers. This prevents the "cancelled leader" bug where cancelling the first timer
|
|
1324
|
+
* in a bucket would leave other timers without a scheduled callback.
|
|
1325
|
+
* When cancelling, we only clear the bucket timeout if this was the last timer.
|
|
1326
|
+
*/
|
|
1327
|
+
export function cancelTimer(timer) {
|
|
1328
|
+
const timerState = activeTimers.get(timer.id);
|
|
1329
|
+
if (timerState && timerState.pending) {
|
|
1330
|
+
timerState.pending = false;
|
|
1331
|
+
// Remove from coalesced bucket if present
|
|
1332
|
+
for (const [bucket, timers] of Array.from(coalescedTimerBuckets.entries())) {
|
|
1333
|
+
const index = timers.findIndex(t => t.id === timer.id);
|
|
1334
|
+
if (index !== -1) {
|
|
1335
|
+
timers.splice(index, 1);
|
|
1336
|
+
if (timers.length === 0) {
|
|
1337
|
+
// Only clear the bucket timeout when the last timer is cancelled
|
|
1338
|
+
coalescedTimerBuckets.delete(bucket);
|
|
1339
|
+
const timeoutId = bucketTimeouts.get(bucket);
|
|
1340
|
+
if (timeoutId) {
|
|
1341
|
+
clearTimeout(timeoutId);
|
|
1342
|
+
bucketTimeouts.delete(bucket);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
// If other timers remain in the bucket, leave the timeout running
|
|
1346
|
+
// so those timers will fire when the timeout expires
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
timerState.reject(new WaitCancelledError('Timer cancelled'));
|
|
1351
|
+
activeTimers.delete(timer.id);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Clear all internal state (useful for testing)
|
|
1356
|
+
* Note: AsyncLocalStorage context is automatically cleaned up when execution ends
|
|
1357
|
+
*
|
|
1358
|
+
* This function properly cleans up all timer resources to prevent memory leaks:
|
|
1359
|
+
* - Cancels all pending setTimeout calls
|
|
1360
|
+
* - Clears all timer state maps
|
|
1361
|
+
* - Stops the periodic cleanup interval
|
|
1362
|
+
*/
|
|
1363
|
+
export function __clearTemporalState() {
|
|
1364
|
+
workflows.clear();
|
|
1365
|
+
workflowFunctions.clear();
|
|
1366
|
+
// Clear workflow completion tracking (memory leak fix)
|
|
1367
|
+
workflowCompletionTimes.clear();
|
|
1368
|
+
// Clear task queue registry via activityRouter
|
|
1369
|
+
activityRouter.clear();
|
|
1370
|
+
// Properly cancel all active timers to prevent memory leaks
|
|
1371
|
+
for (const timer of Array.from(activeTimers.values())) {
|
|
1372
|
+
timer.pending = false;
|
|
1373
|
+
}
|
|
1374
|
+
activeTimers.clear();
|
|
1375
|
+
// Clear bucket timeouts (timeouts are stored on buckets, not individual timers)
|
|
1376
|
+
for (const timeoutId of Array.from(bucketTimeouts.values())) {
|
|
1377
|
+
clearTimeout(timeoutId);
|
|
1378
|
+
}
|
|
1379
|
+
bucketTimeouts.clear();
|
|
1380
|
+
// Clear coalesced timer buckets
|
|
1381
|
+
for (const timers of Array.from(coalescedTimerBuckets.values())) {
|
|
1382
|
+
for (const timer of timers) {
|
|
1383
|
+
timer.pending = false;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
coalescedTimerBuckets.clear();
|
|
1387
|
+
// Stop the periodic cleanup intervals
|
|
1388
|
+
__stopTimerCleanup();
|
|
1389
|
+
__stopWorkflowCleanup();
|
|
1390
|
+
// Clear determinism tracking
|
|
1391
|
+
clearDeterminismWarnings();
|
|
1392
|
+
disableDeterminismDetection();
|
|
1393
|
+
// Note: Step counters are now per-workflow (in WorkflowState), not global.
|
|
1394
|
+
// They are automatically reset when workflows are created.
|
|
1395
|
+
// Clear WorkflowStep context
|
|
1396
|
+
clearWorkflowStep();
|
|
1397
|
+
globalNamespace = 'default';
|
|
1398
|
+
}
|
|
1399
|
+
// ============================================================================
|
|
1400
|
+
// VERSIONING / PATCHING - Full implementation
|
|
1401
|
+
// ============================================================================
|
|
1402
|
+
/**
|
|
1403
|
+
* Check if a patch should be applied
|
|
1404
|
+
* For new executions, this always returns true (take the new path)
|
|
1405
|
+
* For replays of old executions, this returns false to maintain compatibility
|
|
1406
|
+
*/
|
|
1407
|
+
export function patched(patchId) {
|
|
1408
|
+
let patchState = getCurrentPatchState();
|
|
1409
|
+
if (!patchState) {
|
|
1410
|
+
patchState = {
|
|
1411
|
+
appliedPatches: new Set(),
|
|
1412
|
+
deprecatedPatches: new Set(),
|
|
1413
|
+
};
|
|
1414
|
+
setCurrentPatchState(patchState);
|
|
1415
|
+
}
|
|
1416
|
+
// For new executions, always apply patches
|
|
1417
|
+
// In a full implementation, this would check workflow history
|
|
1418
|
+
patchState.appliedPatches.add(patchId);
|
|
1419
|
+
const workflow = getCurrentWorkflow();
|
|
1420
|
+
if (workflow) {
|
|
1421
|
+
workflow.historyLength++;
|
|
1422
|
+
}
|
|
1423
|
+
return true;
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Deprecate an old patch (removes it from consideration in new workflow code)
|
|
1427
|
+
*/
|
|
1428
|
+
export function deprecatePatch(patchId) {
|
|
1429
|
+
let patchState = getCurrentPatchState();
|
|
1430
|
+
if (!patchState) {
|
|
1431
|
+
patchState = {
|
|
1432
|
+
appliedPatches: new Set(),
|
|
1433
|
+
deprecatedPatches: new Set(),
|
|
1434
|
+
};
|
|
1435
|
+
setCurrentPatchState(patchState);
|
|
1436
|
+
}
|
|
1437
|
+
patchState.deprecatedPatches.add(patchId);
|
|
1438
|
+
}
|
|
1439
|
+
// ============================================================================
|
|
1440
|
+
// SLEEP AND CONDITION
|
|
1441
|
+
// ============================================================================
|
|
1442
|
+
/**
|
|
1443
|
+
* Sleep for a duration (durable)
|
|
1444
|
+
*
|
|
1445
|
+
* This implementation integrates with both CF Workflows native sleep and
|
|
1446
|
+
* the Temporal compat layer's durable storage:
|
|
1447
|
+
*
|
|
1448
|
+
* 1. If WorkflowStep context is available (CF Workflows), use step.sleep()
|
|
1449
|
+
* - FREE: doesn't use billable DO time
|
|
1450
|
+
* - DURABLE: survives worker restarts
|
|
1451
|
+
*
|
|
1452
|
+
* 2. Otherwise, fall back to setTimeout with durable storage
|
|
1453
|
+
* - Persists sleep state to storage (in case of crash)
|
|
1454
|
+
* - On replay, completed sleeps are skipped immediately
|
|
1455
|
+
*/
|
|
1456
|
+
export async function sleep(duration) {
|
|
1457
|
+
const ctx = getCurrentContext();
|
|
1458
|
+
const workflow = ctx?.workflow ?? null;
|
|
1459
|
+
if (!workflow) {
|
|
1460
|
+
throw new Error('sleep can only be called within a workflow');
|
|
1461
|
+
}
|
|
1462
|
+
const ms = parseDuration(duration);
|
|
1463
|
+
const durationStr = typeof duration === 'string' ? duration : formatDurationForCF(ms);
|
|
1464
|
+
const stepId = generateSleepStepId(ms);
|
|
1465
|
+
// Use the unified storage strategy pattern
|
|
1466
|
+
// - CFWorkflowsStorageStrategy: Uses step.sleep() - FREE, doesn't consume wall-clock time
|
|
1467
|
+
// - InMemoryStorageStrategy: Uses setTimeout with durable storage - BILLABLE
|
|
1468
|
+
const strategy = getCurrentStorageStrategy();
|
|
1469
|
+
await strategy.sleep(stepId, ms, durationStr);
|
|
1470
|
+
// Update in-memory cache for replay within same execution
|
|
1471
|
+
workflow.stepResults.set(stepId, true);
|
|
1472
|
+
workflow.historyLength++;
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Wait for a condition to be true
|
|
1476
|
+
*/
|
|
1477
|
+
export async function condition(fn, timeout) {
|
|
1478
|
+
const workflow = getCurrentWorkflow();
|
|
1479
|
+
if (!workflow) {
|
|
1480
|
+
throw new Error('condition can only be called within a workflow');
|
|
1481
|
+
}
|
|
1482
|
+
const timeoutMs = timeout ? parseDuration(timeout) : undefined;
|
|
1483
|
+
const startTime = Date.now();
|
|
1484
|
+
while (true) {
|
|
1485
|
+
try {
|
|
1486
|
+
if (fn()) {
|
|
1487
|
+
break;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
catch (error) {
|
|
1491
|
+
// Log the error for debugging but let it propagate
|
|
1492
|
+
const err = ensureError(error);
|
|
1493
|
+
console.error(`[condition] Error in condition function: ${err.message}`);
|
|
1494
|
+
throw err;
|
|
1495
|
+
}
|
|
1496
|
+
// Check timeout
|
|
1497
|
+
if (timeoutMs && Date.now() - startTime >= timeoutMs) {
|
|
1498
|
+
return false;
|
|
1499
|
+
}
|
|
1500
|
+
// Poll every 100ms
|
|
1501
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1502
|
+
}
|
|
1503
|
+
return true;
|
|
1504
|
+
}
|
|
1505
|
+
// Note: ActivityTimeoutError and TaskQueueNotRegisteredError are imported from '../activity-router'
|
|
1506
|
+
// and re-exported at the top of this file for backward compatibility.
|
|
1507
|
+
//
|
|
1508
|
+
// Activity execution (retry, timeout, error handling) is now handled by the shared
|
|
1509
|
+
// WorkerActivityRouter instance, eliminating duplicate code.
|
|
1510
|
+
/**
|
|
1511
|
+
* Create activity proxies
|
|
1512
|
+
*
|
|
1513
|
+
* Activities integrate with multiple execution backends:
|
|
1514
|
+
*
|
|
1515
|
+
* 1. If a worker with executeActivity handler is registered, route to that handler
|
|
1516
|
+
* - Enables testing workflows with mock activity implementations
|
|
1517
|
+
* - Handles timeouts, retries, and error classification
|
|
1518
|
+
*
|
|
1519
|
+
* 2. If WorkflowStep context is available (CF Workflows), use step.do()
|
|
1520
|
+
* - DURABLE: automatically retries and survives restarts
|
|
1521
|
+
* - REPLAY: completed steps return cached results
|
|
1522
|
+
*
|
|
1523
|
+
* 3. Otherwise, fall back to DurableWorkflowRuntime
|
|
1524
|
+
*
|
|
1525
|
+
* Activities can specify a `taskQueue` option to route execution to a specific
|
|
1526
|
+
* worker. If a task queue is specified, it must have a registered worker.
|
|
1527
|
+
* If no task queue is specified, activities use the workflow's task queue.
|
|
1528
|
+
*/
|
|
1529
|
+
export function proxyActivities(options) {
|
|
1530
|
+
// Activity task queue (can be different from workflow's task queue)
|
|
1531
|
+
const activityTaskQueue = options.taskQueue;
|
|
1532
|
+
// Parse timeouts once
|
|
1533
|
+
const startToCloseTimeoutMs = options.startToCloseTimeout
|
|
1534
|
+
? parseDuration(options.startToCloseTimeout)
|
|
1535
|
+
: undefined;
|
|
1536
|
+
const heartbeatTimeoutMs = options.heartbeatTimeout
|
|
1537
|
+
? parseDuration(options.heartbeatTimeout)
|
|
1538
|
+
: undefined;
|
|
1539
|
+
// Build CF Workflows step.do() options from Temporal activity options
|
|
1540
|
+
const buildStepDoOptions = () => {
|
|
1541
|
+
const stepOptions = {};
|
|
1542
|
+
// Map retry policy
|
|
1543
|
+
if (options.retry?.maximumAttempts) {
|
|
1544
|
+
stepOptions.retries = {
|
|
1545
|
+
limit: options.retry.maximumAttempts,
|
|
1546
|
+
backoff: options.retry.backoffCoefficient && options.retry.backoffCoefficient > 1 ? 'exponential' : 'constant',
|
|
1547
|
+
delay: options.retry.initialInterval ? String(options.retry.initialInterval) : undefined,
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
// Map timeout
|
|
1551
|
+
if (options.startToCloseTimeout) {
|
|
1552
|
+
stepOptions.timeout = typeof options.startToCloseTimeout === 'string'
|
|
1553
|
+
? options.startToCloseTimeout
|
|
1554
|
+
: formatDurationForCF(parseDuration(options.startToCloseTimeout));
|
|
1555
|
+
}
|
|
1556
|
+
// Only return options if we have something to configure
|
|
1557
|
+
if (stepOptions.retries || stepOptions.timeout) {
|
|
1558
|
+
return stepOptions;
|
|
1559
|
+
}
|
|
1560
|
+
return undefined;
|
|
1561
|
+
};
|
|
1562
|
+
return new Proxy({}, {
|
|
1563
|
+
get(_, name) {
|
|
1564
|
+
return async (...args) => {
|
|
1565
|
+
const workflow = getCurrentWorkflow();
|
|
1566
|
+
if (!workflow) {
|
|
1567
|
+
throw new Error('Activities can only be called within a workflow');
|
|
1568
|
+
}
|
|
1569
|
+
// Determine which task queue to use for this activity
|
|
1570
|
+
const targetTaskQueue = activityTaskQueue ?? workflow.taskQueue;
|
|
1571
|
+
// Validate the task queue has a registered worker
|
|
1572
|
+
validateTaskQueueForActivity(targetTaskQueue, name);
|
|
1573
|
+
// Include task queue in step ID to ensure isolation between queues
|
|
1574
|
+
const stepId = `activity:${targetTaskQueue}:${name}:${JSON.stringify(args)}`;
|
|
1575
|
+
// Check for replay in workflow stepResults (handles both success and error)
|
|
1576
|
+
if (workflow.stepResults.has(stepId)) {
|
|
1577
|
+
const cached = workflow.stepResults.get(stepId);
|
|
1578
|
+
// If cached value is an error, re-throw it
|
|
1579
|
+
if (cached instanceof Error) {
|
|
1580
|
+
throw cached;
|
|
1581
|
+
}
|
|
1582
|
+
return cached;
|
|
1583
|
+
}
|
|
1584
|
+
// Check for CF Workflows step context - use step.do() for durability
|
|
1585
|
+
const step = getWorkflowStep();
|
|
1586
|
+
if (step) {
|
|
1587
|
+
// Use CF Workflows native step.do() - DURABLE
|
|
1588
|
+
// Use per-workflow counter for deterministic IDs across concurrent workflows
|
|
1589
|
+
workflow.activityStepCounter++;
|
|
1590
|
+
const stepName = `activity:${name}:${workflow.activityStepCounter}`;
|
|
1591
|
+
const stepDoOptions = buildStepDoOptions();
|
|
1592
|
+
// The callback is what CF Workflows will execute.
|
|
1593
|
+
// When a worker handler is registered, invoke it for actual activity execution.
|
|
1594
|
+
// Otherwise, return a stub for CF Workflows runtime (production mode).
|
|
1595
|
+
const callback = async () => {
|
|
1596
|
+
// Check if worker has a handler for this activity
|
|
1597
|
+
const worker = activityRouter.getWorker(targetTaskQueue);
|
|
1598
|
+
if (worker?.executeActivity) {
|
|
1599
|
+
// Create activity context with cancellation signal
|
|
1600
|
+
const activityContext = {
|
|
1601
|
+
signal: workflow.abortController?.signal,
|
|
1602
|
+
};
|
|
1603
|
+
return worker.executeActivity(name, args, activityContext);
|
|
1604
|
+
}
|
|
1605
|
+
// Fallback for CF Workflows runtime (no local handler)
|
|
1606
|
+
return { _activity: name, _args: args, _stub: true };
|
|
1607
|
+
};
|
|
1608
|
+
// Call step.do() with or without options
|
|
1609
|
+
const result = stepDoOptions
|
|
1610
|
+
? await step.do(stepName, stepDoOptions, callback)
|
|
1611
|
+
: await step.do(stepName, callback);
|
|
1612
|
+
workflow.stepResults.set(stepId, result);
|
|
1613
|
+
workflow.historyLength++;
|
|
1614
|
+
return result;
|
|
1615
|
+
}
|
|
1616
|
+
// Fallback when no WorkflowStep is available:
|
|
1617
|
+
// Get the worker for this task queue via activityRouter
|
|
1618
|
+
const worker = activityRouter.getWorker(targetTaskQueue);
|
|
1619
|
+
// If worker has executeActivity handler, route via activityRouter (no step.do() durability)
|
|
1620
|
+
if (worker?.executeActivity) {
|
|
1621
|
+
// Create activity context with cancellation signal
|
|
1622
|
+
const activityContext = {
|
|
1623
|
+
signal: workflow.abortController?.signal,
|
|
1624
|
+
};
|
|
1625
|
+
// Build ActivityRouterOptions from Temporal ActivityOptions
|
|
1626
|
+
// Use heartbeat timeout if specified and shorter than start-to-close timeout
|
|
1627
|
+
// Heartbeat timeout in Temporal means the activity must heartbeat within this interval
|
|
1628
|
+
// In our emulation, we use it as an effective timeout for activities that don't heartbeat
|
|
1629
|
+
let effectiveTimeout = startToCloseTimeoutMs;
|
|
1630
|
+
if (heartbeatTimeoutMs) {
|
|
1631
|
+
if (!effectiveTimeout || heartbeatTimeoutMs < effectiveTimeout) {
|
|
1632
|
+
effectiveTimeout = heartbeatTimeoutMs;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
const routerOptions = {
|
|
1636
|
+
taskQueue: targetTaskQueue,
|
|
1637
|
+
timeout: effectiveTimeout,
|
|
1638
|
+
retries: options.retry ? {
|
|
1639
|
+
maximumAttempts: options.retry.maximumAttempts,
|
|
1640
|
+
initialInterval: options.retry.initialInterval,
|
|
1641
|
+
backoffCoefficient: options.retry.backoffCoefficient,
|
|
1642
|
+
maximumInterval: options.retry.maximumInterval,
|
|
1643
|
+
nonRetryableErrors: options.retry.nonRetryableErrorTypes ? [...options.retry.nonRetryableErrorTypes] : undefined,
|
|
1644
|
+
} : undefined,
|
|
1645
|
+
};
|
|
1646
|
+
try {
|
|
1647
|
+
// Route activity via the shared ActivityRouter - handles timeouts and retries
|
|
1648
|
+
const result = await activityRouter.route(name, args, routerOptions, activityContext);
|
|
1649
|
+
// Cache successful result
|
|
1650
|
+
workflow.stepResults.set(stepId, result);
|
|
1651
|
+
workflow.historyLength++;
|
|
1652
|
+
return result;
|
|
1653
|
+
}
|
|
1654
|
+
catch (error) {
|
|
1655
|
+
// Cache error for replay (determinism)
|
|
1656
|
+
const err = ensureError(error);
|
|
1657
|
+
workflow.stepResults.set(stepId, err);
|
|
1658
|
+
workflow.historyLength++;
|
|
1659
|
+
throw err;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
// Fallback: Execute through DurableWorkflowRuntime
|
|
1663
|
+
// Uses getCurrentStorage() for consistent storage access
|
|
1664
|
+
const runtime = new DurableWorkflowRuntime({
|
|
1665
|
+
storage: getCurrentStorage(),
|
|
1666
|
+
retryPolicy: options.retry
|
|
1667
|
+
? {
|
|
1668
|
+
maxAttempts: options.retry.maximumAttempts ?? 3,
|
|
1669
|
+
initialDelayMs: options.retry.initialInterval ? parseDuration(options.retry.initialInterval) : 1000,
|
|
1670
|
+
maxDelayMs: options.retry.maximumInterval ? parseDuration(options.retry.maximumInterval) : 30000,
|
|
1671
|
+
backoffMultiplier: options.retry.backoffCoefficient ?? 2,
|
|
1672
|
+
jitter: true,
|
|
1673
|
+
}
|
|
1674
|
+
: undefined,
|
|
1675
|
+
});
|
|
1676
|
+
const result = await runtime.executeStep(stepId, {
|
|
1677
|
+
path: ['Activity', name],
|
|
1678
|
+
context: { args, taskQueue: targetTaskQueue },
|
|
1679
|
+
contextHash: stepId,
|
|
1680
|
+
runtime,
|
|
1681
|
+
}, args, 'do');
|
|
1682
|
+
workflow.stepResults.set(stepId, result);
|
|
1683
|
+
workflow.historyLength++;
|
|
1684
|
+
return result;
|
|
1685
|
+
};
|
|
1686
|
+
},
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Create local activity proxies
|
|
1691
|
+
*/
|
|
1692
|
+
export function proxyLocalActivities(options) {
|
|
1693
|
+
// Local activities run in the same process with shorter timeouts
|
|
1694
|
+
return proxyActivities(options);
|
|
1695
|
+
}
|
|
1696
|
+
// ============================================================================
|
|
1697
|
+
// CHILD WORKFLOWS - Enhanced implementation
|
|
1698
|
+
// ============================================================================
|
|
1699
|
+
/**
|
|
1700
|
+
* Start a child workflow
|
|
1701
|
+
*
|
|
1702
|
+
* @throws TaskQueueNotRegisteredError if no worker is registered for the task queue
|
|
1703
|
+
*/
|
|
1704
|
+
export async function startChild(workflowType, options) {
|
|
1705
|
+
// Auto-start cleanup on first workflow creation
|
|
1706
|
+
ensureCleanupStarted();
|
|
1707
|
+
const typeName = typeof workflowType === 'string' ? workflowType : workflowType.name;
|
|
1708
|
+
const workflowId = options.workflowId ?? generateWorkflowId();
|
|
1709
|
+
const runId = generateRunId();
|
|
1710
|
+
const now = new Date();
|
|
1711
|
+
const parentWorkflow = getCurrentWorkflow();
|
|
1712
|
+
// Determine the task queue for the child workflow
|
|
1713
|
+
const childTaskQueue = options.taskQueue ?? parentWorkflow?.taskQueue ?? 'default';
|
|
1714
|
+
// Validate task queue has a registered worker (like real Temporal)
|
|
1715
|
+
validateTaskQueueForWorkflow(childTaskQueue, typeName);
|
|
1716
|
+
// Get parent info if executing within a workflow
|
|
1717
|
+
const parentInfo = parentWorkflow
|
|
1718
|
+
? {
|
|
1719
|
+
workflowId: parentWorkflow.workflowId,
|
|
1720
|
+
runId: parentWorkflow.runId,
|
|
1721
|
+
namespace: parentWorkflow.namespace,
|
|
1722
|
+
}
|
|
1723
|
+
: undefined;
|
|
1724
|
+
// Create child workflow state
|
|
1725
|
+
const childState = {
|
|
1726
|
+
workflowId,
|
|
1727
|
+
runId,
|
|
1728
|
+
workflowType: typeName,
|
|
1729
|
+
taskQueue: childTaskQueue,
|
|
1730
|
+
namespace: parentWorkflow?.namespace ?? globalNamespace,
|
|
1731
|
+
signalHandlers: new Map(),
|
|
1732
|
+
queryHandlers: new Map(),
|
|
1733
|
+
updateHandlers: new Map(),
|
|
1734
|
+
stepResults: new Map(),
|
|
1735
|
+
status: 'RUNNING',
|
|
1736
|
+
searchAttributes: options.searchAttributes ?? {},
|
|
1737
|
+
memo: options.memo,
|
|
1738
|
+
parent: parentInfo,
|
|
1739
|
+
startTime: now,
|
|
1740
|
+
runStartTime: now,
|
|
1741
|
+
historyLength: 1,
|
|
1742
|
+
attempt: 1,
|
|
1743
|
+
children: new Set(),
|
|
1744
|
+
parentClosePolicy: options.parentClosePolicy,
|
|
1745
|
+
abortController: new AbortController(),
|
|
1746
|
+
sleepStepCounter: 0,
|
|
1747
|
+
activityStepCounter: 0,
|
|
1748
|
+
};
|
|
1749
|
+
workflows.set(workflowId, childState);
|
|
1750
|
+
// Track child in parent
|
|
1751
|
+
if (parentWorkflow) {
|
|
1752
|
+
parentWorkflow.children.add(workflowId);
|
|
1753
|
+
parentWorkflow.historyLength++;
|
|
1754
|
+
}
|
|
1755
|
+
// Execute in background with its own context
|
|
1756
|
+
const workflowFn = typeof workflowType === 'function' ? workflowType : workflowFunctions.get(typeName);
|
|
1757
|
+
if (workflowFn) {
|
|
1758
|
+
// Create a new context for the child workflow
|
|
1759
|
+
// Inherits storage from parent context or uses global fallback
|
|
1760
|
+
const childContext = {
|
|
1761
|
+
workflow: childState,
|
|
1762
|
+
patchState: null,
|
|
1763
|
+
storage: getCurrentStorage(),
|
|
1764
|
+
waitManager: globalWaitManager,
|
|
1765
|
+
workflowStep: null,
|
|
1766
|
+
};
|
|
1767
|
+
// Run the child workflow in its own context
|
|
1768
|
+
runWithContext(childContext, () => {
|
|
1769
|
+
workflowFn(...(options.args ?? []))
|
|
1770
|
+
.then((result) => {
|
|
1771
|
+
childState.status = 'COMPLETED';
|
|
1772
|
+
childState.result = result;
|
|
1773
|
+
markWorkflowCompleted(workflowId);
|
|
1774
|
+
})
|
|
1775
|
+
.catch((error) => {
|
|
1776
|
+
childState.status = 'FAILED';
|
|
1777
|
+
childState.error = error;
|
|
1778
|
+
markWorkflowCompleted(workflowId);
|
|
1779
|
+
});
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
return {
|
|
1783
|
+
workflowId,
|
|
1784
|
+
firstExecutionRunId: runId,
|
|
1785
|
+
async result() {
|
|
1786
|
+
// Poll until complete
|
|
1787
|
+
while (childState.status === 'RUNNING') {
|
|
1788
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1789
|
+
}
|
|
1790
|
+
if (childState.status === 'FAILED' && childState.error) {
|
|
1791
|
+
throw childState.error;
|
|
1792
|
+
}
|
|
1793
|
+
if (childState.status === 'CANCELED') {
|
|
1794
|
+
throw new WaitCancelledError('Child workflow was cancelled');
|
|
1795
|
+
}
|
|
1796
|
+
return childState.result;
|
|
1797
|
+
},
|
|
1798
|
+
async signal(signal, ...args) {
|
|
1799
|
+
const handler = childState.signalHandlers.get(signal.name);
|
|
1800
|
+
if (handler) {
|
|
1801
|
+
await handler(...args);
|
|
1802
|
+
}
|
|
1803
|
+
},
|
|
1804
|
+
async cancel() {
|
|
1805
|
+
childState.status = 'CANCELED';
|
|
1806
|
+
markWorkflowCompleted(workflowId);
|
|
1807
|
+
},
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Execute a child workflow and wait for result
|
|
1812
|
+
*/
|
|
1813
|
+
export async function executeChild(workflowType, options) {
|
|
1814
|
+
const handle = await startChild(workflowType, options);
|
|
1815
|
+
return handle.result();
|
|
1816
|
+
}
|
|
1817
|
+
// ============================================================================
|
|
1818
|
+
// CANCELLATION
|
|
1819
|
+
// ============================================================================
|
|
1820
|
+
export class CancellationScope {
|
|
1821
|
+
children = [];
|
|
1822
|
+
cleanupFns = [];
|
|
1823
|
+
_isCancelled = false;
|
|
1824
|
+
get isCancelled() {
|
|
1825
|
+
return this._isCancelled;
|
|
1826
|
+
}
|
|
1827
|
+
cancel() {
|
|
1828
|
+
this._isCancelled = true;
|
|
1829
|
+
for (const child of this.children) {
|
|
1830
|
+
child.cancel();
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Run a function in this cancellation scope
|
|
1835
|
+
*/
|
|
1836
|
+
static async run(fn) {
|
|
1837
|
+
const scope = new CancellationScope();
|
|
1838
|
+
try {
|
|
1839
|
+
return await fn();
|
|
1840
|
+
}
|
|
1841
|
+
finally {
|
|
1842
|
+
for (const cleanup of scope.cleanupFns) {
|
|
1843
|
+
await cleanup();
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Create a non-cancellable scope
|
|
1849
|
+
*/
|
|
1850
|
+
static nonCancellable(fn) {
|
|
1851
|
+
return fn();
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Create a cancellable scope with timeout
|
|
1855
|
+
*/
|
|
1856
|
+
static async cancellable(fn) {
|
|
1857
|
+
return CancellationScope.run(fn);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Check if cancelled
|
|
1862
|
+
*/
|
|
1863
|
+
export function isCancellation(error) {
|
|
1864
|
+
return error instanceof WaitCancelledError;
|
|
1865
|
+
}
|
|
1866
|
+
// ============================================================================
|
|
1867
|
+
// CONTINUE AS NEW
|
|
1868
|
+
// ============================================================================
|
|
1869
|
+
export class ContinueAsNew extends Error {
|
|
1870
|
+
args;
|
|
1871
|
+
options;
|
|
1872
|
+
constructor(args, options = {}) {
|
|
1873
|
+
super('ContinueAsNew');
|
|
1874
|
+
this.name = 'ContinueAsNew';
|
|
1875
|
+
this.args = args;
|
|
1876
|
+
this.options = options;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Continue as new with fresh history
|
|
1881
|
+
*/
|
|
1882
|
+
export function continueAsNew(...args) {
|
|
1883
|
+
throw new ContinueAsNew(args);
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Make continue-as-new function for a specific workflow
|
|
1887
|
+
*/
|
|
1888
|
+
export function makeContinueAsNewFunc(_workflowType, options) {
|
|
1889
|
+
return (...args) => {
|
|
1890
|
+
throw new ContinueAsNew(args, options);
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
// ============================================================================
|
|
1894
|
+
// TEMPORAL ERROR CLASSES - Standard error types for workflow failures
|
|
1895
|
+
// ============================================================================
|
|
1896
|
+
/**
|
|
1897
|
+
* Application-level failure that can be thrown from workflows or activities.
|
|
1898
|
+
* Use this to signal business logic failures vs infrastructure errors.
|
|
1899
|
+
*
|
|
1900
|
+
* @example
|
|
1901
|
+
* ```typescript
|
|
1902
|
+
* import { ApplicationFailure } from '@dotdo/temporal'
|
|
1903
|
+
*
|
|
1904
|
+
* if (!user.hasPermission('admin')) {
|
|
1905
|
+
* throw new ApplicationFailure('User does not have admin permission', true)
|
|
1906
|
+
* }
|
|
1907
|
+
* ```
|
|
1908
|
+
*/
|
|
1909
|
+
export class ApplicationFailure extends Error {
|
|
1910
|
+
nonRetryable;
|
|
1911
|
+
details;
|
|
1912
|
+
type = 'ApplicationFailure';
|
|
1913
|
+
constructor(message, nonRetryable = false, details) {
|
|
1914
|
+
super(message);
|
|
1915
|
+
this.nonRetryable = nonRetryable;
|
|
1916
|
+
this.details = details;
|
|
1917
|
+
this.name = 'ApplicationFailure';
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Create a non-retryable application failure
|
|
1921
|
+
*/
|
|
1922
|
+
static nonRetryable(message, ...details) {
|
|
1923
|
+
return new ApplicationFailure(message, true, details.length > 0 ? details : undefined);
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Create a retryable application failure
|
|
1927
|
+
*/
|
|
1928
|
+
static retryable(message, ...details) {
|
|
1929
|
+
return new ApplicationFailure(message, false, details.length > 0 ? details : undefined);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Failure thrown when an activity execution fails.
|
|
1934
|
+
* Contains information about the failed activity and the underlying cause.
|
|
1935
|
+
*
|
|
1936
|
+
* @example
|
|
1937
|
+
* ```typescript
|
|
1938
|
+
* try {
|
|
1939
|
+
* await activities.processPayment(order)
|
|
1940
|
+
* } catch (error) {
|
|
1941
|
+
* if (error instanceof ActivityFailure) {
|
|
1942
|
+
* console.log(`Activity ${error.activityType} failed: ${error.cause?.message}`)
|
|
1943
|
+
* }
|
|
1944
|
+
* }
|
|
1945
|
+
* ```
|
|
1946
|
+
*/
|
|
1947
|
+
export class ActivityFailure extends Error {
|
|
1948
|
+
activityType;
|
|
1949
|
+
activityId;
|
|
1950
|
+
cause;
|
|
1951
|
+
type = 'ActivityFailure';
|
|
1952
|
+
constructor(activityType, activityId, cause) {
|
|
1953
|
+
super(`Activity ${activityType} (${activityId}) failed${cause ? `: ${cause.message}` : ''}`);
|
|
1954
|
+
this.activityType = activityType;
|
|
1955
|
+
this.activityId = activityId;
|
|
1956
|
+
this.cause = cause;
|
|
1957
|
+
this.name = 'ActivityFailure';
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Failure thrown when a child workflow execution fails.
|
|
1962
|
+
* Contains information about the failed child workflow and the underlying cause.
|
|
1963
|
+
*
|
|
1964
|
+
* @example
|
|
1965
|
+
* ```typescript
|
|
1966
|
+
* try {
|
|
1967
|
+
* await executeChild(childWorkflow, { workflowId: 'child-1' })
|
|
1968
|
+
* } catch (error) {
|
|
1969
|
+
* if (error instanceof ChildWorkflowFailure) {
|
|
1970
|
+
* console.log(`Child workflow ${error.workflowType} failed: ${error.cause?.message}`)
|
|
1971
|
+
* }
|
|
1972
|
+
* }
|
|
1973
|
+
* ```
|
|
1974
|
+
*/
|
|
1975
|
+
export class ChildWorkflowFailure extends Error {
|
|
1976
|
+
workflowType;
|
|
1977
|
+
workflowId;
|
|
1978
|
+
cause;
|
|
1979
|
+
type = 'ChildWorkflowFailure';
|
|
1980
|
+
constructor(workflowType, workflowId, cause) {
|
|
1981
|
+
super(`Child workflow ${workflowType} (${workflowId}) failed${cause ? `: ${cause.message}` : ''}`);
|
|
1982
|
+
this.workflowType = workflowType;
|
|
1983
|
+
this.workflowId = workflowId;
|
|
1984
|
+
this.cause = cause;
|
|
1985
|
+
this.name = 'ChildWorkflowFailure';
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Check if an error is an ApplicationFailure
|
|
1990
|
+
*/
|
|
1991
|
+
export function isApplicationFailure(error) {
|
|
1992
|
+
return error instanceof ApplicationFailure;
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Check if an error is an ActivityFailure
|
|
1996
|
+
*/
|
|
1997
|
+
export function isActivityFailure(error) {
|
|
1998
|
+
return error instanceof ActivityFailure;
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Check if an error is a ChildWorkflowFailure
|
|
2002
|
+
*/
|
|
2003
|
+
export function isChildWorkflowFailure(error) {
|
|
2004
|
+
return error instanceof ChildWorkflowFailure;
|
|
2005
|
+
}
|
|
2006
|
+
// ============================================================================
|
|
2007
|
+
// WORKFLOW CLIENT - Enhanced with list and search
|
|
2008
|
+
// ============================================================================
|
|
2009
|
+
export class WorkflowClient {
|
|
2010
|
+
namespace;
|
|
2011
|
+
storage;
|
|
2012
|
+
constructor(options = {}) {
|
|
2013
|
+
this.namespace = options.namespace ?? globalNamespace;
|
|
2014
|
+
// Use explicit storage if provided, otherwise use current context storage or global fallback
|
|
2015
|
+
this.storage = options.storage ?? getCurrentStorage();
|
|
2016
|
+
// Update global namespace for workflows started by this client
|
|
2017
|
+
globalNamespace = this.namespace;
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Start a workflow
|
|
2021
|
+
*
|
|
2022
|
+
* @throws TaskQueueNotRegisteredError if no worker is registered for the task queue
|
|
2023
|
+
*/
|
|
2024
|
+
async start(workflowType, options) {
|
|
2025
|
+
// Auto-start cleanup on first workflow creation
|
|
2026
|
+
ensureCleanupStarted();
|
|
2027
|
+
const typeName = typeof workflowType === 'string' ? workflowType : workflowType.name;
|
|
2028
|
+
const workflowId = options.workflowId ?? generateWorkflowId();
|
|
2029
|
+
const runId = generateRunId();
|
|
2030
|
+
const now = new Date();
|
|
2031
|
+
// Validate task queue has a registered worker (like real Temporal)
|
|
2032
|
+
validateTaskQueueForWorkflow(options.taskQueue, typeName);
|
|
2033
|
+
// Create workflow state with full context
|
|
2034
|
+
const state = {
|
|
2035
|
+
workflowId,
|
|
2036
|
+
runId,
|
|
2037
|
+
workflowType: typeName,
|
|
2038
|
+
taskQueue: options.taskQueue,
|
|
2039
|
+
namespace: this.namespace,
|
|
2040
|
+
signalHandlers: new Map(),
|
|
2041
|
+
queryHandlers: new Map(),
|
|
2042
|
+
updateHandlers: new Map(),
|
|
2043
|
+
stepResults: new Map(),
|
|
2044
|
+
status: 'RUNNING',
|
|
2045
|
+
searchAttributes: options.searchAttributes ?? {},
|
|
2046
|
+
memo: options.memo,
|
|
2047
|
+
startTime: now,
|
|
2048
|
+
runStartTime: now,
|
|
2049
|
+
historyLength: 1,
|
|
2050
|
+
attempt: 1,
|
|
2051
|
+
children: new Set(),
|
|
2052
|
+
abortController: new AbortController(),
|
|
2053
|
+
sleepStepCounter: 0,
|
|
2054
|
+
activityStepCounter: 0,
|
|
2055
|
+
};
|
|
2056
|
+
workflows.set(workflowId, state);
|
|
2057
|
+
// Execute the workflow with its own context
|
|
2058
|
+
const workflowFn = typeof workflowType === 'function' ? workflowType : workflowFunctions.get(typeName);
|
|
2059
|
+
if (workflowFn) {
|
|
2060
|
+
// Create a new context for this workflow execution
|
|
2061
|
+
const context = {
|
|
2062
|
+
workflow: state,
|
|
2063
|
+
patchState: null,
|
|
2064
|
+
storage: this.storage,
|
|
2065
|
+
waitManager: globalWaitManager,
|
|
2066
|
+
workflowStep: null,
|
|
2067
|
+
};
|
|
2068
|
+
// Run the workflow in its own context (enabling concurrent execution)
|
|
2069
|
+
runWithContext(context, () => {
|
|
2070
|
+
workflowFn(...(options.args ?? []))
|
|
2071
|
+
.then((result) => {
|
|
2072
|
+
state.status = 'COMPLETED';
|
|
2073
|
+
state.result = result;
|
|
2074
|
+
markWorkflowCompleted(workflowId);
|
|
2075
|
+
})
|
|
2076
|
+
.catch((error) => {
|
|
2077
|
+
if (error instanceof ContinueAsNew) {
|
|
2078
|
+
state.status = 'CONTINUED_AS_NEW';
|
|
2079
|
+
}
|
|
2080
|
+
else {
|
|
2081
|
+
state.status = 'FAILED';
|
|
2082
|
+
state.error = error;
|
|
2083
|
+
}
|
|
2084
|
+
markWorkflowCompleted(workflowId);
|
|
2085
|
+
});
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
return this.createHandle(workflowId, runId, state);
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Execute a workflow and wait for result
|
|
2092
|
+
*/
|
|
2093
|
+
async execute(workflowType, options) {
|
|
2094
|
+
const handle = await this.start(workflowType, options);
|
|
2095
|
+
return handle.result();
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Get a handle to an existing workflow
|
|
2099
|
+
*/
|
|
2100
|
+
getHandle(workflowId, runId) {
|
|
2101
|
+
const state = workflows.get(workflowId);
|
|
2102
|
+
if (!state) {
|
|
2103
|
+
throw new Error(`Workflow ${workflowId} not found`);
|
|
2104
|
+
}
|
|
2105
|
+
return this.createHandle(workflowId, runId ?? state.runId, state);
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* List workflows with optional search query
|
|
2109
|
+
*/
|
|
2110
|
+
async list(options = {}) {
|
|
2111
|
+
const results = [];
|
|
2112
|
+
for (const [, state] of Array.from(workflows.entries())) {
|
|
2113
|
+
// If query provided, parse and filter
|
|
2114
|
+
if (options.query) {
|
|
2115
|
+
const match = this.matchesQuery(state, options.query);
|
|
2116
|
+
if (!match)
|
|
2117
|
+
continue;
|
|
2118
|
+
}
|
|
2119
|
+
results.push({
|
|
2120
|
+
status: state.status,
|
|
2121
|
+
workflowId: state.workflowId,
|
|
2122
|
+
runId: state.runId,
|
|
2123
|
+
workflowType: state.workflowType,
|
|
2124
|
+
taskQueue: state.taskQueue,
|
|
2125
|
+
startTime: state.startTime,
|
|
2126
|
+
searchAttributes: state.searchAttributes,
|
|
2127
|
+
memo: state.memo,
|
|
2128
|
+
});
|
|
2129
|
+
if (options.pageSize && results.length >= options.pageSize) {
|
|
2130
|
+
break;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
return results;
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Simple query matcher for search attributes
|
|
2137
|
+
*
|
|
2138
|
+
* Security: This method validates queries to prevent injection attacks.
|
|
2139
|
+
* - Empty queries match all (intentional)
|
|
2140
|
+
* - Invalid query format returns false (fail closed)
|
|
2141
|
+
* - Only whitelisted attribute keys are allowed
|
|
2142
|
+
*/
|
|
2143
|
+
matchesQuery(state, query) {
|
|
2144
|
+
// Empty query matches all (intentional behavior)
|
|
2145
|
+
if (!query || query.trim() === '')
|
|
2146
|
+
return true;
|
|
2147
|
+
// Simple parser for queries like: Status = "active"
|
|
2148
|
+
const match = query.match(/(\w+)\s*=\s*"([^"]+)"/);
|
|
2149
|
+
if (!match) {
|
|
2150
|
+
// Invalid query format - fail closed (don't match)
|
|
2151
|
+
return false;
|
|
2152
|
+
}
|
|
2153
|
+
const [, key, value] = match;
|
|
2154
|
+
// Validate key is a known search attribute (prevent prototype pollution)
|
|
2155
|
+
const validKeys = ['status', 'workflowType', 'runId', ...Object.keys(state.searchAttributes)];
|
|
2156
|
+
if (!validKeys.includes(key)) {
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
const attrValue = state.searchAttributes[key];
|
|
2160
|
+
return String(attrValue) === value;
|
|
2161
|
+
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Signal and optionally start a workflow
|
|
2164
|
+
*/
|
|
2165
|
+
async signalWithStart(workflowType, options) {
|
|
2166
|
+
const workflowId = options.workflowId ?? generateWorkflowId();
|
|
2167
|
+
// Check if workflow exists
|
|
2168
|
+
let state = workflows.get(workflowId);
|
|
2169
|
+
let handle;
|
|
2170
|
+
if (state) {
|
|
2171
|
+
// Workflow exists, just signal it
|
|
2172
|
+
handle = this.createHandle(workflowId, state.runId, state);
|
|
2173
|
+
}
|
|
2174
|
+
else {
|
|
2175
|
+
// Start the workflow
|
|
2176
|
+
handle = await this.start(workflowType, options);
|
|
2177
|
+
state = workflows.get(workflowId);
|
|
2178
|
+
}
|
|
2179
|
+
// Send the signal
|
|
2180
|
+
await handle.signal(options.signal, ...options.signalArgs);
|
|
2181
|
+
return handle;
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Create a workflow handle
|
|
2185
|
+
*/
|
|
2186
|
+
createHandle(workflowId, runId, state) {
|
|
2187
|
+
const self = this;
|
|
2188
|
+
return {
|
|
2189
|
+
workflowId,
|
|
2190
|
+
runId,
|
|
2191
|
+
async result() {
|
|
2192
|
+
// Poll until complete
|
|
2193
|
+
while (state.status === 'RUNNING') {
|
|
2194
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2195
|
+
}
|
|
2196
|
+
if (state.status === 'FAILED' && state.error) {
|
|
2197
|
+
throw state.error;
|
|
2198
|
+
}
|
|
2199
|
+
if (state.status === 'TERMINATED' && state.error) {
|
|
2200
|
+
throw state.error;
|
|
2201
|
+
}
|
|
2202
|
+
return state.result;
|
|
2203
|
+
},
|
|
2204
|
+
async describe() {
|
|
2205
|
+
return {
|
|
2206
|
+
status: state.status,
|
|
2207
|
+
workflowId,
|
|
2208
|
+
runId,
|
|
2209
|
+
workflowType: state.workflowType,
|
|
2210
|
+
taskQueue: state.taskQueue,
|
|
2211
|
+
startTime: state.startTime,
|
|
2212
|
+
searchAttributes: state.searchAttributes,
|
|
2213
|
+
memo: state.memo,
|
|
2214
|
+
};
|
|
2215
|
+
},
|
|
2216
|
+
async signal(signal, ...args) {
|
|
2217
|
+
const handler = state.signalHandlers.get(signal.name);
|
|
2218
|
+
if (handler) {
|
|
2219
|
+
await handler(...args);
|
|
2220
|
+
}
|
|
2221
|
+
// Also deliver to wait manager if present
|
|
2222
|
+
if (globalWaitManager) {
|
|
2223
|
+
await globalWaitManager.deliverEvent(null, `signal:${signal.name}`, args);
|
|
2224
|
+
}
|
|
2225
|
+
},
|
|
2226
|
+
async query(query, ...args) {
|
|
2227
|
+
const handler = state.queryHandlers.get(query.name);
|
|
2228
|
+
if (!handler) {
|
|
2229
|
+
throw new Error(`Query handler for "${query.name}" not found`);
|
|
2230
|
+
}
|
|
2231
|
+
return handler(...args);
|
|
2232
|
+
},
|
|
2233
|
+
async executeUpdate(update, ...args) {
|
|
2234
|
+
const handler = state.updateHandlers.get(update.name);
|
|
2235
|
+
if (!handler) {
|
|
2236
|
+
throw new Error(`Update handler for "${update.name}" not found`);
|
|
2237
|
+
}
|
|
2238
|
+
return handler(...args);
|
|
2239
|
+
},
|
|
2240
|
+
async cancel() {
|
|
2241
|
+
state.status = 'CANCELED';
|
|
2242
|
+
// Abort any pending activities
|
|
2243
|
+
if (state.abortController) {
|
|
2244
|
+
state.abortController.abort();
|
|
2245
|
+
}
|
|
2246
|
+
// Cancel all children based on parent close policy
|
|
2247
|
+
for (const childId of Array.from(state.children)) {
|
|
2248
|
+
const childState = workflows.get(childId);
|
|
2249
|
+
if (childState && childState.status === 'RUNNING') {
|
|
2250
|
+
childState.status = 'CANCELED';
|
|
2251
|
+
if (childState.abortController) {
|
|
2252
|
+
childState.abortController.abort();
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
},
|
|
2257
|
+
async terminate(reason) {
|
|
2258
|
+
state.status = 'TERMINATED';
|
|
2259
|
+
state.error = new Error(reason ?? 'Workflow terminated');
|
|
2260
|
+
// Terminate all children based on parent close policy
|
|
2261
|
+
for (const childId of Array.from(state.children)) {
|
|
2262
|
+
const childState = workflows.get(childId);
|
|
2263
|
+
if (childState && childState.status === 'RUNNING') {
|
|
2264
|
+
if (childState.parentClosePolicy === 'TERMINATE') {
|
|
2265
|
+
childState.status = 'TERMINATED';
|
|
2266
|
+
}
|
|
2267
|
+
else if (childState.parentClosePolicy === 'REQUEST_CANCEL') {
|
|
2268
|
+
childState.status = 'CANCELED';
|
|
2269
|
+
}
|
|
2270
|
+
// ABANDON: do nothing
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
},
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
// ============================================================================
|
|
2278
|
+
// UUID AND RANDOM - Deterministic for Replay
|
|
2279
|
+
// ============================================================================
|
|
2280
|
+
/**
|
|
2281
|
+
* Per-workflow counters for deterministic step IDs
|
|
2282
|
+
* These are tracked per workflow execution to ensure each uuid4()/random() call
|
|
2283
|
+
* gets a unique, deterministic step ID based on call order.
|
|
2284
|
+
*/
|
|
2285
|
+
const uuidCounters = new WeakMap();
|
|
2286
|
+
const randomCounters = new WeakMap();
|
|
2287
|
+
/**
|
|
2288
|
+
* Generate deterministic UUID (for replay)
|
|
2289
|
+
*
|
|
2290
|
+
* This function is deterministic within a workflow execution:
|
|
2291
|
+
* - First execution: generates a new UUID and stores it
|
|
2292
|
+
* - Replay: returns the same UUID that was generated before
|
|
2293
|
+
*
|
|
2294
|
+
* IMPORTANT: Must be called within a workflow context.
|
|
2295
|
+
* The step ID is based on call order within the workflow, so
|
|
2296
|
+
* uuid4() calls must happen in the same order on replay.
|
|
2297
|
+
*
|
|
2298
|
+
* @returns A UUID v4 string that is deterministic on replay
|
|
2299
|
+
* @throws Error if called outside a workflow context
|
|
2300
|
+
*
|
|
2301
|
+
* @example
|
|
2302
|
+
* ```typescript
|
|
2303
|
+
* import { uuid4 } from '@dotdo/temporal'
|
|
2304
|
+
*
|
|
2305
|
+
* export async function orderWorkflow() {
|
|
2306
|
+
* // These IDs will be the same on replay
|
|
2307
|
+
* const orderId = uuid4()
|
|
2308
|
+
* const transactionId = uuid4()
|
|
2309
|
+
* return { orderId, transactionId }
|
|
2310
|
+
* }
|
|
2311
|
+
* ```
|
|
2312
|
+
*/
|
|
2313
|
+
export function uuid4() {
|
|
2314
|
+
const workflow = getCurrentWorkflow();
|
|
2315
|
+
if (!workflow) {
|
|
2316
|
+
// Outside workflow context - fall back to non-deterministic
|
|
2317
|
+
// This allows usage in tests or non-workflow code
|
|
2318
|
+
return crypto.randomUUID();
|
|
2319
|
+
}
|
|
2320
|
+
// Get and increment the counter for this workflow
|
|
2321
|
+
const counter = uuidCounters.get(workflow) ?? 0;
|
|
2322
|
+
uuidCounters.set(workflow, counter + 1);
|
|
2323
|
+
// Create deterministic step ID based on call order
|
|
2324
|
+
const stepId = `uuid4:${counter}`;
|
|
2325
|
+
// Check for existing result (replay case)
|
|
2326
|
+
if (workflow.stepResults.has(stepId)) {
|
|
2327
|
+
return workflow.stepResults.get(stepId);
|
|
2328
|
+
}
|
|
2329
|
+
// Generate new UUID and store for replay
|
|
2330
|
+
const id = crypto.randomUUID();
|
|
2331
|
+
workflow.stepResults.set(stepId, id);
|
|
2332
|
+
workflow.historyLength++;
|
|
2333
|
+
return id;
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Get deterministic random number (for replay)
|
|
2337
|
+
*
|
|
2338
|
+
* This function is deterministic within a workflow execution:
|
|
2339
|
+
* - First execution: generates a new random number and stores it
|
|
2340
|
+
* - Replay: returns the same random number that was generated before
|
|
2341
|
+
*
|
|
2342
|
+
* IMPORTANT: Must be called within a workflow context.
|
|
2343
|
+
* The step ID is based on call order within the workflow, so
|
|
2344
|
+
* random() calls must happen in the same order on replay.
|
|
2345
|
+
*
|
|
2346
|
+
* @returns A number between 0 (inclusive) and 1 (exclusive)
|
|
2347
|
+
* @throws Error if called outside a workflow context
|
|
2348
|
+
*
|
|
2349
|
+
* @example
|
|
2350
|
+
* ```typescript
|
|
2351
|
+
* import { random } from '@dotdo/temporal'
|
|
2352
|
+
*
|
|
2353
|
+
* export async function retryWorkflow() {
|
|
2354
|
+
* // This decision will be the same on replay
|
|
2355
|
+
* const shouldRetry = random() < 0.5
|
|
2356
|
+
* return { shouldRetry }
|
|
2357
|
+
* }
|
|
2358
|
+
* ```
|
|
2359
|
+
*/
|
|
2360
|
+
export function random() {
|
|
2361
|
+
const workflow = getCurrentWorkflow();
|
|
2362
|
+
if (!workflow) {
|
|
2363
|
+
// Outside workflow context - fall back to non-deterministic
|
|
2364
|
+
// This allows usage in tests or non-workflow code
|
|
2365
|
+
return Math.random();
|
|
2366
|
+
}
|
|
2367
|
+
// Get and increment the counter for this workflow
|
|
2368
|
+
const counter = randomCounters.get(workflow) ?? 0;
|
|
2369
|
+
randomCounters.set(workflow, counter + 1);
|
|
2370
|
+
// Create deterministic step ID based on call order
|
|
2371
|
+
const stepId = `random:${counter}`;
|
|
2372
|
+
// Check for existing result (replay case)
|
|
2373
|
+
if (workflow.stepResults.has(stepId)) {
|
|
2374
|
+
return workflow.stepResults.get(stepId);
|
|
2375
|
+
}
|
|
2376
|
+
// Generate new random number and store for replay
|
|
2377
|
+
const num = Math.random();
|
|
2378
|
+
workflow.stepResults.set(stepId, num);
|
|
2379
|
+
workflow.historyLength++;
|
|
2380
|
+
return num;
|
|
2381
|
+
}
|
|
2382
|
+
// ============================================================================
|
|
2383
|
+
// EXPORTS
|
|
2384
|
+
// ============================================================================
|
|
2385
|
+
// Re-export activity worker pool types from activities module
|
|
2386
|
+
export { createActivityWorker, ActivityWorker, registerRemoteActivityWorker, getRemoteActivityWorker, hasRemoteActivityWorker, clearRemoteActivityWorkers, } from './activities';
|
|
2387
|
+
// Re-export unified primitives integration
|
|
2388
|
+
export {
|
|
2389
|
+
// Activity deduplication
|
|
2390
|
+
createActivityDeduplicationContext,
|
|
2391
|
+
// Workflow history store
|
|
2392
|
+
createWorkflowHistoryStore,
|
|
2393
|
+
// Timer and deadline management
|
|
2394
|
+
createWorkflowTimerManager, createWorkflowDeadlineManager,
|
|
2395
|
+
// Unified runtime
|
|
2396
|
+
createUnifiedWorkflowRuntime,
|
|
2397
|
+
// Re-exported primitives
|
|
2398
|
+
ExactlyOnceContext, createExactlyOnceContext, createTemporalStore, WindowManager, EventTimeTrigger, CountTrigger, ProcessingTimeTrigger, PurgingTrigger, Trigger, TriggerResult, WatermarkService, createWatermarkService, seconds, minutes, hours, milliseconds, } from './unified-primitives';
|
|
2399
|
+
// Re-export saga / compensation patterns
|
|
2400
|
+
export { Saga, SagaBuilder, runSaga, parallel, withRetry, withTimeout, createDistributedSagaCoordinator, SagaTimeoutError, SagaCompensationError, } from './saga';
|
|
2401
|
+
// Import activity worker functions for use in default export
|
|
2402
|
+
import { createActivityWorker as _createActivityWorker, ActivityWorker as _ActivityWorker, registerRemoteActivityWorker as _registerRemoteActivityWorker, getRemoteActivityWorker as _getRemoteActivityWorker, hasRemoteActivityWorker as _hasRemoteActivityWorker, clearRemoteActivityWorkers as _clearRemoteActivityWorkers, } from './activities';
|
|
2403
|
+
export default {
|
|
2404
|
+
defineSignal,
|
|
2405
|
+
defineQuery,
|
|
2406
|
+
defineUpdate,
|
|
2407
|
+
setHandler,
|
|
2408
|
+
proxyActivities,
|
|
2409
|
+
proxyLocalActivities,
|
|
2410
|
+
startChild,
|
|
2411
|
+
executeChild,
|
|
2412
|
+
sleep,
|
|
2413
|
+
condition,
|
|
2414
|
+
workflowInfo,
|
|
2415
|
+
continueAsNew,
|
|
2416
|
+
makeContinueAsNewFunc,
|
|
2417
|
+
CancellationScope,
|
|
2418
|
+
isCancellation,
|
|
2419
|
+
WorkflowClient,
|
|
2420
|
+
uuid4,
|
|
2421
|
+
random,
|
|
2422
|
+
configure,
|
|
2423
|
+
// New exports for API coverage
|
|
2424
|
+
createTimer,
|
|
2425
|
+
cancelTimer,
|
|
2426
|
+
patched,
|
|
2427
|
+
deprecatePatch,
|
|
2428
|
+
setSearchAttributes,
|
|
2429
|
+
upsertSearchAttributes,
|
|
2430
|
+
// Task queue routing
|
|
2431
|
+
registerWorker,
|
|
2432
|
+
hasWorker,
|
|
2433
|
+
getWorker,
|
|
2434
|
+
listTaskQueues,
|
|
2435
|
+
TaskQueueNotRegisteredError,
|
|
2436
|
+
ActivityTimeoutError,
|
|
2437
|
+
// Determinism enforcement
|
|
2438
|
+
workflowNow,
|
|
2439
|
+
WorkflowDeterminismWarning,
|
|
2440
|
+
configureDeterminism,
|
|
2441
|
+
getDeterminismWarnings,
|
|
2442
|
+
clearDeterminismWarnings,
|
|
2443
|
+
enableDeterminismDetection,
|
|
2444
|
+
disableDeterminismDetection,
|
|
2445
|
+
inWorkflowContext,
|
|
2446
|
+
// CF Workflows integration
|
|
2447
|
+
setWorkflowStep,
|
|
2448
|
+
clearWorkflowStep,
|
|
2449
|
+
getWorkflowStep,
|
|
2450
|
+
// Storage strategy pattern (for advanced use)
|
|
2451
|
+
createStorageStrategy,
|
|
2452
|
+
CFWorkflowsStorageStrategy,
|
|
2453
|
+
InMemoryStorageStrategy,
|
|
2454
|
+
// Activity worker pool (for independent scaling)
|
|
2455
|
+
createActivityWorker: _createActivityWorker,
|
|
2456
|
+
ActivityWorker: _ActivityWorker,
|
|
2457
|
+
registerRemoteActivityWorker: _registerRemoteActivityWorker,
|
|
2458
|
+
getRemoteActivityWorker: _getRemoteActivityWorker,
|
|
2459
|
+
hasRemoteActivityWorker: _hasRemoteActivityWorker,
|
|
2460
|
+
clearRemoteActivityWorkers: _clearRemoteActivityWorkers,
|
|
2461
|
+
// Utility for testing
|
|
2462
|
+
__clearTemporalState,
|
|
2463
|
+
};
|
|
2464
|
+
//# sourceMappingURL=index.js.map
|