├── .eslintignore ├── .eslintrc ├── .github ├── CODEOWNERS └── workflows │ ├── ci.yml │ ├── conventions.yml │ ├── docs.yml │ ├── nightly.yml │ ├── omes.yml │ ├── release.yml │ └── stress.yml ├── .gitignore ├── .gitmodules ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── cliff.toml ├── commitlint.config.js ├── docs ├── activation-in-debug-mode.mermaid ├── activation.mermaid ├── arch-debug-mode.svg ├── arch.svg ├── building.md ├── data-converter.md ├── debug-replay.mermaid ├── protobuf-libraries.md └── sdk-structure.md ├── etc ├── mac-cargo-config.toml ├── otel-collector-config.yaml └── prometheus.yaml ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── activity │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── client │ ├── README.md │ ├── package.json │ ├── src │ │ ├── async-completion-client.ts │ │ ├── base-client.ts │ │ ├── build-id-types.ts │ │ ├── client.ts │ │ ├── connection.ts │ │ ├── errors.ts │ │ ├── grpc-retry.ts │ │ ├── helpers.ts │ │ ├── index.ts │ │ ├── interceptors.ts │ │ ├── iterators-utils.ts │ │ ├── pkg.ts │ │ ├── schedule-client.ts │ │ ├── schedule-helpers.ts │ │ ├── schedule-types.ts │ │ ├── task-queue-client.ts │ │ ├── types.ts │ │ ├── workflow-client.ts │ │ ├── workflow-options.ts │ │ └── workflow-update-stage.ts │ └── tsconfig.json ├── cloud │ ├── README.md │ ├── package.json │ ├── src │ │ ├── cloud-operations-client.ts │ │ ├── index.ts │ │ ├── pkg.ts │ │ └── types.ts │ └── tsconfig.json ├── common │ ├── README.md │ ├── package.json │ ├── src │ │ ├── activity-options.ts │ │ ├── converter │ │ │ ├── data-converter.ts │ │ │ ├── failure-converter.ts │ │ │ ├── payload-codec.ts │ │ │ ├── payload-converter.ts │ │ │ ├── payload-search-attributes.ts │ │ │ ├── protobuf-payload-converters.ts │ │ │ └── types.ts │ │ ├── deprecated-time.ts │ │ ├── encoding.ts │ │ ├── errors.ts │ │ ├── failure.ts │ │ ├── index.ts │ │ ├── interceptors.ts │ │ ├── interfaces.ts │ │ ├── internal-non-workflow │ │ │ ├── codec-helpers.ts │ │ │ ├── codec-types.ts │ │ │ ├── data-converter-helpers.ts │ │ │ ├── index.ts │ │ │ ├── parse-host-uri.ts │ │ │ ├── proxy-config.ts │ │ │ └── tls-config.ts │ │ ├── internal-workflow │ │ │ ├── enums-helpers.ts │ │ │ ├── index.ts │ │ │ └── objects-helpers.ts │ │ ├── logger.ts │ │ ├── metrics.ts │ │ ├── priority.ts │ │ ├── proto-utils.ts │ │ ├── protobufs.ts │ │ ├── retry-policy.ts │ │ ├── search-attributes.ts │ │ ├── time.ts │ │ ├── type-helpers.ts │ │ ├── versioning-intent-enum.ts │ │ ├── versioning-intent.ts │ │ ├── worker-deployments.ts │ │ ├── workflow-definition-options.ts │ │ ├── workflow-handle.ts │ │ └── workflow-options.ts │ └── tsconfig.json ├── core-bridge │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── ai-rules.md │ ├── bridge-macros │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── derive_js_function.rs │ │ │ ├── derive_tryfromjs.rs │ │ │ ├── derive_tryintojs.rs │ │ │ └── lib.rs │ ├── common.js │ ├── index.js │ ├── package.json │ ├── scripts │ │ └── build.js │ ├── src │ │ ├── client.rs │ │ ├── helpers │ │ │ ├── abort_controller.rs │ │ │ ├── callbacks.rs │ │ │ ├── errors.rs │ │ │ ├── future.rs │ │ │ ├── handles.rs │ │ │ ├── inspect.rs │ │ │ ├── json_string.rs │ │ │ ├── mod.rs │ │ │ ├── properties.rs │ │ │ ├── try_from_js.rs │ │ │ └── try_into_js.rs │ │ ├── lib.rs │ │ ├── logs.rs │ │ ├── metrics.rs │ │ ├── runtime.rs │ │ ├── testing.rs │ │ └── worker.rs │ ├── ts │ │ ├── errors.ts │ │ ├── index.ts │ │ └── native.ts │ └── tsconfig.json ├── create-project │ ├── .npmignore │ ├── LICENSE.md │ ├── README.md │ ├── cli.js │ ├── package.json │ ├── src │ │ ├── create-project.ts │ │ ├── helpers │ │ │ ├── fetch-samples.ts │ │ │ ├── get-error-code.ts │ │ │ ├── git.ts │ │ │ ├── headers.ts │ │ │ ├── install.test.ts │ │ │ ├── install.ts │ │ │ ├── is-online.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ ├── samples.ts │ │ │ ├── strip-snip-comments.ts │ │ │ ├── subprocess.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ └── pkg.ts │ └── tsconfig.json ├── docs │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── docs │ │ └── index.md │ ├── docusaurus.config.js │ ├── package-lock.json │ ├── package.json │ ├── sidebars.js │ ├── src │ │ ├── css │ │ │ └── custom.css │ │ └── theme │ │ │ └── DocBreadcrumbs │ │ │ └── index.js │ └── static │ │ ├── .nojekyll │ │ └── img │ │ ├── favicon.ico │ │ ├── social.png │ │ ├── temporal-logo-dark.svg │ │ └── temporal-logo.svg ├── interceptors-opentelemetry │ ├── README.md │ ├── package.json │ ├── src │ │ ├── client │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── instrumentation.ts │ │ ├── worker │ │ │ └── index.ts │ │ └── workflow │ │ │ ├── context-manager.ts │ │ │ ├── definitions.ts │ │ │ ├── index.ts │ │ │ ├── runtime.ts │ │ │ └── span-exporter.ts │ └── tsconfig.json ├── meta │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── nyc-test-coverage │ ├── README.md │ ├── package.json │ ├── src │ │ ├── globalCoverage.ts │ │ ├── index.ts │ │ ├── interceptors.ts │ │ ├── loader.ts │ │ └── sinks.ts │ └── tsconfig.json ├── proto │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── protos │ │ ├── index.d.ts │ │ ├── index.js │ │ └── root.js │ ├── scripts │ │ └── compile-proto.js │ ├── src │ │ └── patch-protobuf-root.ts │ └── tsconfig.json ├── test │ ├── history_files │ │ ├── cancel_fake_progress_history.bin │ │ ├── cancel_fake_progress_history.json │ │ ├── complete_update_after_workflow_returns_pre1488.json │ │ ├── lang_flags_replay_correctly_1_11_1.json │ │ ├── lang_flags_replay_correctly_1_11_2.json │ │ └── lang_flags_replay_correctly_1_9_3.json │ ├── package.json │ ├── protos │ │ ├── messages.proto │ │ ├── namespaced-messages.proto │ │ ├── root.js │ │ └── uppercase.proto │ ├── scripts │ │ └── compile-proto.js │ ├── src │ │ ├── activities │ │ │ ├── async-completer.ts │ │ │ ├── cancellable-fetch.ts │ │ │ ├── create-concat-activity.ts │ │ │ ├── default-and-defined.ts │ │ │ ├── failure-tester.ts │ │ │ ├── fake-progress.ts │ │ │ ├── helpers.ts │ │ │ ├── index.ts │ │ │ └── interceptors.ts │ │ ├── debug-replayer.ts │ │ ├── deployment-versioning-no-annotations │ │ │ └── index.ts │ │ ├── deployment-versioning-v1 │ │ │ └── index.ts │ │ ├── deployment-versioning-v2 │ │ │ └── index.ts │ │ ├── deployment-versioning-v3 │ │ │ └── index.ts │ │ ├── helpers-integration-multi-codec.ts │ │ ├── helpers-integration.ts │ │ ├── helpers.ts │ │ ├── load │ │ │ ├── all-in-one.ts │ │ │ ├── all-scenarios.ts │ │ │ ├── args.ts │ │ │ ├── child-process.ts │ │ │ ├── nightly-scenarios.ts │ │ │ ├── run-all-nightly-scenarios.ts │ │ │ ├── run-all-stress-ci-scenarios.ts │ │ │ ├── setup.ts │ │ │ ├── starter.ts │ │ │ ├── stress-ci-scenarios.ts │ │ │ └── worker.ts │ │ ├── mock-internal-flags.ts │ │ ├── mock-native-worker.ts │ │ ├── mocks │ │ │ └── workflows-with-node-dependencies │ │ │ │ ├── example │ │ │ │ ├── client.ts │ │ │ │ ├── index.ts │ │ │ │ └── models.ts │ │ │ │ └── issue-516.ts │ │ ├── payload-converters │ │ │ ├── failure-converter-bad-export.ts │ │ │ ├── payload-converter-bad-export.ts │ │ │ ├── payload-converter-no-export.ts │ │ │ ├── payload-converter-returns-undefined.ts │ │ │ ├── payload-converter-throws-from-payload.ts │ │ │ └── proto-payload-converter.ts │ │ ├── query-perf.ts │ │ ├── run-a-worker.ts │ │ ├── run-a-workflow.ts │ │ ├── run-activation-perf-tests.ts │ │ ├── test-activity-log-interceptor.ts │ │ ├── test-async-completion.ts │ │ ├── test-bridge.ts │ │ ├── test-bundler.ts │ │ ├── test-client-connection.ts │ │ ├── test-client-errors.ts │ │ ├── test-custom-payload-codec.ts │ │ ├── test-custom-payload-converter.ts │ │ ├── test-default-activity.ts │ │ ├── test-default-workflow.ts │ │ ├── test-enums-helpers.ts │ │ ├── test-ephemeral-server.ts │ │ ├── test-failure-converter.ts │ │ ├── test-integration-split-one.ts │ │ ├── test-integration-split-three.ts │ │ ├── test-integration-split-two.ts │ │ ├── test-integration-update-interceptors.ts │ │ ├── test-integration-update.ts │ │ ├── test-integration-workflows-with-recorded-logs.ts │ │ ├── test-integration-workflows.ts │ │ ├── test-interceptors.ts │ │ ├── test-interface-type-safety.ts │ │ ├── test-isolation.ts │ │ ├── test-iterators-utils.ts │ │ ├── test-local-activities.ts │ │ ├── test-logger.ts │ │ ├── test-metrics-custom.ts │ │ ├── test-mockactivityenv.ts │ │ ├── test-native-connection-headers.ts │ │ ├── test-native-connection.ts │ │ ├── test-nyc-coverage.ts │ │ ├── test-otel.ts │ │ ├── test-parse-uri.ts │ │ ├── test-patch-and-condition.ts │ │ ├── test-patch-root.ts │ │ ├── test-payload-converter.ts │ │ ├── test-prometheus.ts │ │ ├── test-proto-utils.ts │ │ ├── test-replay.ts │ │ ├── test-retry-policy.ts │ │ ├── test-runtime.ts │ │ ├── test-schedules.ts │ │ ├── test-server-options.ts │ │ ├── test-signal-query-patch.ts │ │ ├── test-signals-are-always-delivered.ts │ │ ├── test-sinks.ts │ │ ├── test-temporal-cloud.ts │ │ ├── test-testenvironment.ts │ │ ├── test-time.ts │ │ ├── test-type-helpers.ts │ │ ├── test-typed-search-attributes.ts │ │ ├── test-worker-activities.ts │ │ ├── test-worker-debug-mode.ts │ │ ├── test-worker-deployment-versioning.ts │ │ ├── test-worker-exposes-abortcontroller.ts │ │ ├── test-worker-exposes-textencoderdecoder.ts │ │ ├── test-worker-heartbeats.ts │ │ ├── test-worker-lifecycle.ts │ │ ├── test-worker-no-activities.ts │ │ ├── test-worker-no-workflows.ts │ │ ├── test-worker-poller-autoscale.ts │ │ ├── test-worker-tuner.ts │ │ ├── test-worker-versioning-unit.ts │ │ ├── test-worker-versioning.ts │ │ ├── test-workflow-cancellation.ts │ │ ├── test-workflow-log-interceptor.ts │ │ ├── test-workflow-unhandled-rejection-crash.ts │ │ ├── test-workflows.ts │ │ ├── workflows │ │ │ ├── abort-controller.ts │ │ │ ├── activity-failures.ts │ │ │ ├── args-and-return.ts │ │ │ ├── async-activity-completion-tester.ts │ │ │ ├── async-fail-signal.ts │ │ │ ├── async-workflow.ts │ │ │ ├── cancel-activity-after-first-completion.ts │ │ │ ├── cancel-fake-progress.ts │ │ │ ├── cancel-http-request.ts │ │ │ ├── cancel-requested-with-non-cancellable.ts │ │ │ ├── cancel-scope-on-failed-validation.ts │ │ │ ├── cancel-timer-immediately-alternative-impl.ts │ │ │ ├── cancel-timer-immediately.ts │ │ │ ├── cancel-workflow.ts │ │ │ ├── cancellation-error-is-propagated.ts │ │ │ ├── cancellation-scopes-with-callbacks.ts │ │ │ ├── cancellation-scopes.ts │ │ │ ├── child-and-noncancellable.ts │ │ │ ├── child-workflow-cancel.ts │ │ │ ├── child-workflow-sample.ts │ │ │ ├── child-workflow-signals.ts │ │ │ ├── condition-completion-race.ts │ │ │ ├── condition-timeout-0.ts │ │ │ ├── condition.ts │ │ │ ├── configured-activities.ts │ │ │ ├── continue-as-new-same-workflow.ts │ │ │ ├── continue-as-new-suggested.ts │ │ │ ├── continue-as-new-to-different-workflow.ts │ │ │ ├── core-issue-589.ts │ │ │ ├── date.ts │ │ │ ├── default-activity-wf.ts │ │ │ ├── default-workflow-function.ts │ │ │ ├── deferred-resolve.ts │ │ │ ├── definitions.ts │ │ │ ├── deprecate-patch.ts │ │ │ ├── echo-binary-protobuf.ts │ │ │ ├── fail-signal.ts │ │ │ ├── fail-unless-signaled-before-start.ts │ │ │ ├── fails-workflow-task.ts │ │ │ ├── fill-memory.ts │ │ │ ├── global-overrides.ts │ │ │ ├── handle-external-workflow-cancellation-while-activity-running.ts │ │ │ ├── http.ts │ │ │ ├── importer.ts │ │ │ ├── index.ts │ │ │ ├── interceptor-example.ts │ │ │ ├── internals-interceptor-example.ts │ │ │ ├── interrupt-signal.ts │ │ │ ├── invalid-or-failed-queries.ts │ │ │ ├── log-before-timing-out.ts │ │ │ ├── log-sample.ts │ │ │ ├── log-sink-tester.ts │ │ │ ├── long-history-generator.ts │ │ │ ├── multiple-activities-single-timeout.ts │ │ │ ├── nested-cancellation.ts │ │ │ ├── noncancellable-awaited-in-root-scope.ts │ │ │ ├── noncancellable-in-noncancellable.ts │ │ │ ├── noncancellable-shields-children.ts │ │ │ ├── otel-interceptors.ts │ │ │ ├── partial-noncancelable.ts │ │ │ ├── patch-and-condition-post-patch.ts │ │ │ ├── patch-and-condition-pre-patch.ts │ │ │ ├── patched-top-level.ts │ │ │ ├── patched.ts │ │ │ ├── priority.ts │ │ │ ├── promise-all.ts │ │ │ ├── promise-race.ts │ │ │ ├── promise-then-promise.ts │ │ │ ├── protobufs.ts │ │ │ ├── race.ts │ │ │ ├── random.ts │ │ │ ├── reject-promise.ts │ │ │ ├── run-activity-in-different-task-queue.ts │ │ │ ├── scope-cancelled-while-waiting-on-external-workflow-cancellation.ts │ │ │ ├── set-timeout-after-microtasks.ts │ │ │ ├── shared-cancellation-scopes.ts │ │ │ ├── signal-handlers-clear.ts │ │ │ ├── signal-query-patch-post-patch.ts │ │ │ ├── signal-query-patch-pre-patch.ts │ │ │ ├── signal-target.ts │ │ │ ├── signal-update-ordering.ts │ │ │ ├── signals-are-always-processed.ts │ │ │ ├── signals-ordering.ts │ │ │ ├── signals-timers-activities-order.ts │ │ │ ├── sinks.ts │ │ │ ├── sleep-impl.ts │ │ │ ├── sleep.ts │ │ │ ├── smorgasbord.ts │ │ │ ├── stack-tracer.ts │ │ │ ├── success-string.ts │ │ │ ├── swc.ts │ │ │ ├── tasks-and-microtasks.ts │ │ │ ├── testenv-test-workflows.ts │ │ │ ├── text-encoder-decoder.ts │ │ │ ├── throw-async.ts │ │ │ ├── trailing-timer.ts │ │ │ ├── try-to-continue-after-completion.ts │ │ │ ├── two-strings.ts │ │ │ ├── unblock-or-cancel.ts │ │ │ ├── unhandled-rejection.ts │ │ │ ├── updates-ordering.ts │ │ │ ├── upsert-and-read-memo.ts │ │ │ ├── upsert-and-read-search-attributes.ts │ │ │ ├── wait-on-signal-then-activity.ts │ │ │ ├── wait-on-user.ts │ │ │ └── workflow-cancellation-scenarios.ts │ │ └── zeroes-http-server.ts │ ├── tls_certs │ │ ├── mkcerts.sh │ │ ├── test-ca.crt │ │ ├── test-ca.key │ │ ├── test-client-chain.crt │ │ ├── test-client.crt │ │ ├── test-client.key │ │ ├── test-server-chain.crt │ │ ├── test-server.crt │ │ └── test-server.key │ └── tsconfig.json ├── testing │ ├── README.md │ ├── package.json │ ├── proto │ │ ├── Makefile │ │ ├── api-linter.yaml │ │ ├── buf.yaml │ │ ├── dependencies │ │ │ └── gogoproto │ │ │ │ └── gogo.proto │ │ └── temporal │ │ │ └── api │ │ │ └── testservice │ │ │ └── v1 │ │ │ ├── request_response.proto │ │ │ └── service.proto │ ├── src │ │ ├── assert-to-failure-interceptor.ts │ │ ├── client.ts │ │ ├── connection.ts │ │ ├── ephemeral-server.ts │ │ ├── index.ts │ │ ├── mocking-activity-environment.ts │ │ ├── pkg.ts │ │ ├── testing-workflow-environment.ts │ │ └── utils.ts │ └── tsconfig.json ├── worker │ ├── README.md │ ├── package.json │ ├── src │ │ ├── activity-log-interceptor.ts │ │ ├── activity.ts │ │ ├── connection-options.ts │ │ ├── connection.ts │ │ ├── debug-replayer │ │ │ ├── client.ts │ │ │ ├── inbound-interceptor.ts │ │ │ ├── index.ts │ │ │ ├── outbound-interceptor.ts │ │ │ ├── worker-thread.ts │ │ │ └── workflow-notifier.ts │ │ ├── errors.ts │ │ ├── index.ts │ │ ├── interceptors.ts │ │ ├── logger.ts │ │ ├── pkg.ts │ │ ├── replay.ts │ │ ├── runtime-logger.ts │ │ ├── runtime-metrics.ts │ │ ├── runtime-options.ts │ │ ├── runtime.ts │ │ ├── rxutils.ts │ │ ├── sinks.ts │ │ ├── utils.ts │ │ ├── worker-options.ts │ │ ├── worker-tuner.ts │ │ ├── worker.ts │ │ ├── workflow-codec-runner.ts │ │ ├── workflow-log-interceptor.ts │ │ └── workflow │ │ │ ├── bundler.ts │ │ │ ├── interface.ts │ │ │ ├── logger.ts │ │ │ ├── metrics.ts │ │ │ ├── module-overrides │ │ │ ├── assert.ts │ │ │ ├── url.ts │ │ │ └── util.ts │ │ │ ├── reusable-vm.ts │ │ │ ├── threaded-vm.ts │ │ │ ├── vm-shared.ts │ │ │ ├── vm.ts │ │ │ ├── workflow-worker-thread.ts │ │ │ └── workflow-worker-thread │ │ │ ├── input.ts │ │ │ └── output.ts │ └── tsconfig.json └── workflow │ ├── README.md │ ├── package.json │ ├── src │ ├── alea.ts │ ├── cancellation-scope.ts │ ├── errors.ts │ ├── flags.ts │ ├── global-attributes.ts │ ├── global-overrides.ts │ ├── index.ts │ ├── interceptors.ts │ ├── interfaces.ts │ ├── internals.ts │ ├── logs.ts │ ├── metrics.ts │ ├── pkg.ts │ ├── sinks.ts │ ├── stack-helpers.ts │ ├── trigger.ts │ ├── update-scope.ts │ ├── worker-interface.ts │ ├── workflow-handle.ts │ └── workflow.ts │ └── tsconfig.json ├── scripts ├── clean.mjs ├── create-certs-dir.js ├── gen-docs.js ├── init-from-verdaccio.js ├── prepublish.mjs ├── publish-to-verdaccio.js ├── registry.js ├── test-example.js ├── utils.js └── wait-on-temporal.mjs ├── tsconfig.base.json └── tsconfig.prune.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | es2020 4 | commonjs 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # the repo. Unless a later match takes precedence, 3 | # @temporalio/sdk will be requested for review when 4 | # someone opens a pull request. 5 | * @temporalio/sdk 6 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly Tests 2 | 3 | on: 4 | schedule: 5 | # * is a special character in YAML so you have to quote this string 6 | - cron: '00 08 * * *' 7 | # (1 AM PST) 8 | 9 | jobs: 10 | nightly: 11 | uses: ./.github/workflows/stress.yml 12 | with: 13 | test-type: ci-nightly 14 | test-timeout-minutes: 360 # yes, 6 hours 15 | reuse-v8-context: false 16 | nightly-reuse-context: 17 | uses: ./.github/workflows/stress.yml 18 | with: 19 | test-type: ci-nightly 20 | test-timeout-minutes: 360 # yes, 6 hours 21 | reuse-v8-context: true 22 | -------------------------------------------------------------------------------- /.github/workflows/omes.yml: -------------------------------------------------------------------------------- 1 | name: Omes Testing 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'releases/*' 8 | 9 | jobs: 10 | omes-image-build: 11 | uses: temporalio/omes/.github/workflows/docker-images.yml@main 12 | secrets: inherit 13 | with: 14 | lang: typescript 15 | sdk-repo-url: ${{ github.event.pull_request.head.repo.full_name || 'temporalio/sdk-typescript' }} 16 | sdk-repo-ref: ${{ github.event.pull_request.head.ref || github.ref }} 17 | # TODO: Remove once we have a good way of cleaning up sha-based pushed images 18 | docker-tag-ext: ci-latest 19 | do-push: true 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | lib/ 4 | *.tsbuildinfo 5 | build/ 6 | lerna*.log 7 | packages/test/protos/json-module.js 8 | packages/test/protos/root.d.ts 9 | packages/testing/test-server* 10 | packages/testing/generated-protos/ 11 | packages/core-bridge/releases 12 | packages/*/package-lock.json 13 | /sdk-node.iml 14 | *~ 15 | 16 | # One test creates persisted SQLite DBs; they should normally be deleted automatically, 17 | # but may be left behind in some error scenarios. 18 | packages/test/temporal-db-*.sqlite 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "sdk-core"] 2 | path = packages/core-bridge/sdk-core 3 | url = https://github.com/temporalio/sdk-core.git 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | packages/core-bridge/target 4 | sdk-core 5 | lib 6 | es2020 7 | commonjs 8 | .docusaurus 9 | packages/*/CHANGELOG.md 10 | packages/docs/docs/api 11 | typedoc-sidebar.js 12 | lerna.json 13 | README.md 14 | packages/proto/protos/root.d.ts 15 | packages/proto/protos/json-module.js 16 | packages/test/protos/root.d.ts 17 | packages/test/protos/json-module.js 18 | packages/testing/generated-protos/* 19 | packages/testing/proto/* 20 | 21 | # Ignored since it fails Windows CI 22 | packages/create-project/cli.js 23 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "temporal.replayerEntrypoint": "packages/test/src/debug-replayer.ts", 3 | "rust-analyzer.linkedProjects": ["./packages/core-bridge/Cargo.toml"] 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Release notes are now published exclusively through our 2 | [GitHub release page](https://github.com/temporalio/sdk-typescript/releases). 3 | This file is no longer maintained. 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Temporal TypeScript SDK 2 | 3 | MIT License 4 | 5 | Copyright (c) 2021 Temporal Technologies Inc. All Rights Reserved 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'subject-case': [2, 'always'], 5 | 'scope-enum': [ 6 | 2, 7 | 'always', 8 | ['activity', 'perf', 'client', 'docs', 'core', 'release', 'create-project', 'worker', 'workflow', 'proto'], 9 | ], 10 | 'header-max-length': [2, 'always', 120], 11 | 'body-max-line-length': [1, 'always', 100], 12 | 'footer-max-line-length': [2, 'always', 120], 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /docs/activation-in-debug-mode.mermaid: -------------------------------------------------------------------------------- 1 | %% Activation diagram for Worker with `debugMode` option on 2 | sequenceDiagram 3 | participant Server 4 | participant Core as Rust Core 5 | participant MT as Node Main Thread 6 | participant VM as Workflow VM Sandbox 7 | 8 | MT->>+Core: Poll Workflow Activation 9 | opt No pending work 10 | Core->>+Server: Poll Workflow Task 11 | Server-->>-Core: Respond with Workflow Task 12 | end 13 | Core->>-MT: Respond with Activation 14 | MT->>MT: Decode Payloads 15 | MT->>+WT: Run Workflow Activation 16 | 17 | WT->>VM: Update Activator (now, WorkflowInfo, SDK flags, patches) 18 | 19 | alt "Single Batch mode" 20 | WT->>VM: Activate(queries) 21 | VM->>VM: Run Microtasks 22 | WT->>VM: Try Unblock Conditions 23 | else Legacy "Multi Batches mode" 24 | loop [signals, updates+completions] as jobs 25 | WT->>VM: Activate(jobs) 26 | VM->>VM: Run Microtasks 27 | WT->>VM: Try Unblock Conditions 28 | end 29 | end 30 | 31 | MT->>VM: Collect Commands 32 | MT->>MT: Encode Payloads 33 | MT->>+VM: Collect Sink Calls 34 | VM-->>-MT: Respond with Sink Calls 35 | MT->>MT: Run Sink Functions 36 | MT->>Core: Complete Activation 37 | opt Completed Workflow Task 38 | Core->>Server: Complete Workflow Task 39 | end 40 | 41 | -------------------------------------------------------------------------------- /docs/activation.mermaid: -------------------------------------------------------------------------------- 1 | %% Activation diagram for Worker with `reuseV8Context` option off 2 | sequenceDiagram 3 | participant Server 4 | participant Core as Rust Core 5 | participant MT as Node Main Thread 6 | participant WT as Node Worker Thread 7 | participant VM as Workflow VM Sandbox 8 | 9 | MT->>+Core: Poll Workflow Activation 10 | opt No pending work 11 | Core->>+Server: Poll Workflow Task 12 | Server-->>-Core: Respond with Workflow Task 13 | end 14 | Core->>-MT: Respond with Activation 15 | MT->>MT: Decode Payloads 16 | MT->>+WT: Run Workflow Activation 17 | 18 | WT->>VM: Update Activator (now, WorkflowInfo, SDK flags, patches) 19 | 20 | alt "Single Batch mode" 21 | WT->>VM: Activate(queries) 22 | VM->>VM: Run Microtasks 23 | WT->>VM: Try Unblock Conditions 24 | else Legacy "Multi Batches mode" 25 | loop [signals, updates+completions] as jobs 26 | WT->>VM: Activate(jobs) 27 | VM->>VM: Run Microtasks 28 | WT->>VM: Try Unblock Conditions 29 | end 30 | end 31 | 32 | WT->>VM: Collect Commands 33 | WT-->>-MT: Respond to Activation 34 | MT->>MT: Encode Payloads 35 | MT->>+WT: Collect Sink Calls 36 | WT->>VM: Collect Sink Calls 37 | VM-->>WT: Respond with Sink Calls 38 | WT-->>-MT: Respond with Sink Calls 39 | MT->>MT: Run Sink Functions 40 | MT->>Core: Complete Activation 41 | opt Completed Workflow Task 42 | Core->>Server: Complete Workflow Task 43 | end 44 | -------------------------------------------------------------------------------- /docs/debug-replay.mermaid: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | participant R as Runner (e.g. VS Code) 3 | participant MT as Node.js Main Thread 4 | participant WF as Workflow Context 5 | participant WT as Worker Thread 6 | 7 | R->>MT: Launch 8 | MT->>R: Get history 9 | MT->>MT: Replay workflow 10 | loop 11 | MT->>WF: Activate workflow 12 | WF->>MT: Update runner with current eventId (blocking call) 13 | MT->>WT: Update runner (block using atomics) 14 | WT->>R: Set current event ID 15 | alt has breakpoint? 16 | R->>R: ⏸ Pause execution 17 | end 18 | R->>WT: Respond OK 19 | end 20 | -------------------------------------------------------------------------------- /docs/sdk-structure.md: -------------------------------------------------------------------------------- 1 | # SDK Structure 2 | 3 | The TypeScript SDK is developed in a monorepo consisting of several packages managed with [`lerna`](https://lerna.js.org/). The public packages are: 4 | 5 | - [`temporalio`](../packages/meta) - Meta package, bundles the common packages for ease of installation. 6 | - [`@temporalio/worker`](../packages/worker) - Communicates with the Temporal service and runs workflows and activities 7 | - [`@temporalio/workflow`](../packages/workflow) - Workflow runtime library 8 | - [`@temporalio/activity`](../packages/activity) - Access to current activity context 9 | - [`@temporalio/client`](../packages/client) - Communicate with the Temporal service for things like administration and scheduling workflows 10 | - [`@temporalio/proto`](../packages/proto) - Compiled protobuf definitions 11 | - [`@temporalio/workflow-common`](../packages/workflow-common) - Code shared between `@temporalio/workflow` and other packages 12 | - [`@temporalio/common`](../packages/common) - All shared code (re-exports everything in `@temporalio/workflow-common`, and also has code shared between packages other than `@temporalio/workflow`) 13 | - [`@temporalio/create`](../packages/create-project) - NPM package initializer 14 | 15 | [Repo visualization](https://octo-repo-visualization.vercel.app/?repo=temporalio%2Fsdk-typescript) 16 | -------------------------------------------------------------------------------- /etc/mac-cargo-config.toml: -------------------------------------------------------------------------------- 1 | # Copy this file to ~/.cargo/config.toml in order to cross-compile for Linux and Windows 2 | [target.x86_64-unknown-linux-gnu] 3 | linker = "x86_64-linux-gnu-gcc" 4 | [target.aarch64-unknown-linux-gnu] 5 | linker = "aarch64-linux-gnu-gcc" 6 | [target.x86_64-pc-windows-gnu] 7 | linker = "/usr/local/bin/x86_64-w64-mingw32-gcc" 8 | -------------------------------------------------------------------------------- /etc/otel-collector-config.yaml: -------------------------------------------------------------------------------- 1 | receivers: 2 | otlp: 3 | protocols: 4 | grpc: 5 | 6 | exporters: 7 | prometheus: 8 | endpoint: '0.0.0.0:8889' 9 | namespace: temporal_sdk 10 | logging: 11 | 12 | jaeger: 13 | endpoint: jaeger:14250 14 | insecure: true 15 | 16 | processors: 17 | batch: 18 | 19 | extensions: 20 | health_check: 21 | pprof: 22 | endpoint: :1888 23 | zpages: 24 | endpoint: :55679 25 | 26 | service: 27 | extensions: [pprof, zpages, health_check] 28 | pipelines: 29 | traces: 30 | receivers: [otlp] 31 | processors: [batch] 32 | exporters: [logging, jaeger] 33 | metrics: 34 | receivers: [otlp] 35 | processors: [batch] 36 | exporters: [logging, prometheus] 37 | -------------------------------------------------------------------------------- /etc/prometheus.yaml: -------------------------------------------------------------------------------- 1 | scrape_configs: 2 | - job_name: 'otel-collector' 3 | scrape_interval: 3s 4 | static_configs: 5 | - targets: ['otel-collector:8889'] 6 | - targets: ['otel-collector:8888'] 7 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.12.0-rc.0", 3 | "npmClient": "npm", 4 | "command": { 5 | "publish": { 6 | "message": "chore(release): Publish", 7 | "registry": "https://registry.npmjs.org/" 8 | }, 9 | "version": { 10 | "message": "chore(release): Publish" 11 | } 12 | }, 13 | "$schema": "node_modules/lerna/schemas/lerna-schema.json" 14 | } 15 | -------------------------------------------------------------------------------- /packages/activity/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/activity` 2 | 3 | [![NPM](https://img.shields.io/npm/v/@temporalio/activity?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/activity) 4 | 5 | Part of [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/). 6 | 7 | - [Activity docs](https://docs.temporal.io/typescript/activities/) 8 | - [API reference](https://typescript.temporal.io/api/namespaces/activity) 9 | - [Sample projects](https://github.com/temporalio/samples-typescript) 10 | -------------------------------------------------------------------------------- /packages/activity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@temporalio/activity", 3 | "version": "1.12.0-rc.0", 4 | "description": "Temporal.io SDK Activity sub-package", 5 | "main": "lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "keywords": [ 8 | "temporal", 9 | "workflow", 10 | "worker", 11 | "activity" 12 | ], 13 | "author": "Temporal Technologies Inc. ", 14 | "license": "MIT", 15 | "dependencies": { 16 | "@temporalio/common": "file:../common", 17 | "abort-controller": "^3.0.0" 18 | }, 19 | "engines": { 20 | "node": ">= 18.0.0" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/temporalio/sdk-typescript/issues" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/temporalio/sdk-typescript.git", 28 | "directory": "packages/activity" 29 | }, 30 | "homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/activity", 31 | "publishConfig": { 32 | "access": "public" 33 | }, 34 | "files": [ 35 | "src", 36 | "lib" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /packages/activity/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src" 6 | }, 7 | "references": [{ "path": "../common" }], 8 | "include": ["./src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/client/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/client` 2 | 3 | [![NPM](https://img.shields.io/npm/v/@temporalio/client?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/client) 4 | 5 | Part of [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/). 6 | 7 | - [Client docs](https://docs.temporal.io/typescript/clients) 8 | - [API reference](https://typescript.temporal.io/api/namespaces/client) 9 | - [Sample projects](https://github.com/temporalio/samples-typescript) 10 | -------------------------------------------------------------------------------- /packages/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@temporalio/client", 3 | "version": "1.12.0-rc.0", 4 | "description": "Temporal.io SDK Client sub-package", 5 | "main": "lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "scripts": {}, 8 | "keywords": [ 9 | "temporal", 10 | "workflow", 11 | "client" 12 | ], 13 | "author": "Temporal Technologies Inc. ", 14 | "license": "MIT", 15 | "dependencies": { 16 | "@grpc/grpc-js": "^1.12.4", 17 | "@temporalio/common": "file:../common", 18 | "@temporalio/proto": "file:../proto", 19 | "abort-controller": "^3.0.0", 20 | "long": "^5.2.3", 21 | "uuid": "^9.0.1" 22 | }, 23 | "devDependencies": { 24 | "protobufjs": "^7.2.5" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/temporalio/sdk-typescript/issues" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/temporalio/sdk-typescript.git", 32 | "directory": "packages/client" 33 | }, 34 | "homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/client", 35 | "publishConfig": { 36 | "access": "public" 37 | }, 38 | "files": [ 39 | "src", 40 | "lib" 41 | ], 42 | "engines": { 43 | "node": ">= 18.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/client/src/pkg.ts: -------------------------------------------------------------------------------- 1 | // ../package.json is outside of the TS project rootDir which causes TS to complain about this import. 2 | // We do not want to change the rootDir because it messes up the output structure. 3 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 4 | // @ts-ignore 5 | import pkg from '../package.json'; 6 | 7 | export default pkg as { name: string; version: string }; 8 | -------------------------------------------------------------------------------- /packages/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src" 6 | }, 7 | "references": [{ "path": "../common" }], 8 | "include": ["./src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/cloud/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/cloud` 2 | 3 | [![NPM](https://img.shields.io/npm/v/@temporalio/cloud?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/cloud) 4 | 5 | Part of [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/). 6 | 7 | - [API reference](https://typescript.temporal.io/api/namespaces/cloud) 8 | - [Sample projects](https://github.com/temporalio/samples-typescript) 9 | -------------------------------------------------------------------------------- /packages/cloud/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@temporalio/cloud", 3 | "version": "1.12.0-rc.0", 4 | "description": "Temporal.io SDK — Temporal Cloud Client", 5 | "main": "lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "scripts": {}, 8 | "keywords": [ 9 | "temporal", 10 | "workflow", 11 | "client", 12 | "temporal cloud" 13 | ], 14 | "author": "Temporal Technologies Inc. ", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@grpc/grpc-js": "^1.12.4", 18 | "@temporalio/client": "file:../client", 19 | "@temporalio/common": "file:../common", 20 | "@temporalio/proto": "file:../proto", 21 | "abort-controller": "^3.0.0" 22 | }, 23 | "devDependencies": { 24 | "protobufjs": "^7.2.5" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/temporalio/sdk-typescript/issues" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/temporalio/sdk-typescript.git", 32 | "directory": "packages/cloud" 33 | }, 34 | "homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/cloud", 35 | "engines": { 36 | "node": ">= 18.0.0" 37 | }, 38 | "publishConfig": { 39 | "access": "public" 40 | }, 41 | "files": [ 42 | "src", 43 | "lib" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /packages/cloud/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @experimental The Temporal Cloud Operations Client API is an experimental feature and may be subject to change. 3 | */ 4 | export { 5 | CloudOperationsClient, 6 | CloudOperationsConnection, 7 | CloudOperationsConnectionOptions, 8 | CloudOperationsClientOptions, 9 | } from './cloud-operations-client'; 10 | -------------------------------------------------------------------------------- /packages/cloud/src/pkg.ts: -------------------------------------------------------------------------------- 1 | // ../package.json is outside of the TS project rootDir which causes TS to complain about this import. 2 | // We do not want to change the rootDir because it messes up the output structure. 3 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 4 | // @ts-ignore 5 | import pkg from '../package.json'; 6 | 7 | export default pkg as { name: string; version: string }; 8 | -------------------------------------------------------------------------------- /packages/cloud/src/types.ts: -------------------------------------------------------------------------------- 1 | import * as proto from '@temporalio/proto'; 2 | 3 | export type CloudService = proto.temporal.api.cloud.cloudservice.v1.CloudService; 4 | export const { CloudService } = proto.temporal.api.cloud.cloudservice.v1; 5 | -------------------------------------------------------------------------------- /packages/cloud/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src" 6 | }, 7 | "references": [{ "path": "../common" }], 8 | "include": ["./src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/common/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/common` 2 | 3 | [![NPM](https://img.shields.io/npm/v/@temporalio/common?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/common) 4 | 5 | Part of [Temporal](https://temporal.io)'s TypeScript SDK (see [docs](https://docs.temporal.io/typescript/introduction/) and [samples](https://github.com/temporalio/samples-typescript)). 6 | 7 | Common library for code that's used across the Client, Worker, and/or Workflow: 8 | 9 | - [DataConverter docs](https://docs.temporal.io/typescript/data-converters) 10 | - [Failure docs](https://docs.temporal.io/typescript/handling-failure) 11 | - [API reference](https://typescript.temporal.io/api/namespaces/common) 12 | -------------------------------------------------------------------------------- /packages/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@temporalio/common", 3 | "version": "1.12.0-rc.0", 4 | "description": "Common library for code that's used across the Client, Worker, and/or Workflow", 5 | "main": "lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "keywords": [ 8 | "temporal", 9 | "workflow", 10 | "worker" 11 | ], 12 | "author": "Temporal Technologies Inc. ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "@temporalio/proto": "file:../proto", 16 | "long": "^5.2.3", 17 | "ms": "^3.0.0-canary.1", 18 | "proto3-json-serializer": "^2.0.0" 19 | }, 20 | "devDependencies": { 21 | "protobufjs": "^7.2.5" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/temporalio/sdk-typescript/issues" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/temporalio/sdk-typescript.git", 29 | "directory": "packages/common" 30 | }, 31 | "homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/common", 32 | "publishConfig": { 33 | "access": "public" 34 | }, 35 | "engines": { 36 | "node": ">= 18.0.0" 37 | }, 38 | "files": [ 39 | "src", 40 | "lib" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /packages/common/src/converter/payload-codec.ts: -------------------------------------------------------------------------------- 1 | import { Payload } from '../interfaces'; 2 | 3 | /** 4 | * `PayloadCodec` is an optional step that happens between the wire and the {@link PayloadConverter}: 5 | * 6 | * Temporal Server <--> Wire <--> `PayloadCodec` <--> `PayloadConverter` <--> User code 7 | * 8 | * Implement this to transform an array of {@link Payload}s to/from the format sent over the wire and stored by Temporal Server. 9 | * Common transformations are encryption and compression. 10 | */ 11 | export interface PayloadCodec { 12 | /** 13 | * Encode an array of {@link Payload}s for sending over the wire. 14 | * @param payloads May have length 0. 15 | */ 16 | encode(payloads: Payload[]): Promise; 17 | 18 | /** 19 | * Decode an array of {@link Payload}s received from the wire. 20 | */ 21 | decode(payloads: Payload[]): Promise; 22 | } 23 | -------------------------------------------------------------------------------- /packages/common/src/converter/types.ts: -------------------------------------------------------------------------------- 1 | import { encode } from '../encoding'; 2 | 3 | export const METADATA_ENCODING_KEY = 'encoding'; 4 | export const encodingTypes = { 5 | METADATA_ENCODING_NULL: 'binary/null', 6 | METADATA_ENCODING_RAW: 'binary/plain', 7 | METADATA_ENCODING_JSON: 'json/plain', 8 | METADATA_ENCODING_PROTOBUF_JSON: 'json/protobuf', 9 | METADATA_ENCODING_PROTOBUF: 'binary/protobuf', 10 | } as const; 11 | export type EncodingType = (typeof encodingTypes)[keyof typeof encodingTypes]; 12 | 13 | export const encodingKeys = { 14 | METADATA_ENCODING_NULL: encode(encodingTypes.METADATA_ENCODING_NULL), 15 | METADATA_ENCODING_RAW: encode(encodingTypes.METADATA_ENCODING_RAW), 16 | METADATA_ENCODING_JSON: encode(encodingTypes.METADATA_ENCODING_JSON), 17 | METADATA_ENCODING_PROTOBUF_JSON: encode(encodingTypes.METADATA_ENCODING_PROTOBUF_JSON), 18 | METADATA_ENCODING_PROTOBUF: encode(encodingTypes.METADATA_ENCODING_PROTOBUF), 19 | } as const; 20 | 21 | export const METADATA_MESSAGE_TYPE_KEY = 'messageType'; 22 | -------------------------------------------------------------------------------- /packages/common/src/internal-non-workflow/codec-types.ts: -------------------------------------------------------------------------------- 1 | import type { Payload } from '../interfaces'; 2 | import type { ProtoFailure } from '../failure'; 3 | 4 | export interface EncodedPayload extends Payload { 5 | encoded: true; 6 | } 7 | 8 | export interface DecodedPayload extends Payload { 9 | decoded: true; 10 | } 11 | 12 | /** Replace `Payload`s with `EncodedPayload`s */ 13 | export type Encoded = ReplaceNested; 14 | 15 | /** Replace `Payload`s with `DecodedPayload`s */ 16 | export type Decoded = ReplaceNested; 17 | 18 | export type EncodedProtoFailure = Encoded; 19 | export type DecodedProtoFailure = Decoded; 20 | 21 | /** An object T with any nested values of type ToReplace replaced with ReplaceWith */ 22 | export type ReplaceNested = T extends (...args: any[]) => any 23 | ? T 24 | : [keyof T] extends [never] 25 | ? T 26 | : T extends Record // Special exception for nexusHeader. 27 | ? T 28 | : T extends { [k: string]: ToReplace } 29 | ? { 30 | [P in keyof T]: ReplaceNested; 31 | } 32 | : T extends ToReplace 33 | ? ReplaceWith | Exclude 34 | : { 35 | [P in keyof T]: ReplaceNested; 36 | }; 37 | -------------------------------------------------------------------------------- /packages/common/src/internal-non-workflow/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal SDK library: users should usually use other packages instead. Not included in Workflow bundle. 3 | * 4 | * @module 5 | */ 6 | export * from './codec-helpers'; 7 | export * from './codec-types'; 8 | export * from './data-converter-helpers'; 9 | export * from './parse-host-uri'; 10 | export * from './proxy-config'; 11 | export * from './tls-config'; 12 | -------------------------------------------------------------------------------- /packages/common/src/internal-workflow/index.ts: -------------------------------------------------------------------------------- 1 | export * from './enums-helpers'; 2 | export * from './objects-helpers'; 3 | -------------------------------------------------------------------------------- /packages/common/src/internal-workflow/objects-helpers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper to prevent `undefined` and `null` values overriding defaults when merging maps. 3 | */ 4 | export function filterNullAndUndefined>(obj: T): T { 5 | return Object.fromEntries(Object.entries(obj).filter(([_k, v]) => v != null)) as any; 6 | } 7 | 8 | /** 9 | * Merge two objects, possibly removing keys. 10 | * 11 | * More specifically: 12 | * - Any key/value pair in `delta` overrides the corresponding key/value pair in `original`; 13 | * - A key present in `delta` with value `undefined` removes the key from the resulting object; 14 | * - If `original` is `undefined` or empty, return `delta`; 15 | * - If `delta` is `undefined` or empty, return `original` (or undefined if `original` is also undefined); 16 | * - If there are no changes, then return `original`. 17 | */ 18 | export function mergeObjects>(original: T, delta: T | undefined): T; 19 | export function mergeObjects>( 20 | original: T | undefined, 21 | delta: T | undefined 22 | ): T | undefined { 23 | if (original == null) return delta; 24 | if (delta == null) return original; 25 | 26 | const merged: Record = { ...original }; 27 | let changed = false; 28 | for (const [k, v] of Object.entries(delta)) { 29 | if (v !== merged[k]) { 30 | if (v == null) delete merged[k]; 31 | else merged[k] = v; 32 | changed = true; 33 | } 34 | } 35 | 36 | return changed ? (merged as T) : original; 37 | } 38 | -------------------------------------------------------------------------------- /packages/common/src/protobufs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Entry point for classes and utilities related to using 3 | * {@link https://docs.temporal.io/typescript/data-converters#protobufs | Protobufs} for serialization. 4 | * 5 | * Import from `@temporalio/common/lib/protobufs`, for example: 6 | * 7 | * ``` 8 | * import { patchProtobufRoot } from '@temporalio/common/lib/protobufs'; 9 | * ``` 10 | * @module 11 | */ 12 | 13 | // Don't export from index, so we save space in Workflow bundles of users who don't use Protobufs 14 | export * from './converter/protobuf-payload-converters'; 15 | export { patchProtobufRoot } from '@temporalio/proto/lib/patch-protobuf-root'; 16 | -------------------------------------------------------------------------------- /packages/common/src/versioning-intent-enum.ts: -------------------------------------------------------------------------------- 1 | import type { coresdk } from '@temporalio/proto'; 2 | import type { VersioningIntent as VersioningIntentString } from './versioning-intent'; 3 | import { assertNever, checkExtends } from './type-helpers'; 4 | 5 | // Avoid importing the proto implementation to reduce workflow bundle size 6 | // Copied from coresdk.common.VersioningIntent 7 | /** 8 | * Protobuf enum representation of {@link VersioningIntentString}. 9 | * 10 | * @experimental The Worker Versioning API is still being designed. Major changes are expected. 11 | */ 12 | export enum VersioningIntent { 13 | UNSPECIFIED = 0, 14 | COMPATIBLE = 1, 15 | DEFAULT = 2, 16 | } 17 | 18 | checkExtends(); 19 | checkExtends(); 20 | 21 | export function versioningIntentToProto(intent: VersioningIntentString | undefined): VersioningIntent { 22 | switch (intent) { 23 | case 'DEFAULT': 24 | return VersioningIntent.DEFAULT; 25 | case 'COMPATIBLE': 26 | return VersioningIntent.COMPATIBLE; 27 | case undefined: 28 | return VersioningIntent.UNSPECIFIED; 29 | default: 30 | assertNever('Unexpected VersioningIntent', intent); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/common/src/versioning-intent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Indicates whether the user intends certain commands to be run on a compatible worker Build Id version or not. 3 | * 4 | * `COMPATIBLE` indicates that the command should run on a worker with compatible version if possible. It may not be 5 | * possible if the target task queue does not also have knowledge of the current worker's Build Id. 6 | * 7 | * `DEFAULT` indicates that the command should run on the target task queue's current overall-default Build Id. 8 | * 9 | * Where this type is accepted optionally, an unset value indicates that the SDK should choose the most sensible default 10 | * behavior for the type of command, accounting for whether the command will be run on the same task queue as the 11 | * current worker. The default behavior for starting Workflows is `DEFAULT`. The default behavior for Workflows starting 12 | * Activities, starting Child Workflows, or Continuing As New is `COMPATIBLE`. 13 | * 14 | * @experimental The Worker Versioning API is still being designed. Major changes are expected. 15 | */ 16 | export type VersioningIntent = 'COMPATIBLE' | 'DEFAULT'; 17 | -------------------------------------------------------------------------------- /packages/common/src/workflow-definition-options.ts: -------------------------------------------------------------------------------- 1 | import { VersioningBehavior } from './worker-deployments'; 2 | 3 | /** 4 | * Options that can be used when defining a workflow via {@link setWorkflowOptions}. 5 | */ 6 | export interface WorkflowDefinitionOptions { 7 | versioningBehavior?: VersioningBehavior; 8 | } 9 | 10 | type AsyncFunction = (...args: Args) => Promise; 11 | export type WorkflowDefinitionOptionsOrGetter = WorkflowDefinitionOptions | (() => WorkflowDefinitionOptions); 12 | 13 | /** 14 | * @internal 15 | * @hidden 16 | * A workflow function that has been defined with options from {@link WorkflowDefinitionOptions}. 17 | */ 18 | export interface WorkflowFunctionWithOptions extends AsyncFunction { 19 | workflowDefinitionOptions: WorkflowDefinitionOptionsOrGetter; 20 | } 21 | -------------------------------------------------------------------------------- /packages/common/src/workflow-handle.ts: -------------------------------------------------------------------------------- 1 | import { Workflow, WorkflowResultType, SignalDefinition } from './interfaces'; 2 | 3 | /** 4 | * Base WorkflowHandle interface, extended in workflow and client libs. 5 | * 6 | * Transforms a workflow interface `T` into a client interface. 7 | */ 8 | export interface BaseWorkflowHandle { 9 | /** 10 | * Promise that resolves when Workflow execution completes 11 | */ 12 | result(): Promise>; 13 | 14 | /** 15 | * Signal a running Workflow. 16 | * 17 | * @param def a signal definition as returned from {@link defineSignal} 18 | * 19 | * @example 20 | * ```ts 21 | * await handle.signal(incrementSignal, 3); 22 | * ``` 23 | */ 24 | signal( 25 | def: SignalDefinition | string, 26 | ...args: Args 27 | ): Promise; 28 | 29 | /** 30 | * The workflowId of the current Workflow 31 | */ 32 | readonly workflowId: string; 33 | } 34 | -------------------------------------------------------------------------------- /packages/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["./src/**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/core-bridge/.gitignore: -------------------------------------------------------------------------------- 1 | artifacts.json 2 | index.node 3 | target/ 4 | -------------------------------------------------------------------------------- /packages/core-bridge/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "temporal-sdk-typescript-bridge" 3 | version = "0.1.0" 4 | authors = ["Temporal Technologies Inc. "] 5 | license = "MIT" 6 | edition = "2024" 7 | repository = "https://github.com/temporalio/sdk-typescript" 8 | description = "Core Bridge for the TypeScript Temporal SDK" 9 | publish = false 10 | exclude = ["index.node", "sdk-core"] 11 | 12 | [lib] 13 | name = "temporal_sdk_typescript_bridge" 14 | crate-type = ["cdylib"] 15 | 16 | [profile.release] 17 | opt-level = 3 18 | debug = false 19 | lto = true 20 | incremental = false 21 | 22 | [dependencies] 23 | anyhow = "1.0" 24 | async-trait = "0.1.83" 25 | bridge-macros = { path = "bridge-macros" } 26 | futures = { version = "0.3", features = ["executor"] } 27 | neon = { version = "1.0.0", default-features = false, features = [ 28 | "napi-6", 29 | "futures", 30 | ] } 31 | opentelemetry = "0.29" 32 | os_pipe = "1.2.1" 33 | parking_lot = "0.12" 34 | prost = "0.13" 35 | prost-types = "0.13" 36 | serde = { version = "1.0", features = ["derive"] } 37 | serde_json = "1.0" 38 | temporal-sdk-core = { version = "*", path = "./sdk-core/core", features = [ 39 | "ephemeral-server", 40 | ] } 41 | temporal-client = { version = "*", path = "./sdk-core/client" } 42 | thiserror = "2" 43 | tokio = "1.13" 44 | tokio-stream = "0.1" 45 | tonic = "0.12" 46 | tracing = "0.1" 47 | tracing-subscriber = { version = "0.3", default-features = false, features = [ 48 | "parking_lot", 49 | "env-filter", 50 | "registry", 51 | "ansi", 52 | ] } 53 | -------------------------------------------------------------------------------- /packages/core-bridge/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/core-bridge` 2 | 3 | [![NPM](https://img.shields.io/npm/v/@temporalio/core-bridge?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/core-bridge) 4 | 5 | 6 | Part of [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/). 7 | 8 | > [!CAUTION] 9 | > This package is not intended to be used directly. Any API provided 10 | > by this package is internal and subject to change without notice. 11 | -------------------------------------------------------------------------------- /packages/core-bridge/bridge-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bridge-macros" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | syn = { version = "2.0", features = ["full", "extra-traits"] } 11 | quote = "1.0" 12 | proc-macro2 = "1.0" 13 | convert_case = "0.6" 14 | 15 | [dev-dependencies] 16 | temporal-sdk-typescript-bridge = { path = ".." } 17 | -------------------------------------------------------------------------------- /packages/core-bridge/src/helpers/future.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | 3 | use neon::{prelude::Context, result::JsResult, types::JsPromise}; 4 | use tracing::warn; 5 | 6 | use crate::helpers::{BridgeError, IntoThrow, TryIntoJs}; 7 | 8 | pub struct BridgeFuture { 9 | future: Pin> + Send + 'static>>, 10 | tokio_handle: tokio::runtime::Handle, 11 | } 12 | 13 | impl BridgeFuture { 14 | #[must_use] 15 | pub fn new( 16 | future: Pin> + Send + 'static>>, 17 | ) -> Self { 18 | Self { 19 | future, 20 | tokio_handle: tokio::runtime::Handle::current(), 21 | } 22 | } 23 | } 24 | 25 | impl TryIntoJs for BridgeFuture { 26 | type Output = JsPromise; 27 | 28 | fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsPromise> { 29 | let cx_channel = cx.channel(); 30 | let (deferred, promise) = cx.promise(); 31 | 32 | self.tokio_handle.spawn(async move { 33 | let result = self.future.await; 34 | let send_result = deferred.try_settle_with(&cx_channel, move |mut cx| { 35 | result.into_throw(&mut cx)?.try_into_js(&mut cx) 36 | }); 37 | if let Err(err) = send_result { 38 | warn!("Failed to complete JS Promise: {err:?}"); 39 | } 40 | }); 41 | 42 | Ok(promise) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/core-bridge/src/helpers/inspect.rs: -------------------------------------------------------------------------------- 1 | use neon::{context::Context, handle::Handle, prelude::*}; 2 | 3 | /// Print out a JS object to the console. 4 | /// 5 | /// Literally: `console.log(obj)`. 6 | pub fn log_js_object<'a, 'b, C: Context<'b>>( 7 | cx: &mut C, 8 | js_object: &Handle<'a, JsValue>, 9 | ) -> NeonResult<()> { 10 | let global = cx.global_object(); 11 | let console = global.get::(cx, "console")?; 12 | let log = console.get::(cx, "log")?; 13 | let args = vec![js_object.upcast()]; // Upcast js_object to JsValue 14 | 15 | log.call(cx, console, args)?; 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /packages/core-bridge/src/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod abort_controller; 2 | pub mod callbacks; 3 | pub mod errors; 4 | pub mod future; 5 | pub mod handles; 6 | pub mod inspect; 7 | pub mod json_string; 8 | pub mod properties; 9 | pub mod try_from_js; 10 | pub mod try_into_js; 11 | 12 | pub use abort_controller::{AbortController, AbortSignal}; 13 | pub use callbacks::{JsAsyncCallback, JsCallback}; 14 | pub use errors::{AppendFieldContext, BridgeError, BridgeResult, IntoThrow}; 15 | pub use future::BridgeFuture; 16 | pub use handles::{MutableFinalize, OpaqueInboundHandle, OpaqueOutboundHandle}; 17 | pub use json_string::JsonString; 18 | pub use properties::{FunctionContextExt as _, ObjectExt as _}; 19 | pub use try_from_js::TryFromJs; 20 | pub use try_into_js::TryIntoJs; 21 | -------------------------------------------------------------------------------- /packages/core-bridge/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn( 2 | clippy::pedantic, 3 | clippy::nursery, 4 | clippy::cargo, 5 | clippy::perf, 6 | clippy::style 7 | )] 8 | #![allow( 9 | clippy::missing_errors_doc, 10 | clippy::too_long_first_doc_paragraph, 11 | clippy::option_if_let_else, 12 | clippy::multiple_crate_versions, 13 | clippy::significant_drop_tightening 14 | )] 15 | 16 | pub mod helpers; 17 | 18 | mod client; 19 | mod logs; 20 | mod metrics; 21 | mod runtime; 22 | mod testing; 23 | mod worker; 24 | 25 | #[neon::main] 26 | fn main(mut cx: neon::prelude::ModuleContext) -> neon::prelude::NeonResult<()> { 27 | client::init(&mut cx)?; 28 | logs::init(&mut cx)?; 29 | metrics::init(&mut cx)?; 30 | runtime::init(&mut cx)?; 31 | testing::init(&mut cx)?; 32 | worker::init(&mut cx)?; 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /packages/core-bridge/ts/index.ts: -------------------------------------------------------------------------------- 1 | import * as native from './native'; 2 | import * as errors from './errors'; 3 | 4 | export { 5 | /** 6 | * @internal This module is not intended to be used directly. Any API provided 7 | * by this package is internal and subject to change without notice. 8 | * @hidden 9 | */ 10 | native, 11 | 12 | /** 13 | * @internal This module is not intended to be used directly. Any API provided 14 | * by this package is internal and subject to change without notice. 15 | * @hidden 16 | */ 17 | errors, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/core-bridge/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true, 5 | "outDir": "./lib", 6 | "rootDir": "./ts" 7 | }, 8 | "references": [{ "path": "../common" }], 9 | "include": ["./ts/**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/create-project/.npmignore: -------------------------------------------------------------------------------- 1 | *.test.ts 2 | *.test.js 3 | test/ -------------------------------------------------------------------------------- /packages/create-project/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2021 Temporal Technologies Inc. All Rights Reserved. 4 | 5 | Copyright (c) 2021 Vercel, Inc. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /packages/create-project/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/create` 2 | 3 | [![NPM](https://img.shields.io/npm/v/@temporalio/create?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/create) 4 | 5 | Project initializer for [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/). 6 | 7 | [`@temporalio/create` documentation](https://docs.temporal.io/typescript/package-initializer) 8 | -------------------------------------------------------------------------------- /packages/create-project/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { run } from './lib/index.js'; 3 | run(); 4 | -------------------------------------------------------------------------------- /packages/create-project/src/helpers/fetch-samples.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-named-as-default 2 | import got from 'got'; 3 | import { headers } from './headers.js'; 4 | 5 | const SAMPLE_REPO_CONTENTS = 'https://api.github.com/repos/temporalio/samples-typescript/contents/'; 6 | 7 | interface File { 8 | name: string; 9 | type: string; 10 | } 11 | 12 | export async function fetchSamples(): Promise { 13 | let response; 14 | 15 | try { 16 | // https://github.com/sindresorhus/got/blob/main/documentation/3-streams.md#response-1 17 | response = await got(SAMPLE_REPO_CONTENTS, { headers }); 18 | } catch (_error) { 19 | throw new Error(`Unable to reach github.com`); 20 | } 21 | 22 | const files = JSON.parse(response.body) as File[]; 23 | 24 | return files 25 | .filter((file) => file.type === 'dir' && file.name !== 'test' && !file.name.startsWith('.')) 26 | .map(({ name }) => name); 27 | } 28 | -------------------------------------------------------------------------------- /packages/create-project/src/helpers/get-error-code.ts: -------------------------------------------------------------------------------- 1 | interface ErrorWithCode { 2 | code: string; 3 | } 4 | 5 | export function getErrorCode(error: unknown): string { 6 | if ((error as ErrorWithCode).code !== undefined && typeof (error as ErrorWithCode).code === 'string') { 7 | return (error as ErrorWithCode).code; 8 | } else { 9 | return ''; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/create-project/src/helpers/headers.ts: -------------------------------------------------------------------------------- 1 | const { GITHUB_TOKEN } = process.env; 2 | 3 | if (GITHUB_TOKEN) { 4 | console.log(`Using GITHUB_TOKEN env var for downloading from GitHub`); 5 | } 6 | 7 | export const headers: Record = { 8 | 'User-Agent': '@temporalio/create project initializer', 9 | ...(GITHUB_TOKEN ? { Authorization: `Bearer ${GITHUB_TOKEN}` } : undefined), 10 | }; 11 | -------------------------------------------------------------------------------- /packages/create-project/src/helpers/is-online.ts: -------------------------------------------------------------------------------- 1 | // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/helpers/is-online.ts 2 | import { execSync } from 'node:child_process'; 3 | import dns from 'node:dns'; 4 | import { URL } from 'node:url'; 5 | 6 | // Look for any proxy the user might have configured on their machine 7 | function getProxy(): string | undefined { 8 | if (process.env.https_proxy) { 9 | return process.env.https_proxy; 10 | } 11 | 12 | try { 13 | const httpsProxy = execSync('npm config get https-proxy').toString().trim(); 14 | return httpsProxy !== 'null' ? httpsProxy : undefined; 15 | } catch (_e) { 16 | return; 17 | } 18 | } 19 | 20 | export function testIfThisComputerIsOnline(): Promise { 21 | return new Promise((resolve) => { 22 | dns.lookup('github.com', (registryErr) => { 23 | if (!registryErr) { 24 | return resolve(true); 25 | } 26 | 27 | // If we can't reach the registry directly, see if the user has a proxy 28 | // configured. If they do, see if the proxy is reachable. 29 | const proxy = getProxy(); 30 | if (!proxy) { 31 | return resolve(false); 32 | } 33 | 34 | const { hostname } = new URL(proxy); 35 | if (!hostname) { 36 | return resolve(false); 37 | } 38 | 39 | dns.lookup(hostname, (proxyErr) => { 40 | resolve(proxyErr == null); 41 | }); 42 | }); 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /packages/create-project/src/helpers/is-writeable.ts: -------------------------------------------------------------------------------- 1 | // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/helpers/is-writeable.ts 2 | import fs from 'node:fs'; 3 | 4 | import { getErrorCode } from './get-error-code.js'; 5 | 6 | export async function isWriteable(directory: string): Promise { 7 | try { 8 | await fs.promises.access(directory, (fs.constants || fs).W_OK); 9 | return true; 10 | } catch (error) { 11 | if (getErrorCode(error) === 'EACCES') { 12 | return false; 13 | } else { 14 | throw error; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/create-project/src/helpers/make-dir.ts: -------------------------------------------------------------------------------- 1 | // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/helpers/make-dir.ts 2 | import fs from 'node:fs'; 3 | 4 | export async function makeDir(root: string, options = { recursive: true }): Promise { 5 | await fs.promises.mkdir(root, options); 6 | } 7 | -------------------------------------------------------------------------------- /packages/create-project/src/helpers/strip-snip-comments.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { readFile, writeFile } from 'node:fs/promises'; 3 | import { sync } from 'glob'; 4 | 5 | export async function stripSnipComments(root: string): Promise { 6 | const files = sync('**/*.ts', { cwd: root }); 7 | await Promise.all( 8 | files.map(async (file) => { 9 | const filePath = path.join(root, file); 10 | const fileString = await readFile(filePath, 'utf8'); 11 | await writeFile(filePath, fileString.replace(/ *\/\/ @@@SNIP.+\n/g, '')); 12 | }) 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/create-project/src/helpers/subprocess.ts: -------------------------------------------------------------------------------- 1 | // https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options 2 | import { ChildProcess, spawn as origSpawn, SpawnOptions } from 'child_process'; 3 | 4 | export class ChildProcessError extends Error { 5 | public readonly name = 'ChildProcessError'; 6 | public command?: string; 7 | public args?: ReadonlyArray; 8 | 9 | constructor( 10 | message: string, 11 | public readonly code: number | null, 12 | public readonly signal: string | null 13 | ) { 14 | super(message); 15 | } 16 | } 17 | 18 | export async function spawn(command: string, args?: ReadonlyArray, options?: SpawnOptions): Promise { 19 | try { 20 | // Workaround @types/node - avoid choosing overloads per options.stdio variants 21 | await waitOnChild(options === undefined ? origSpawn(command, args) : origSpawn(command, args || [], options)); 22 | } catch (err) { 23 | if (err instanceof ChildProcessError) { 24 | err.command = command; 25 | err.args = args; 26 | } 27 | throw err; 28 | } 29 | } 30 | 31 | export async function waitOnChild(child: ChildProcess): Promise { 32 | return new Promise((resolve, reject) => { 33 | child.on('exit', (code, signal) => { 34 | if (code === 0) { 35 | resolve(); 36 | } else { 37 | reject(new ChildProcessError('Process failed', code, signal)); 38 | } 39 | }); 40 | child.on('error', reject); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /packages/create-project/src/helpers/validate-pkg.ts: -------------------------------------------------------------------------------- 1 | // Modified from: https://github.com/vercel/next.js/blob/2425f4703c4c6164cecfdb6aa8f80046213f0cc6/packages/create-next-app/helpers/validate-pkg.ts 2 | import validateProjectName from 'validate-npm-package-name'; 3 | 4 | export function validateNpmName(name: string): { 5 | valid: boolean; 6 | problems?: string[]; 7 | } { 8 | const nameValidation = validateProjectName(name); 9 | if (nameValidation.validForNewPackages) { 10 | return { valid: true }; 11 | } 12 | 13 | return { 14 | valid: false, 15 | problems: [...(nameValidation.errors || []), ...(nameValidation.warnings || [])], 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /packages/create-project/src/pkg.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from 'node:fs/promises'; 2 | import { URL } from 'node:url'; 3 | 4 | const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')); 5 | 6 | export default pkg as { name: string; version: string }; 7 | -------------------------------------------------------------------------------- /packages/create-project/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src", 6 | "resolveJsonModule": true, 7 | "module": "esnext" 8 | }, 9 | "include": ["./src/**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | docs/api 23 | typedoc-sidebar.js 24 | -------------------------------------------------------------------------------- /packages/docs/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/docs` 2 | 3 | Generates the API reference docs using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ## Local Development 6 | 7 | ```console 8 | npm start 9 | ``` 10 | 11 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 12 | 13 | ## Deployment 14 | 15 | The reference is built and deployed to Netlify under https://typescript.temporal.io during CI for branch `main` using GitHub Actions. 16 | -------------------------------------------------------------------------------- /packages/docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@temporalio/docs", 3 | "private": true, 4 | "scripts": { 5 | "docusaurus": "docusaurus", 6 | "start": "TYPEDOC_WATCH=true DOCUSAURUS_NO_DUPLICATE_TITLE_WARNING=false docusaurus start", 7 | "build-docs": "TYPEDOC_WATCH=false DOCUSAURUS_NO_DUPLICATE_TITLE_WARNING=false docusaurus build", 8 | "maybe-install-deps-and-build-docs": "(test -d node_modules || npm ci) && npm run build-docs", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^3.7.0", 18 | "@docusaurus/preset-classic": "^3.7.0", 19 | "docusaurus-plugin-snipsync": "^1.0.0", 20 | "docusaurus-plugin-typedoc": "^0.22.0", 21 | "dotenv": "^16.3.1", 22 | "glob": "^10.3.10", 23 | "prism-react-renderer": "^2.3.1", 24 | "react": "^18.2.0", 25 | "react-dom": "^18.2.0", 26 | "snipsync": "^1.11.0", 27 | "typedoc": "^0.25.7", 28 | "typedoc-plugin-markdown": "^3.17.1" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.5%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/docs/src/theme/DocBreadcrumbs/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useSidebarBreadcrumbs } from '@docusaurus/plugin-content-docs/client'; 3 | import { useHomePageRoute } from '@docusaurus/theme-common/internal'; 4 | import Link from '@docusaurus/Link'; 5 | 6 | export default function DocBreadcrumbs() { 7 | const breadcrumbs = useSidebarBreadcrumbs(); 8 | const homePageRoute = useHomePageRoute(); 9 | 10 | if (!breadcrumbs) { 11 | return null; 12 | } 13 | 14 | return ( 15 | 31 | ); 32 | } 33 | 34 | function getItemClassNames(breadcrumbs, idx) { 35 | const classes = ['breadcrumbs__item']; 36 | if (idx === breadcrumbs.length - 1) { 37 | classes.push('breadcrumbs__item--active'); 38 | } 39 | return classes.join(' '); 40 | } 41 | 42 | function getHrefFromItem(item) { 43 | return item.customProps?.breadcrumbLink || item.href; 44 | } 45 | -------------------------------------------------------------------------------- /packages/docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/temporalio/sdk-typescript/caae0a4eec4ed489c53f102b04f7d4afde318c5b/packages/docs/static/.nojekyll -------------------------------------------------------------------------------- /packages/docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/temporalio/sdk-typescript/caae0a4eec4ed489c53f102b04f7d4afde318c5b/packages/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /packages/docs/static/img/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/temporalio/sdk-typescript/caae0a4eec4ed489c53f102b04f7d4afde318c5b/packages/docs/static/img/social.png -------------------------------------------------------------------------------- /packages/interceptors-opentelemetry/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/interceptors-opentelemetry` 2 | 3 | [![NPM](https://img.shields.io/npm/v/@temporalio/interceptors-opentelemetry?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/interceptors-opentelemetry) 4 | 5 | [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction) interceptors for tracing Workflow and Activity executions with [OpenTelemetry](https://opentelemetry.io/). 6 | 7 | - [Interceptors docs](https://docs.temporal.io/typescript/interceptors) 8 | - [OpenTelemetry Interceptor example setup](https://github.com/temporalio/samples-typescript/tree/main/interceptors-opentelemetry) 9 | -------------------------------------------------------------------------------- /packages/interceptors-opentelemetry/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * `npm i @temporalio/interceptors-opentelemetry` 3 | * 4 | * Interceptors that add OpenTelemetry tracing. 5 | * 6 | * [Documentation](https://docs.temporal.io/typescript/logging#opentelemetry-tracing) 7 | * 8 | * @module 9 | */ 10 | 11 | export * from './workflow'; 12 | export * from './worker'; 13 | export { 14 | OpenTelemetryWorkflowClientInterceptor, 15 | /** deprecated: Use OpenTelemetryWorkflowClientInterceptor instead */ 16 | OpenTelemetryWorkflowClientInterceptor as OpenTelemetryWorkflowClientCallsInterceptor, 17 | } from './client'; 18 | -------------------------------------------------------------------------------- /packages/interceptors-opentelemetry/src/workflow/runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sets global variables required for importing opentelemetry in isolate 3 | * @module 4 | */ 5 | import { inWorkflowContext } from '@temporalio/workflow'; 6 | 7 | if (inWorkflowContext()) { 8 | // Required by opentelemetry (pretend to be a browser) 9 | Object.assign(globalThis, { 10 | performance: { 11 | timeOrigin: Date.now(), 12 | now() { 13 | return Date.now() - this.timeOrigin; 14 | }, 15 | }, 16 | window: globalThis, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /packages/interceptors-opentelemetry/src/workflow/span-exporter.ts: -------------------------------------------------------------------------------- 1 | import * as tracing from '@opentelemetry/sdk-trace-base'; 2 | import { ExportResult, ExportResultCode } from '@opentelemetry/core'; 3 | import * as wf from '@temporalio/workflow'; 4 | import { OpenTelemetrySinks, SerializableSpan } from './definitions'; 5 | 6 | const { exporter } = wf.proxySinks(); 7 | 8 | export class SpanExporter implements tracing.SpanExporter { 9 | public export(spans: tracing.ReadableSpan[], resultCallback: (result: ExportResult) => void): void { 10 | exporter.export(spans.map((span) => this.makeSerializable(span))); 11 | resultCallback({ code: ExportResultCode.SUCCESS }); 12 | } 13 | 14 | public async shutdown(): Promise { 15 | // Nothing to shut down 16 | } 17 | 18 | public makeSerializable(span: tracing.ReadableSpan): SerializableSpan { 19 | return { 20 | name: span.name, 21 | kind: span.kind, 22 | spanContext: span.spanContext(), 23 | parentSpanId: span.parentSpanId, 24 | startTime: span.startTime, 25 | endTime: span.endTime, 26 | status: span.status, 27 | attributes: span.attributes, 28 | links: span.links, 29 | events: span.events, 30 | duration: span.duration, 31 | ended: span.ended, 32 | droppedAttributesCount: span.droppedAttributesCount, 33 | droppedEventsCount: span.droppedEventsCount, 34 | droppedLinksCount: span.droppedLinksCount, 35 | instrumentationLibrary: span.instrumentationLibrary, 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/interceptors-opentelemetry/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src" 6 | }, 7 | "references": [{ "path": "../client" }, { "path": "../common" }, { "path": "../worker" }, { "path": "../workflow" }], 8 | "include": ["./src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/meta/README.md: -------------------------------------------------------------------------------- 1 | # `temporalio` 2 | 3 | ⚠️ This meta package is deprecated. We recommend installing the individual packages directly: 4 | 5 | ``` 6 | npm install @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/activity 7 | ``` 8 | 9 | For more information, see: 10 | 11 | - [Temporal.io](https://temporal.io/) 12 | - [docs.temporal.io](https://docs.temporal.io/) 13 | - [TypeScript SDK docs](https://docs.temporal.io/typescript/introduction/) 14 | - [TypeScript API reference](https://typescript.temporal.io/) -------------------------------------------------------------------------------- /packages/meta/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temporalio", 3 | "description": "Temporal.io SDK meta-package", 4 | "dependencies": { 5 | "@temporalio/activity": "file:../activity", 6 | "@temporalio/client": "file:../client", 7 | "@temporalio/common": "file:../common", 8 | "@temporalio/interceptors-opentelemetry": "file:../interceptors-opentelemetry", 9 | "@temporalio/proto": "file:../proto", 10 | "@temporalio/testing": "file:../testing", 11 | "@temporalio/worker": "file:../worker", 12 | "@temporalio/workflow": "file:../workflow" 13 | }, 14 | "author": "Temporal Technologies Inc. ", 15 | "license": "MIT", 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/temporalio/sdk-typescript.git" 19 | }, 20 | "keywords": [ 21 | "temporal", 22 | "workflow", 23 | "isolate" 24 | ], 25 | "bugs": { 26 | "url": "https://github.com/temporalio/sdk-typescript/issues" 27 | }, 28 | "homepage": "https://github.com/temporalio/sdk-typescript#readme", 29 | "publishConfig": { 30 | "access": "public" 31 | }, 32 | "files": [ 33 | "src", 34 | "lib" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/meta/src/index.ts: -------------------------------------------------------------------------------- 1 | // ORDER IS IMPORTANT! When a type is re-exported, TypeDoc will keep the first 2 | // one it encountered as canonical, and mark others as references to that one. 3 | export * as protobufs from '@temporalio/common/lib/protobufs'; 4 | export * as proto from '@temporalio/proto'; 5 | export * as common from '@temporalio/common'; 6 | export * as workflow from '@temporalio/workflow'; 7 | export * as activity from '@temporalio/activity'; 8 | export * as worker from '@temporalio/worker'; 9 | export * as client from '@temporalio/client'; 10 | export * as testing from '@temporalio/testing'; 11 | export * as opentelemetry from '@temporalio/interceptors-opentelemetry'; 12 | -------------------------------------------------------------------------------- /packages/meta/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true, 5 | "outDir": "./lib", 6 | "rootDir": "./src" 7 | }, 8 | "references": [ 9 | { "path": "../activity" }, 10 | { "path": "../client" }, 11 | { "path": "../common" }, 12 | { "path": "../worker" }, 13 | { "path": "../workflow" } 14 | ], 15 | "include": ["./src/**/*.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /packages/nyc-test-coverage/src/globalCoverage.ts: -------------------------------------------------------------------------------- 1 | import { CoverageMapData } from 'istanbul-lib-coverage'; 2 | 3 | declare global { 4 | // eslint-disable-next-line no-var 5 | var __coverage__: CoverageMapData; 6 | } 7 | 8 | export {}; 9 | -------------------------------------------------------------------------------- /packages/nyc-test-coverage/src/interceptors.ts: -------------------------------------------------------------------------------- 1 | import type { CoverageMapData } from 'istanbul-lib-coverage'; 2 | import { proxySinks, WorkflowInterceptors } from '@temporalio/workflow'; 3 | import { CoverageSinks } from './sinks'; 4 | 5 | const { coverage } = proxySinks(); 6 | 7 | // Export the interceptors 8 | export const interceptors = (): WorkflowInterceptors => ({ 9 | internals: [ 10 | { 11 | concludeActivation(input, next) { 12 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 13 | // @ts-ignore 14 | const globalCoverage: CoverageMapData = global.__coverage__; 15 | if (globalCoverage) { 16 | // Send coverage data through the sink to be merged into the _real_ global coverage map 17 | // Make a deep copy first, otherwise clearCoverage may wipe out the data 18 | // before it actually get processed by the sink 19 | coverage.merge(JSON.parse(JSON.stringify(globalCoverage))); 20 | clearCoverage(globalCoverage); 21 | } 22 | 23 | return next(input); 24 | }, 25 | }, 26 | ], 27 | }); 28 | 29 | function clearCoverage(coverage: CoverageMapData): void { 30 | for (const path of Object.keys(coverage)) { 31 | for (const index of Object.keys(coverage[path].s)) { 32 | coverage[path].s[index] = 0; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/nyc-test-coverage/src/loader.ts: -------------------------------------------------------------------------------- 1 | import { createInstrumenter } from 'istanbul-lib-instrument'; 2 | import * as convert from 'convert-source-map'; 3 | import type { LoaderDefinitionFunction } from 'webpack'; 4 | 5 | const instrumentWithIstanbulLoader: LoaderDefinitionFunction = function (source, sourceMap): void { 6 | let srcMap = sourceMap ?? convert.fromSource(source)?.sourcemap; 7 | if (typeof srcMap === 'string') srcMap = JSON.parse(srcMap); 8 | 9 | const instrumenter = createInstrumenter({ 10 | esModules: true, 11 | produceSourceMap: true, 12 | }); 13 | 14 | instrumenter.instrument( 15 | source, 16 | this.resourcePath, 17 | (error, instrumentedSource) => { 18 | this.callback(error, instrumentedSource, instrumenter.lastSourceMap() as any); 19 | }, 20 | srcMap as any 21 | ); 22 | }; 23 | 24 | export default instrumentWithIstanbulLoader; 25 | -------------------------------------------------------------------------------- /packages/nyc-test-coverage/src/sinks.ts: -------------------------------------------------------------------------------- 1 | import { Sinks } from '@temporalio/workflow'; 2 | 3 | export interface CoverageSinks extends Sinks { 4 | coverage: { 5 | merge(coverageMap: any): void; 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /packages/nyc-test-coverage/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src" 6 | }, 7 | "references": [{ "path": "../worker" }, { "path": "../workflow" }], 8 | "include": ["./src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/proto/.gitignore: -------------------------------------------------------------------------------- 1 | protos/json-module.js 2 | protos/root.d.ts -------------------------------------------------------------------------------- /packages/proto/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/proto` 2 | 3 | [![NPM](https://img.shields.io/npm/v/@temporalio/proto?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/proto) 4 | 5 | Part of [Temporal](https://temporal.io)'s TypeScript SDK (see [docs](https://docs.temporal.io/typescript/introduction/) and [samples](https://github.com/temporalio/samples-typescript)). 6 | 7 | You should usually not be using this package directly. Instead use: 8 | 9 | - [`@temporalio/client`](https://typescript.temporal.io/api/namespaces/client) 10 | - [`@temporalio/worker`](https://typescript.temporal.io/api/namespaces/worker) 11 | - [`@temporalio/workflow`](https://typescript.temporal.io/api/namespaces/workflow) 12 | - [`@temporalio/activity`](https://typescript.temporal.io/api/namespaces/activity) 13 | -------------------------------------------------------------------------------- /packages/proto/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@temporalio/proto", 3 | "version": "1.12.0-rc.0", 4 | "description": "Temporal.io SDK compiled protobuf definitions", 5 | "main": "protos/index.js", 6 | "types": "protos/index.d.ts", 7 | "files": [ 8 | "protos/", 9 | "src/", 10 | "lib/" 11 | ], 12 | "scripts": { 13 | "build": "node ./scripts/compile-proto.js" 14 | }, 15 | "keywords": [ 16 | "temporal", 17 | "workflow", 18 | "isolate" 19 | ], 20 | "author": "Temporal Technologies Inc. ", 21 | "license": "MIT", 22 | "dependencies": { 23 | "long": "^5.2.3", 24 | "protobufjs": "^7.2.5" 25 | }, 26 | "devDependencies": { 27 | "glob": "^10.3.10", 28 | "protobufjs-cli": "^1.1.2" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/temporalio/sdk-typescript/issues" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/temporalio/sdk-typescript.git", 36 | "directory": "packages/proto" 37 | }, 38 | "homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/proto", 39 | "publishConfig": { 40 | "access": "public" 41 | }, 42 | "engines": { 43 | "node": ">= 18.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/proto/protos/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./root'); 2 | -------------------------------------------------------------------------------- /packages/proto/protos/root.js: -------------------------------------------------------------------------------- 1 | // Workaround an issue that prevents protobufjs from loading 'long' in Yarn 3 PnP 2 | // https://github.com/protobufjs/protobuf.js/issues/1745#issuecomment-1200319399 3 | const $protobuf = require('protobufjs/light'); 4 | $protobuf.util.Long = require('long'); 5 | $protobuf.configure(); 6 | 7 | const { patchProtobufRoot } = require('../lib/patch-protobuf-root'); 8 | const unpatchedRoot = require('./json-module'); 9 | module.exports = patchProtobufRoot(unpatchedRoot); 10 | -------------------------------------------------------------------------------- /packages/proto/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["./src/**/*.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/test/history_files/cancel_fake_progress_history.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/temporalio/sdk-typescript/caae0a4eec4ed489c53f102b04f7d4afde318c5b/packages/test/history_files/cancel_fake_progress_history.bin -------------------------------------------------------------------------------- /packages/test/protos/messages.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message ProtoActivityInput { 4 | string name = 1; 5 | int32 age = 2; 6 | } 7 | 8 | message ProtoActivityResult { 9 | string sentence = 1; 10 | } 11 | 12 | message BinaryMessage { 13 | bytes data = 1; 14 | } -------------------------------------------------------------------------------- /packages/test/protos/namespaced-messages.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package foo.bar; 4 | 5 | message ProtoActivityInput { 6 | string name = 1; 7 | int32 age = 2; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /packages/test/protos/root.js: -------------------------------------------------------------------------------- 1 | const { patchProtobufRoot } = require('@temporalio/common/lib/protobufs'); 2 | const unpatchedRoot = require('./json-module'); 3 | module.exports = patchProtobufRoot(unpatchedRoot); 4 | -------------------------------------------------------------------------------- /packages/test/protos/uppercase.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package Uppercase; 4 | 5 | message UpperMessage { 6 | string name = 1; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /packages/test/src/activities/async-completer.ts: -------------------------------------------------------------------------------- 1 | import { Observer } from 'rxjs'; 2 | import { CompleteAsyncError, Context, Info } from '@temporalio/activity'; 3 | 4 | export interface Activities { 5 | completeAsync(): Promise; 6 | } 7 | 8 | export function createActivities(observer: Observer): Activities { 9 | return { 10 | async completeAsync() { 11 | observer.next(Context.current().info); 12 | throw new CompleteAsyncError(); 13 | }, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/test/src/activities/cancellable-fetch.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import type { AbortSignal as FetchAbortSignal } from 'node-fetch/externals'; 3 | import { Context } from '@temporalio/activity'; 4 | 5 | export async function cancellableFetch(url: string): Promise { 6 | const response = await fetch(url, { signal: Context.current().cancellationSignal as FetchAbortSignal }); 7 | const contentLengthHeader = response.headers.get('Content-Length'); 8 | if (contentLengthHeader === null) { 9 | throw new Error('expected Content-Length header to be set'); 10 | } 11 | const contentLength = parseInt(contentLengthHeader); 12 | let bytesRead = 0; 13 | const chunks: Buffer[] = []; 14 | 15 | for await (const chunk of response.body) { 16 | if (!(chunk instanceof Buffer)) { 17 | throw new TypeError('Expected Buffer'); 18 | } 19 | bytesRead += chunk.length; 20 | chunks.push(chunk); 21 | Context.current().heartbeat(bytesRead / contentLength); 22 | } 23 | return Buffer.concat(chunks); 24 | } 25 | -------------------------------------------------------------------------------- /packages/test/src/activities/create-concat-activity.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 2 | export function createConcatActivity(logs: string[]) { 3 | return { 4 | async concat(string1: string, string2: string): Promise { 5 | logs.push(`Activity${string1}${string2}`); 6 | return string1 + string2; 7 | }, 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /packages/test/src/activities/default-and-defined.ts: -------------------------------------------------------------------------------- 1 | import { activityInfo } from '@temporalio/activity'; 2 | 3 | export interface NameAndArgs { 4 | name: string; 5 | activityName?: string; 6 | args: any[]; 7 | } 8 | 9 | export async function definedActivity(...args: unknown[]): Promise { 10 | return { name: 'definedActivity', args }; 11 | } 12 | 13 | export default async function (...args: unknown[]): Promise { 14 | return { name: 'default', activityName: activityInfo().activityType, args }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/test/src/activities/fake-progress.ts: -------------------------------------------------------------------------------- 1 | import { Context } from '@temporalio/activity'; 2 | import { CancelledFailure } from '@temporalio/common'; 3 | 4 | export async function fakeProgress(sleepIntervalMs = 1000, numIters = 100): Promise { 5 | try { 6 | for (let progress = 1; progress <= numIters; ++progress) { 7 | // sleep for given interval or throw if Activity is cancelled 8 | await Context.current().sleep(sleepIntervalMs); 9 | Context.current().heartbeat(progress); 10 | } 11 | } catch (err) { 12 | if (err instanceof CancelledFailure) { 13 | // Cleanup 14 | } 15 | throw err; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/test/src/activities/helpers.ts: -------------------------------------------------------------------------------- 1 | import { OpenTelemetryWorkflowClientCallsInterceptor } from '@temporalio/interceptors-opentelemetry'; 2 | import { Client, WorkflowHandle } from '@temporalio/client'; 3 | import { QueryDefinition } from '@temporalio/common'; 4 | import { getContext } from './interceptors'; 5 | 6 | function getSchedulingWorkflowHandle(): WorkflowHandle { 7 | const { info, connection, dataConverter } = getContext(); 8 | const { workflowExecution } = info; 9 | const client = new Client({ 10 | connection, 11 | namespace: info.workflowNamespace, 12 | dataConverter, 13 | interceptors: { 14 | workflow: [new OpenTelemetryWorkflowClientCallsInterceptor()], 15 | }, 16 | }); 17 | return client.workflow.getHandle(workflowExecution.workflowId, workflowExecution.runId); 18 | } 19 | 20 | export async function signalSchedulingWorkflow(signalName: string): Promise { 21 | const handle = getSchedulingWorkflowHandle(); 22 | await handle.signal(signalName); 23 | } 24 | 25 | export async function queryOwnWf(queryDef: QueryDefinition, ...args: A): Promise { 26 | const handle = getSchedulingWorkflowHandle(); 27 | return await handle.query(queryDef, ...args); 28 | } 29 | -------------------------------------------------------------------------------- /packages/test/src/activities/interceptors.ts: -------------------------------------------------------------------------------- 1 | import * as activity from '@temporalio/activity'; 2 | import { ConnectionLike } from '@temporalio/client'; 3 | import { defaultDataConverter, LoadedDataConverter } from '@temporalio/common'; 4 | import { ActivityExecuteInput, ActivityInboundCallsInterceptor, Next } from '@temporalio/worker'; 5 | 6 | export class ConnectionInjectorInterceptor implements ActivityInboundCallsInterceptor { 7 | constructor( 8 | public readonly connection: ConnectionLike, 9 | public readonly dataConverter = defaultDataConverter 10 | ) {} 11 | async execute(input: ActivityExecuteInput, next: Next): Promise { 12 | Object.assign(activity.Context.current(), { 13 | connection: this.connection, 14 | dataConverter: this.dataConverter, 15 | }); 16 | return next(input); 17 | } 18 | } 19 | 20 | /** 21 | * Extend the basic activity Context 22 | */ 23 | export interface Context extends activity.Context { 24 | connection: ConnectionLike; 25 | dataConverter: LoadedDataConverter; 26 | } 27 | 28 | /** 29 | * Type "safe" helper to get a context with connection 30 | */ 31 | export function getContext(): Context { 32 | return activity.Context.current() as unknown as Context; 33 | } 34 | -------------------------------------------------------------------------------- /packages/test/src/debug-replayer.ts: -------------------------------------------------------------------------------- 1 | import { startDebugReplayer } from '@temporalio/worker'; 2 | 3 | startDebugReplayer({ 4 | workflowsPath: require.resolve('./workflows'), 5 | }); 6 | -------------------------------------------------------------------------------- /packages/test/src/deployment-versioning-no-annotations/index.ts: -------------------------------------------------------------------------------- 1 | import { setHandler, condition } from '@temporalio/workflow'; 2 | import { unblockSignal, versionQuery } from '../workflows'; 3 | 4 | export async function deploymentVersioning(): Promise { 5 | let doFinish = false; 6 | setHandler(unblockSignal, () => void (doFinish = true)); 7 | setHandler(versionQuery, () => 'v1'); 8 | await condition(() => doFinish); 9 | return 'version-v1'; 10 | } 11 | 12 | export default async function (): Promise { 13 | return 'dynamic'; 14 | } 15 | -------------------------------------------------------------------------------- /packages/test/src/deployment-versioning-v1/index.ts: -------------------------------------------------------------------------------- 1 | import { setHandler, condition, setWorkflowOptions, workflowInfo } from '@temporalio/workflow'; 2 | import { unblockSignal, versionQuery } from '../workflows'; 3 | 4 | setWorkflowOptions({ versioningBehavior: 'AUTO_UPGRADE' }, deploymentVersioning); 5 | export async function deploymentVersioning(): Promise { 6 | let doFinish = false; 7 | setHandler(unblockSignal, () => void (doFinish = true)); 8 | setHandler(versionQuery, () => 'v1'); 9 | await condition(() => doFinish); 10 | return 'version-v1'; 11 | } 12 | 13 | // Dynamic/default workflow handler 14 | setWorkflowOptions({ versioningBehavior: 'PINNED' }, module.exports.default); 15 | export default async function (): Promise { 16 | return 'dynamic'; 17 | } 18 | 19 | setWorkflowOptions(() => { 20 | // Need to ensure accessing workflow context still works in here 21 | workflowInfo(); 22 | return { 23 | versioningBehavior: 'PINNED', 24 | }; 25 | }, usesGetter); 26 | export async function usesGetter(): Promise { 27 | return 'usesGetter'; 28 | } 29 | -------------------------------------------------------------------------------- /packages/test/src/deployment-versioning-v2/index.ts: -------------------------------------------------------------------------------- 1 | import { setHandler, condition, setWorkflowOptions } from '@temporalio/workflow'; 2 | import { unblockSignal, versionQuery } from '../workflows'; 3 | 4 | setWorkflowOptions({ versioningBehavior: 'PINNED' }, deploymentVersioning); 5 | export async function deploymentVersioning(): Promise { 6 | let doFinish = false; 7 | setHandler(unblockSignal, () => void (doFinish = true)); 8 | setHandler(versionQuery, () => 'v2'); 9 | await condition(() => doFinish); 10 | return 'version-v2'; 11 | } 12 | -------------------------------------------------------------------------------- /packages/test/src/deployment-versioning-v3/index.ts: -------------------------------------------------------------------------------- 1 | import { setHandler, condition, setWorkflowOptions } from '@temporalio/workflow'; 2 | import { unblockSignal, versionQuery } from '../workflows'; 3 | 4 | setWorkflowOptions({ versioningBehavior: 'AUTO_UPGRADE' }, deploymentVersioning); 5 | export async function deploymentVersioning(): Promise { 6 | let doFinish = false; 7 | setHandler(unblockSignal, () => void (doFinish = true)); 8 | setHandler(versionQuery, () => 'v3'); 9 | await condition(() => doFinish); 10 | return 'version-v3'; 11 | } 12 | -------------------------------------------------------------------------------- /packages/test/src/load/nightly-scenarios.ts: -------------------------------------------------------------------------------- 1 | import { samplers, longHaul, longHistoriesWithSmallCache100Iters } from './all-scenarios'; 2 | 3 | export default { longHaul, longHistoriesWithSmallCache100Iters, ...samplers }; 4 | -------------------------------------------------------------------------------- /packages/test/src/load/run-all-nightly-scenarios.ts: -------------------------------------------------------------------------------- 1 | import scenarios from './nightly-scenarios'; 2 | import { runScenarios } from './all-scenarios'; 3 | 4 | runScenarios(scenarios); 5 | -------------------------------------------------------------------------------- /packages/test/src/load/run-all-stress-ci-scenarios.ts: -------------------------------------------------------------------------------- 1 | import * as scenarios from './stress-ci-scenarios'; 2 | import { runScenarios } from './all-scenarios'; 3 | 4 | runScenarios(scenarios); 5 | -------------------------------------------------------------------------------- /packages/test/src/load/stress-ci-scenarios.ts: -------------------------------------------------------------------------------- 1 | export { activityCancellation10kIters, queryWithSmallCache100Iters } from './all-scenarios'; 2 | -------------------------------------------------------------------------------- /packages/test/src/mock-internal-flags.ts: -------------------------------------------------------------------------------- 1 | import { getActivator } from '@temporalio/workflow/lib/global-attributes'; 2 | import { SdkFlag } from '@temporalio/workflow/lib/flags'; 3 | 4 | const defaultValueOverrides = new Map(); 5 | 6 | let mockInstalled = false; 7 | 8 | function maybeInstallMock() { 9 | if (mockInstalled) return; 10 | const activator = getActivator(); 11 | const originalHasFlag = activator.hasFlag.bind(activator); 12 | activator.hasFlag = (flag) => { 13 | const overridenDefaultValue = defaultValueOverrides.get(flag.id); 14 | if (overridenDefaultValue !== undefined) { 15 | flag = { id: flag.id, default: overridenDefaultValue, alternativeConditions: undefined }; 16 | } 17 | return originalHasFlag(flag); 18 | }; 19 | mockInstalled = true; 20 | } 21 | 22 | // Override the default value of an SDK flag. That is, assuming a workflow execution is not in 23 | // replay mode and that `f` has not already been recorded, then `hasFlag(f)` will return 24 | // `defaultValue`, and record the flag to history if `defaultValue` is `true`. 25 | export function overrideSdkInternalFlag(flag: SdkFlag, defaultValue: boolean): void { 26 | maybeInstallMock(); 27 | defaultValueOverrides.set(flag.id, defaultValue); 28 | } 29 | -------------------------------------------------------------------------------- /packages/test/src/mocks/workflows-with-node-dependencies/example/client.ts: -------------------------------------------------------------------------------- 1 | import dns from 'dns'; // DO NOT ADD node: prefix on this import 2 | 3 | export function exampleHeavyweightFunction(): void { 4 | dns.resolve('localhost', () => { 5 | /* ignore */ 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /packages/test/src/mocks/workflows-with-node-dependencies/example/index.ts: -------------------------------------------------------------------------------- 1 | export * from './client'; 2 | export * from './models'; 3 | -------------------------------------------------------------------------------- /packages/test/src/mocks/workflows-with-node-dependencies/example/models.ts: -------------------------------------------------------------------------------- 1 | export interface ExampleModel { 2 | someProperty: ExampleEnum; 3 | } 4 | 5 | export enum ExampleEnum { 6 | success = 'succcess', 7 | failure = 'failure', 8 | } 9 | 10 | export function extractProperty(obj: ExampleModel): ExampleEnum { 11 | return obj.someProperty; 12 | } 13 | -------------------------------------------------------------------------------- /packages/test/src/mocks/workflows-with-node-dependencies/issue-516.ts: -------------------------------------------------------------------------------- 1 | import { ExampleModel, ExampleEnum, extractProperty } from './example/index'; 2 | 3 | // Demonstrate issue #516 4 | export async function issue516(): Promise { 5 | const successModel: ExampleModel = { someProperty: ExampleEnum.success }; 6 | return extractProperty(successModel); 7 | } 8 | -------------------------------------------------------------------------------- /packages/test/src/payload-converters/failure-converter-bad-export.ts: -------------------------------------------------------------------------------- 1 | export const failureConverter = { toPayload: Function.prototype }; 2 | -------------------------------------------------------------------------------- /packages/test/src/payload-converters/payload-converter-bad-export.ts: -------------------------------------------------------------------------------- 1 | export const payloadConverter = { toPayload: Function.prototype }; 2 | -------------------------------------------------------------------------------- /packages/test/src/payload-converters/payload-converter-no-export.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,import/unambiguous */ 2 | null; 3 | -------------------------------------------------------------------------------- /packages/test/src/payload-converters/payload-converter-returns-undefined.ts: -------------------------------------------------------------------------------- 1 | import { Payload, PayloadConverter, ValueError } from '@temporalio/common'; 2 | import { decode } from '@temporalio/common/lib/encoding'; 3 | 4 | class TestPayloadConverter implements PayloadConverter { 5 | public toPayload(_value: unknown): Payload { 6 | return undefined as any; 7 | } 8 | 9 | public fromPayload(content: Payload): T { 10 | if (content.data === undefined || content.data === null) { 11 | throw new ValueError('Got payload with no data'); 12 | } 13 | return JSON.parse(decode(content.data)); 14 | } 15 | } 16 | 17 | export const payloadConverter = new TestPayloadConverter(); 18 | -------------------------------------------------------------------------------- /packages/test/src/payload-converters/payload-converter-throws-from-payload.ts: -------------------------------------------------------------------------------- 1 | import { encodingKeys, METADATA_ENCODING_KEY, Payload, PayloadConverter } from '@temporalio/common'; 2 | import { encode } from '@temporalio/common/lib/encoding'; 3 | 4 | class TestPayloadConverter implements PayloadConverter { 5 | public toPayload(value: unknown): Payload { 6 | return { 7 | metadata: { 8 | [METADATA_ENCODING_KEY]: encodingKeys.METADATA_ENCODING_JSON, 9 | }, 10 | data: encode(JSON.stringify(value)), 11 | }; 12 | } 13 | 14 | public fromPayload(_content: Payload): T { 15 | throw new Error('test fromPayload'); 16 | } 17 | } 18 | 19 | export const payloadConverter = new TestPayloadConverter(); 20 | -------------------------------------------------------------------------------- /packages/test/src/payload-converters/proto-payload-converter.ts: -------------------------------------------------------------------------------- 1 | import { DefaultPayloadConverterWithProtobufs } from '@temporalio/common/lib/protobufs'; 2 | import root, { foo } from '../../protos/root'; // eslint-disable-line import/default 3 | 4 | // Used in tests 5 | export const messageInstance = foo.bar.ProtoActivityInput.create({ name: 'Proto', age: 1 }); 6 | 7 | export const payloadConverter = new DefaultPayloadConverterWithProtobufs({ protobufRoot: root }); 8 | -------------------------------------------------------------------------------- /packages/test/src/run-a-worker.ts: -------------------------------------------------------------------------------- 1 | import arg from 'arg'; 2 | import { Worker, Runtime, DefaultLogger, LogLevel, makeTelemetryFilterString } from '@temporalio/worker'; 3 | import * as activities from './activities'; 4 | 5 | async function main() { 6 | const argv = arg({ 7 | '--log-level': String, 8 | }); 9 | if (argv['--log-level']) { 10 | const logLevel = argv['--log-level'].toUpperCase(); 11 | Runtime.install({ 12 | logger: new DefaultLogger(logLevel as LogLevel), 13 | telemetryOptions: { 14 | logging: { 15 | filter: makeTelemetryFilterString({ core: logLevel as LogLevel, other: logLevel as LogLevel }), 16 | forward: {}, 17 | }, 18 | }, 19 | }); 20 | } 21 | const worker = await Worker.create({ 22 | activities, 23 | workflowsPath: require.resolve('./workflows'), 24 | taskQueue: 'test', 25 | nonStickyToStickyPollRatio: 0.5, 26 | }); 27 | await worker.run(); 28 | console.log('Worker gracefully shutdown'); 29 | } 30 | 31 | main().then( 32 | () => void process.exit(0), 33 | (err) => { 34 | console.error(err); 35 | process.exit(1); 36 | } 37 | ); 38 | -------------------------------------------------------------------------------- /packages/test/src/run-a-workflow.ts: -------------------------------------------------------------------------------- 1 | import arg from 'arg'; 2 | import { NativeConnection } from '@temporalio/worker'; 3 | import { Connection, WorkflowClient } from '@temporalio/client'; 4 | import * as workflows from './workflows'; 5 | 6 | async function main() { 7 | const argv = arg({ 8 | '--workflow-id': String, 9 | '--use-native': Boolean, 10 | }); 11 | const [workflowType, ...argsRaw] = argv._; 12 | const args = argsRaw.map((v) => JSON.parse(v)); 13 | const workflowId = argv['--workflow-id'] ?? 'test'; 14 | const useNative = !!argv['--use-native']; 15 | if (!Object.prototype.hasOwnProperty.call(workflows, workflowType)) { 16 | throw new TypeError(`Invalid workflowType ${workflowType}`); 17 | } 18 | console.log('running', { workflowType, args }); 19 | 20 | const connection = useNative ? await NativeConnection.connect() : await Connection.connect(); 21 | const client = new WorkflowClient({ connection }); 22 | const result = await client.execute(workflowType, { 23 | workflowId, 24 | taskQueue: 'test', 25 | args, 26 | }); 27 | console.log('complete', { result }); 28 | } 29 | 30 | main().then( 31 | () => void process.exit(0), 32 | (err) => { 33 | console.error(err); 34 | process.exit(1); 35 | } 36 | ); 37 | -------------------------------------------------------------------------------- /packages/test/src/test-default-activity.ts: -------------------------------------------------------------------------------- 1 | import * as activities from './activities/default-and-defined'; 2 | import { helpers, makeTestFunction } from './helpers-integration'; 3 | import { workflowWithMaybeDefinedActivity } from './workflows/default-activity-wf'; 4 | 5 | const test = makeTestFunction({ workflowsPath: require.resolve('./workflows/default-activity-wf') }); 6 | 7 | test('Uses default activity if no matching activity exists', async (t) => { 8 | const { executeWorkflow, createWorker } = helpers(t); 9 | const worker = await createWorker({ 10 | activities, 11 | }); 12 | 13 | await worker.runUntil(async () => { 14 | const activityArgs = ['test', 'args']; 15 | const res = await executeWorkflow(workflowWithMaybeDefinedActivity, { 16 | args: [false, activityArgs], 17 | }); 18 | t.deepEqual(res, { name: 'default', activityName: 'nonExistentActivity', args: activityArgs }); 19 | }); 20 | }); 21 | 22 | test('Does not use default activity if matching activity exists', async (t) => { 23 | const { executeWorkflow, createWorker } = helpers(t); 24 | const worker = await createWorker({ 25 | activities, 26 | }); 27 | 28 | await worker.runUntil(async () => { 29 | const activityArgs = ['test', 'args']; 30 | const res = await executeWorkflow(workflowWithMaybeDefinedActivity, { 31 | args: [true, activityArgs], 32 | }); 33 | t.deepEqual(res, { name: 'definedActivity', args: activityArgs }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/test/src/test-logger.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { DefaultLogger, LogEntry } from '@temporalio/worker'; 3 | 4 | test('DefaultLogger logs messages according to configured level', (t) => { 5 | const logs: Array> = []; 6 | const log = new DefaultLogger('WARN', ({ level, message, meta }) => logs.push({ level, message, meta })); 7 | log.debug('hey', { a: 1 }); 8 | log.info('ho'); 9 | log.warn('lets', { a: 1 }); 10 | log.error('go'); 11 | t.deepEqual(logs, [ 12 | { level: 'WARN', message: 'lets', meta: { a: 1 } }, 13 | { level: 'ERROR', message: 'go', meta: undefined }, 14 | ]); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/test/src/test-patch-root.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { patchProtobufRoot } from '@temporalio/common/lib/protobufs'; 3 | 4 | test('patchRoot', (t) => { 5 | const type = new Type(); 6 | t.deepEqual((patchProtobufRoot({ nested: { type } }) as any).type, type); 7 | 8 | const bar = new Namespace({ BarMsg: new Type() }); 9 | const root = { 10 | nested: { 11 | foo: new Namespace({ 12 | Msg: new Type(), 13 | nested: { 14 | bar, 15 | }, 16 | }), 17 | }, 18 | }; 19 | t.like(patchProtobufRoot(root), { 20 | ...root, 21 | foo: { Msg: new Type(), bar: { BarMsg: new Type() }, nested: { bar } }, 22 | } as any); 23 | }); 24 | 25 | class Namespace { 26 | public static className = 'Namespace'; 27 | 28 | constructor(props: Record) { 29 | for (const key in props) { 30 | (this as any)[key] = props[key]; 31 | } 32 | } 33 | } 34 | 35 | class Type { 36 | public static className = 'Type'; 37 | } 38 | -------------------------------------------------------------------------------- /packages/test/src/test-server-options.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { normalizeTlsConfig } from '@temporalio/common/lib/internal-non-workflow'; 3 | 4 | test('normalizeTlsConfig turns null to undefined', (t) => { 5 | t.is(normalizeTlsConfig(null), undefined); 6 | }); 7 | 8 | test('normalizeTlsConfig turns false to undefined', (t) => { 9 | t.is(normalizeTlsConfig(false), undefined); 10 | }); 11 | 12 | test('normalizeTlsConfig turns 0 to undefined', (t) => { 13 | t.is(normalizeTlsConfig(0 as any), undefined); 14 | }); 15 | 16 | test('normalizeTlsConfig turns true to object', (t) => { 17 | t.deepEqual(normalizeTlsConfig(true), {}); 18 | }); 19 | 20 | test('normalizeTlsConfig turns 1 to object', (t) => { 21 | t.deepEqual(normalizeTlsConfig(1 as any), {}); 22 | }); 23 | 24 | test('normalizeTlsConfig passes through undefined', (t) => { 25 | t.is(normalizeTlsConfig(undefined), undefined); 26 | }); 27 | 28 | test('normalizeTlsConfig passes through object', (t) => { 29 | const cfg = { serverNameOverride: 'temporal' }; 30 | t.is(normalizeTlsConfig(cfg), cfg); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/test/src/test-time.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | // eslint-disable-next-line import/no-named-as-default 3 | import Long from 'long'; 4 | import { msToTs } from '@temporalio/common/lib/time'; 5 | 6 | test('msToTs converts to Timestamp', (t) => { 7 | t.deepEqual({ seconds: Long.fromInt(600), nanos: 0 }, msToTs('10 minutes')); 8 | }); 9 | 10 | test('msToTs converts number to Timestamp', (t) => { 11 | t.deepEqual({ seconds: Long.fromInt(42), nanos: 0 }, msToTs(42000)); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/test/src/test-worker-debug-mode.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { v4 as uuid4 } from 'uuid'; 3 | import { WorkflowClient } from '@temporalio/client'; 4 | import { defaultOptions } from './mock-native-worker'; 5 | import { RUN_INTEGRATION_TESTS, Worker } from './helpers'; 6 | import { successString } from './workflows'; 7 | 8 | if (RUN_INTEGRATION_TESTS) { 9 | test('Worker works in debugMode', async (t) => { 10 | // To debug Workflows with this worker run the test with `ava debug` and add breakpoints to your Workflows 11 | const taskQueue = 'debug-mode'; 12 | const worker = await Worker.create({ ...defaultOptions, taskQueue, debugMode: true }); 13 | const client = new WorkflowClient(); 14 | const result = await worker.runUntil( 15 | client.execute(successString, { 16 | workflowId: uuid4(), 17 | taskQueue, 18 | }) 19 | ); 20 | t.is(result, 'success'); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /packages/test/src/test-worker-exposes-abortcontroller.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { v4 as uuid4 } from 'uuid'; 3 | import { Client } from '@temporalio/client'; 4 | import { RUN_INTEGRATION_TESTS, Worker } from './helpers'; 5 | import { defaultOptions } from './mock-native-worker'; 6 | import { abortController } from './workflows'; 7 | 8 | if (RUN_INTEGRATION_TESTS) { 9 | test(`Worker runtime exposes AbortController as a global`, async (t) => { 10 | const worker = await Worker.create({ ...defaultOptions, taskQueue: 'test-worker-exposes-abortcontroller' }); 11 | const client = new Client(); 12 | const result = await worker.runUntil( 13 | client.workflow.execute(abortController, { 14 | args: [], 15 | taskQueue: 'test-worker-exposes-abortcontroller', 16 | workflowId: uuid4(), 17 | workflowExecutionTimeout: '5s', 18 | }) 19 | ); 20 | t.is(result, 'abort successful'); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /packages/test/src/test-worker-no-activities.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { v4 as uuid4 } from 'uuid'; 3 | import { WorkflowClient } from '@temporalio/client'; 4 | import { defaultOptions } from './mock-native-worker'; 5 | import { RUN_INTEGRATION_TESTS, Worker } from './helpers'; 6 | import { successString } from './workflows'; 7 | 8 | if (RUN_INTEGRATION_TESTS) { 9 | test('Worker functions when asked not to run Activities', async (t) => { 10 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 11 | const { activities, taskQueue, ...rest } = defaultOptions; 12 | const worker = await Worker.create({ taskQueue: 'only-workflows', ...rest }); 13 | const client = new WorkflowClient(); 14 | const result = await worker.runUntil( 15 | client.execute(successString, { 16 | workflowId: uuid4(), 17 | taskQueue: 'only-workflows', 18 | }) 19 | ); 20 | t.is(result, 'success'); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /packages/test/src/test-worker-no-workflows.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { v4 as uuid4 } from 'uuid'; 3 | import { WorkflowClient } from '@temporalio/client'; 4 | import { RUN_INTEGRATION_TESTS, Worker } from './helpers'; 5 | import { defaultOptions } from './mock-native-worker'; 6 | import { runActivityInDifferentTaskQueue } from './workflows'; 7 | 8 | if (RUN_INTEGRATION_TESTS) { 9 | test('Worker functions when asked not to run Workflows', async (t) => { 10 | const { activities } = defaultOptions; 11 | const workflowlessWorker = await Worker.create({ taskQueue: 'only-activities', activities }); 12 | const normalWorker = await Worker.create({ ...defaultOptions, taskQueue: 'also-workflows' }); 13 | const client = new WorkflowClient(); 14 | const result = await normalWorker.runUntil( 15 | workflowlessWorker.runUntil( 16 | client.execute(runActivityInDifferentTaskQueue, { 17 | args: ['only-activities'], 18 | taskQueue: 'also-workflows', 19 | workflowId: uuid4(), 20 | }) 21 | ) 22 | ); 23 | t.is(result, 'hi'); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /packages/test/src/test-workflow-unhandled-rejection-crash.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { v4 as uuid4 } from 'uuid'; 3 | import { UnexpectedError, Worker } from '@temporalio/worker'; 4 | import { WorkflowClient } from '@temporalio/client'; 5 | import { defaultOptions } from './mock-native-worker'; 6 | import { RUN_INTEGRATION_TESTS } from './helpers'; 7 | import { throwUnhandledRejection } from './workflows'; 8 | 9 | if (RUN_INTEGRATION_TESTS) { 10 | test('Worker crashes if workflow throws unhandled rejection that cannot be associated with a workflow run', async (t) => { 11 | // To debug Workflows with this worker run the test with `ava debug` and add breakpoints to your Workflows 12 | const taskQueue = `unhandled-rejection-crash-${uuid4()}`; 13 | const worker = await Worker.create({ ...defaultOptions, taskQueue }); 14 | const client = new WorkflowClient(); 15 | const handle = await client.start(throwUnhandledRejection, { 16 | workflowId: uuid4(), 17 | taskQueue, 18 | args: [{ crashWorker: true }], 19 | }); 20 | try { 21 | await t.throwsAsync(worker.run(), { 22 | instanceOf: UnexpectedError, 23 | message: 24 | 'Workflow Worker Thread exited prematurely: UnhandledRejectionError: ' + 25 | "Unhandled Promise rejection for unknown Workflow Run id='undefined': " + 26 | 'Error: error to crash the worker', 27 | }); 28 | t.is(worker.getState(), 'FAILED'); 29 | } finally { 30 | await handle.terminate(); 31 | } 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /packages/test/src/workflows/abort-controller.ts: -------------------------------------------------------------------------------- 1 | export async function abortController(): Promise { 2 | let aborted: string | null = null; 3 | 4 | const controller = new AbortController(); 5 | const { signal } = controller; 6 | 7 | const abortEventListener = () => { 8 | aborted = signal.reason; 9 | }; 10 | 11 | signal.addEventListener('abort', abortEventListener); 12 | controller.abort('abort successful'); 13 | signal.removeEventListener('abort', abortEventListener); 14 | 15 | return aborted; 16 | } 17 | -------------------------------------------------------------------------------- /packages/test/src/workflows/args-and-return.ts: -------------------------------------------------------------------------------- 1 | import { decode } from '@temporalio/common/lib/encoding'; 2 | import { ApplicationFailure } from '@temporalio/workflow'; 3 | 4 | export async function argsAndReturn(greeting: string, _skip: undefined, arr: Uint8Array): Promise { 5 | if (!(arr instanceof Uint8Array)) { 6 | throw ApplicationFailure.nonRetryable('Uint8Array not wrapped'); 7 | } 8 | const name = decode(arr); 9 | return `${greeting}, ${name}`; 10 | } 11 | -------------------------------------------------------------------------------- /packages/test/src/workflows/async-activity-completion-tester.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used in test-async-completion to schedule an activity 3 | * @module 4 | */ 5 | 6 | import { CancellationScope, ActivityCancellationType, proxyActivities, sleep } from '@temporalio/workflow'; 7 | import type { Activities } from '../activities/async-completer'; 8 | 9 | export async function runAnAsyncActivity(cancel?: boolean): Promise { 10 | const { completeAsync } = proxyActivities({ 11 | scheduleToCloseTimeout: '30 minutes', 12 | retry: { 13 | maximumAttempts: 1, 14 | }, 15 | cancellationType: ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, 16 | }); 17 | 18 | return await CancellationScope.cancellable(async () => { 19 | const promise = completeAsync(); 20 | if (cancel) { 21 | // So the activity can be scheduled 22 | await sleep(100); 23 | CancellationScope.current().cancel(); 24 | } 25 | return await promise; 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /packages/test/src/workflows/async-fail-signal.ts: -------------------------------------------------------------------------------- 1 | import { setHandler, sleep, ApplicationFailure } from '@temporalio/workflow'; 2 | import { failSignal } from './definitions'; 3 | 4 | export async function asyncFailSignalWorkflow(): Promise { 5 | setHandler(failSignal, async () => { 6 | await sleep(100); 7 | throw ApplicationFailure.nonRetryable('Signal failed'); 8 | }); 9 | // Don't complete to allow Workflow to be interrupted by fail() signal 10 | await sleep(100000); 11 | } 12 | -------------------------------------------------------------------------------- /packages/test/src/workflows/async-workflow.ts: -------------------------------------------------------------------------------- 1 | export async function asyncWorkflow(): Promise { 2 | return await (async () => 'async')(); 3 | } 4 | -------------------------------------------------------------------------------- /packages/test/src/workflows/cancel-activity-after-first-completion.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests that cancellation scopes are correctly associated when resumed after completion 3 | * 4 | * @module 5 | */ 6 | 7 | import { CancellationScope, CancelledFailure } from '@temporalio/workflow'; 8 | import { httpGet } from './configured-activities'; 9 | 10 | export async function cancelActivityAfterFirstCompletion(url: string): Promise { 11 | const promise = CancellationScope.nonCancellable(async () => { 12 | return [ 13 | await httpGet(url), 14 | await httpGet(url), // <-- This activity should still be shielded 15 | ]; 16 | }); 17 | try { 18 | return await Promise.race([promise, CancellationScope.current().cancelRequested]); 19 | } catch (err) { 20 | if (!(err instanceof CancelledFailure)) { 21 | throw err; 22 | } 23 | console.log('Workflow cancelled while waiting on non cancellable scope'); 24 | return await promise; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/test/src/workflows/cancel-fake-progress.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActivityCancellationType, 3 | proxyActivities, 4 | CancellationScope, 5 | isCancellation, 6 | setHandler, 7 | condition, 8 | } from '@temporalio/workflow'; 9 | import type * as activities from '../activities'; 10 | import { activityStartedSignal } from './definitions'; 11 | 12 | const { fakeProgress } = proxyActivities({ 13 | startToCloseTimeout: '200s', 14 | cancellationType: ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, 15 | }); 16 | 17 | export async function cancelFakeProgress(): Promise { 18 | let activityStarted = false; 19 | setHandler(activityStartedSignal, () => void (activityStarted = true)); 20 | try { 21 | await CancellationScope.cancellable(async () => { 22 | const promise = fakeProgress(); 23 | await condition(() => activityStarted); 24 | CancellationScope.current().cancel(); 25 | await promise; 26 | }); 27 | throw new Error('Activity completed instead of being cancelled'); 28 | } catch (err) { 29 | if (!isCancellation(err)) { 30 | throw err; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/test/src/workflows/cancel-http-request.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActivityCancellationType, 3 | proxyActivities, 4 | CancellationScope, 5 | isCancellation, 6 | setHandler, 7 | condition, 8 | } from '@temporalio/workflow'; 9 | import type * as activities from '../activities'; 10 | import { activityStartedSignal } from './definitions'; 11 | 12 | const { cancellableFetch } = proxyActivities({ 13 | startToCloseTimeout: '20s', 14 | heartbeatTimeout: '3s', 15 | cancellationType: ActivityCancellationType.WAIT_CANCELLATION_COMPLETED, 16 | }); 17 | 18 | export async function cancellableHTTPRequest(url: string): Promise { 19 | let activityStarted = false; 20 | setHandler(activityStartedSignal, () => void (activityStarted = true)); 21 | try { 22 | await CancellationScope.cancellable(async () => { 23 | const promise = cancellableFetch(url, true); 24 | await condition(() => activityStarted); 25 | CancellationScope.current().cancel(); 26 | await promise; 27 | }); 28 | } catch (err) { 29 | if (!isCancellation(err)) { 30 | throw err; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/test/src/workflows/cancel-requested-with-non-cancellable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Demonstrates how to make Workflow aware of cancellation while waiting on nonCancellable scope. 3 | * Used in the documentation site. 4 | */ 5 | // @@@SNIPSTART typescript-cancel-requested-with-non-cancellable 6 | import { CancellationScope, CancelledFailure, proxyActivities } from '@temporalio/workflow'; 7 | import type * as activities from '../activities'; 8 | 9 | const { httpGetJSON } = proxyActivities({ 10 | startToCloseTimeout: '10m', 11 | }); 12 | 13 | export async function resumeAfterCancellation(url: string): Promise { 14 | let result: any = undefined; 15 | const scope = new CancellationScope({ cancellable: false }); 16 | const promise = scope.run(() => httpGetJSON(url)); 17 | try { 18 | result = await Promise.race([scope.cancelRequested, promise]); 19 | } catch (err) { 20 | if (!(err instanceof CancelledFailure)) { 21 | throw err; 22 | } 23 | // Prevent Workflow from completing so Activity can complete 24 | result = await promise; 25 | } 26 | return result; 27 | } 28 | // @@@SNIPEND 29 | -------------------------------------------------------------------------------- /packages/test/src/workflows/cancel-timer-immediately-alternative-impl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Demonstrates the basics of cancellation scopes. 3 | * Alternative implementation with cancellation from an outer scope. 4 | * Used in the documentation site. 5 | */ 6 | // @@@SNIPSTART typescript-cancel-a-timer-from-workflow-alternative-impl 7 | import { CancelledFailure, CancellationScope, sleep } from '@temporalio/workflow'; 8 | 9 | export async function cancelTimerAltImpl(): Promise { 10 | try { 11 | const scope = new CancellationScope(); 12 | const promise = scope.run(() => sleep(1)); 13 | scope.cancel(); // <-- Cancel the timer created in scope 14 | await promise; // <-- Throws CancelledFailure 15 | } catch (e) { 16 | if (e instanceof CancelledFailure) { 17 | console.log('Timer cancelled 👍'); 18 | } else { 19 | throw e; // <-- Fail the workflow 20 | } 21 | } 22 | } 23 | // @@@SNIPEND 24 | -------------------------------------------------------------------------------- /packages/test/src/workflows/cancel-timer-immediately.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Demonstrates the basics of cancellation scopes. 3 | * Used in the documentation site. 4 | */ 5 | // @@@SNIPSTART typescript-cancel-a-timer-from-workflow 6 | import { CancelledFailure, CancellationScope, sleep } from '@temporalio/workflow'; 7 | 8 | export async function cancelTimer(): Promise { 9 | // Timers and Activities are automatically cancelled when their containing scope is cancelled. 10 | try { 11 | await CancellationScope.cancellable(async () => { 12 | const promise = sleep(1); // <-- Will be cancelled because it is attached to this closure's scope 13 | CancellationScope.current().cancel(); 14 | await promise; // <-- Promise must be awaited in order for `cancellable` to throw 15 | }); 16 | } catch (e) { 17 | if (e instanceof CancelledFailure) { 18 | console.log('Timer cancelled 👍'); 19 | } else { 20 | throw e; // <-- Fail the workflow 21 | } 22 | } 23 | } 24 | // @@@SNIPEND 25 | -------------------------------------------------------------------------------- /packages/test/src/workflows/cancel-workflow.ts: -------------------------------------------------------------------------------- 1 | import { ActivityFailure, CancellationScope, CancelledFailure } from '@temporalio/workflow'; 2 | import { httpGet } from './configured-activities'; 3 | 4 | export async function cancelWorkflow(url: string): Promise { 5 | // By default timers and activities are automatically cancelled when the workflow is cancelled 6 | // and will throw the original CancelledFailure 7 | try { 8 | return await httpGet(url); 9 | } catch (e) { 10 | if (!(e instanceof ActivityFailure && e.cause instanceof CancelledFailure)) { 11 | throw e; 12 | } 13 | try { 14 | // Activity throws because Workflow has been cancelled 15 | return await httpGet(url); 16 | } catch (e) { 17 | if (!(e instanceof CancelledFailure)) { 18 | throw e; 19 | } 20 | // Activity is allowed to complete because it's in a non cancellable scope 21 | return await CancellationScope.nonCancellable(async () => httpGet(url)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/test/src/workflows/cancellation-error-is-propagated.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests that CancelledError is propagated out of a CancellationScope. 3 | */ 4 | import { CancellationScope, sleep } from '@temporalio/workflow'; 5 | 6 | export async function cancellationErrorIsPropagated(): Promise { 7 | await CancellationScope.cancellable(async () => { 8 | const promise = sleep(0); 9 | CancellationScope.current().cancel(); 10 | await promise; 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /packages/test/src/workflows/cancellation-scopes-with-callbacks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Demonstrates how to use cancellation scopes with callbacks. 3 | * Used in the documentation site. 4 | */ 5 | // @@@SNIPSTART typescript-cancellation-scopes-with-callbacks 6 | import { CancellationScope } from '@temporalio/workflow'; 7 | 8 | function doSomething(callback: () => any) { 9 | setTimeout(callback, 10); 10 | } 11 | 12 | export async function cancellationScopesWithCallbacks(): Promise { 13 | await new Promise((resolve, reject) => { 14 | doSomething(resolve); 15 | CancellationScope.current().cancelRequested.catch(reject); 16 | }); 17 | } 18 | // @@@SNIPEND 19 | -------------------------------------------------------------------------------- /packages/test/src/workflows/cancellation-scopes.ts: -------------------------------------------------------------------------------- 1 | import { CancelledFailure, CancellationScope, sleep } from '@temporalio/workflow'; 2 | 3 | function sleepAndLogCancellation(cancellationExpected: boolean) { 4 | return async () => { 5 | try { 6 | await sleep(3); 7 | } catch (e) { 8 | // We still want to know the workflow was cancelled 9 | if (e instanceof CancelledFailure) { 10 | console.log(`Scope cancelled ${cancellationExpected ? '👍' : '👎'}`); 11 | } 12 | throw e; 13 | } 14 | }; 15 | } 16 | 17 | export async function cancellationScopes(): Promise { 18 | // First without cancellation 19 | await CancellationScope.cancellable(sleepAndLogCancellation(false)); 20 | 21 | // Test cancellation from workflow 22 | const scope1 = new CancellationScope(); 23 | const p1 = scope1.run(sleepAndLogCancellation(true)); 24 | const scope2 = new CancellationScope(); 25 | const p2 = scope2.run(sleepAndLogCancellation(false)); 26 | scope1.cancel(); 27 | try { 28 | await p1; 29 | console.log('Exception was not propagated 👎'); 30 | } catch (e) { 31 | if (e instanceof CancelledFailure) { 32 | console.log('Exception was propagated 👍'); 33 | } 34 | } 35 | await p2; 36 | console.log('Scope 2 was not cancelled 👍'); 37 | 38 | // Test workflow cancellation propagates 39 | try { 40 | await CancellationScope.cancellable(sleepAndLogCancellation(true)); 41 | console.log('Exception was not propagated 👎'); 42 | } catch (e) { 43 | if (e instanceof CancelledFailure) { 44 | console.log('Exception was propagated 👍'); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/test/src/workflows/child-and-noncancellable.ts: -------------------------------------------------------------------------------- 1 | import { CancellationScope, sleep } from '@temporalio/workflow'; 2 | 3 | export async function childAndNonCancellable(): Promise { 4 | const child = new CancellationScope(); 5 | const promise = child.run(async () => { 6 | await CancellationScope.nonCancellable(async () => { 7 | await sleep(5); 8 | console.log('Slept in non-cancellable scope 👍'); 9 | }); 10 | }); 11 | child.cancel(); 12 | await promise; 13 | } 14 | -------------------------------------------------------------------------------- /packages/test/src/workflows/child-workflow-sample.ts: -------------------------------------------------------------------------------- 1 | import { executeChild } from '@temporalio/workflow'; 2 | // successString is a workflow implementation like childWorkflowExample below. 3 | // It is called with no arguments and return the string "success". 4 | import { successString } from './success-string'; 5 | 6 | export async function childWorkflowExample(): Promise { 7 | return await executeChild(successString, { args: [] }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/test/src/workflows/condition-completion-race.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests that the workflow doesn't throw if condition resolves and expires in the same activation 3 | * 4 | * @module 5 | */ 6 | import { condition, setHandler } from '@temporalio/workflow'; 7 | import { unblockSignal } from './definitions'; 8 | 9 | export async function conditionRacer(): Promise { 10 | let blocked = true; 11 | setHandler(unblockSignal, () => void (blocked = false)); 12 | return await condition(() => !blocked, '1s'); 13 | } 14 | -------------------------------------------------------------------------------- /packages/test/src/workflows/condition-timeout-0.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prior to 1.5.0, `condition(fn, 0)` was treated the same as `condition(..., undefined)`, 3 | * which means that the condition would block indefinitely and would return undefined once 4 | * fn evaluates to true, rather than returning true or false. 5 | */ 6 | import { condition, setHandler, defineSignal } from '@temporalio/workflow'; 7 | 8 | export const aSignal = defineSignal('a'); 9 | export const bSignal = defineSignal('b'); 10 | 11 | export async function conditionTimeout0(): Promise { 12 | let counter = 0; 13 | 14 | let aSignalReceived = false; 15 | setHandler(aSignal, () => { 16 | aSignalReceived = true; 17 | }); 18 | 19 | let bSignalReceived = false; 20 | setHandler(bSignal, () => { 21 | bSignalReceived = true; 22 | }); 23 | 24 | const aResult = await condition(() => aSignalReceived, 0); 25 | if (aResult === true || aResult === undefined) counter += 1; 26 | 27 | // Do it a second time, so that we can validate that patching logic works 28 | const bResult = await condition(() => bSignalReceived, 0); 29 | if (bResult === true || bResult === undefined) counter += 10; 30 | 31 | return counter; 32 | } 33 | -------------------------------------------------------------------------------- /packages/test/src/workflows/condition.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests the condition function resolves as eagerly as possible including during external dependency resolution. 3 | * 4 | * @module 5 | */ 6 | import { condition, sleep, CancellationScope, isCancellation } from '@temporalio/workflow'; 7 | 8 | export async function conditionWaiter(): Promise { 9 | let x = 0; 10 | await Promise.all([ 11 | sleep(1).then(() => (x = 1)), 12 | condition(() => x === 1).then(() => (x = 2)), 13 | condition(() => x === 2), 14 | ]); 15 | 16 | if (await condition(() => x === 3, '1s')) { 17 | throw new Error('Condition returned true'); 18 | } 19 | 20 | try { 21 | await CancellationScope.cancellable(async () => { 22 | const p = condition(() => x === 3); 23 | CancellationScope.current().cancel(); 24 | await p; 25 | }); 26 | throw new Error('CancellationScope did not throw'); 27 | } catch (err) { 28 | if (!isCancellation(err)) { 29 | throw err; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/test/src/workflows/configured-activities.ts: -------------------------------------------------------------------------------- 1 | import { proxyActivities } from '@temporalio/workflow'; 2 | import * as activityInterfaces from '../activities'; 3 | 4 | const activities = proxyActivities({ 5 | startToCloseTimeout: '10m', 6 | }); 7 | 8 | export const { 9 | httpGet, 10 | echo, 11 | setup, 12 | cleanup, 13 | httpGetJSON, 14 | httpPostJSON, 15 | fakeProgress, 16 | waitForCancellation, 17 | throwAnError, 18 | cancellableFetch, 19 | progressiveSleep, 20 | } = activities; 21 | -------------------------------------------------------------------------------- /packages/test/src/workflows/continue-as-new-same-workflow.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests continueAsNew for the same Workflow from execute and signal handler 3 | * @module 4 | */ 5 | import { continueAsNew, CancellationScope, defineSignal, setHandler } from '@temporalio/workflow'; 6 | 7 | export const continueAsNewSignal = defineSignal('continueAsNew'); 8 | 9 | export async function continueAsNewSameWorkflow( 10 | continueFrom: 'execute' | 'signal' | 'none' = 'execute', 11 | continueTo: 'execute' | 'signal' | 'none' = 'signal' 12 | ): Promise { 13 | setHandler(continueAsNewSignal, async () => { 14 | await continueAsNew('none'); 15 | }); 16 | if (continueFrom === 'none') { 17 | return; 18 | } 19 | if (continueFrom === 'execute') { 20 | await continueAsNew(continueTo); 21 | } 22 | await CancellationScope.current().cancelRequested; 23 | } 24 | -------------------------------------------------------------------------------- /packages/test/src/workflows/continue-as-new-suggested.ts: -------------------------------------------------------------------------------- 1 | import { workflowInfo } from '@temporalio/workflow'; 2 | 3 | export async function continueAsNewSuggested(): Promise { 4 | return workflowInfo().continueAsNewSuggested; 5 | } 6 | -------------------------------------------------------------------------------- /packages/test/src/workflows/continue-as-new-to-different-workflow.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests continueAsNew to another Workflow 3 | * @module 4 | */ 5 | import { ContinueAsNewOptions, makeContinueAsNewFunc } from '@temporalio/workflow'; 6 | import { sleeper } from './sleep'; 7 | 8 | export async function continueAsNewToDifferentWorkflow( 9 | ms = 1, 10 | extraArgs?: Partial 11 | ): Promise { 12 | const continueAsNew = makeContinueAsNewFunc({ workflowType: 'sleeper', ...(extraArgs ?? {}) }); 13 | await continueAsNew(ms); 14 | } 15 | -------------------------------------------------------------------------------- /packages/test/src/workflows/core-issue-589.ts: -------------------------------------------------------------------------------- 1 | import * as wf from '@temporalio/workflow'; 2 | import { CustomLoggerSinks } from './log-sink-tester'; 3 | import { unblockSignal } from './definitions'; 4 | 5 | const { customLogger } = wf.proxySinks(); 6 | 7 | // Demo for https://github.com/temporalio/sdk-core/issues/589 8 | export async function coreIssue589(): Promise { 9 | wf.setHandler(wf.defineQuery('q'), () => { 10 | return 'not important'; 11 | }); 12 | 13 | let unblocked = false; 14 | wf.setHandler(unblockSignal, () => { 15 | unblocked = true; 16 | }); 17 | await wf.condition(() => unblocked, 10000); 18 | 19 | customLogger.info( 20 | `Checkpoint, replaying: ${wf.workflowInfo().unsafe.isReplaying}, hl: ${wf.workflowInfo().historyLength}` 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/test/src/workflows/date.ts: -------------------------------------------------------------------------------- 1 | const startTime = new Date().getTime(); 2 | 3 | export async function date(): Promise { 4 | console.log(startTime); 5 | console.log(Date.now()); 6 | console.log(new Date() instanceof Date); 7 | console.log(new Date(0) instanceof Date); 8 | console.log(new Date(0).toJSON() === '1970-01-01T00:00:00.000Z'); 9 | console.log(Date.UTC(1970, 0) === 0); 10 | console.log(Date.parse('1970-01-01') === 0); 11 | } 12 | -------------------------------------------------------------------------------- /packages/test/src/workflows/default-activity-wf.ts: -------------------------------------------------------------------------------- 1 | import { proxyActivities } from '@temporalio/workflow'; 2 | import { NameAndArgs } from '../activities/default-and-defined'; 3 | import type * as activities from '../activities/default-and-defined'; 4 | 5 | const { definedActivity, nonExistentActivity } = proxyActivities< 6 | typeof activities & { nonExistentActivity: (...args: unknown[]) => Promise } 7 | >({ 8 | startToCloseTimeout: '30 seconds', 9 | }); 10 | 11 | export async function workflowWithMaybeDefinedActivity( 12 | useDefinedActivity: boolean, 13 | activityArgs: unknown[] 14 | ): Promise { 15 | if (useDefinedActivity) { 16 | return await definedActivity(...activityArgs); 17 | } 18 | return await nonExistentActivity(...activityArgs); 19 | } 20 | -------------------------------------------------------------------------------- /packages/test/src/workflows/default-workflow-function.ts: -------------------------------------------------------------------------------- 1 | import { workflowInfo } from '@temporalio/workflow'; 2 | 3 | export interface WorkflowTypeAndArgs { 4 | handler: string; 5 | workflowType?: string; 6 | args: unknown[]; 7 | } 8 | 9 | export async function existing(...args: unknown[]): Promise { 10 | return { 11 | handler: 'existing', 12 | args, 13 | }; 14 | } 15 | 16 | export default async function (...args: unknown[]): Promise { 17 | return { 18 | handler: 'default', 19 | workflowType: workflowInfo().workflowType, 20 | args, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/test/src/workflows/deferred-resolve.ts: -------------------------------------------------------------------------------- 1 | export async function deferredResolve(): Promise { 2 | void new Promise((resolve) => resolve(2)).then((val) => console.log(val)); 3 | console.log(1); 4 | } 5 | -------------------------------------------------------------------------------- /packages/test/src/workflows/definitions.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-duplicate-imports */ 2 | import { defineQuery, defineSignal } from '@temporalio/workflow'; 3 | 4 | export const activityStartedSignal = defineSignal('activityStarted'); 5 | export const failSignal = defineSignal('fail'); 6 | export const failWithMessageSignal = defineSignal<[string]>('fail'); 7 | export const argsTestSignal = defineSignal<[number, string]>('argsTest'); 8 | export const unblockSignal = defineSignal('unblock'); 9 | export const versionQuery = defineQuery('version'); 10 | -------------------------------------------------------------------------------- /packages/test/src/workflows/deprecate-patch.ts: -------------------------------------------------------------------------------- 1 | import { deprecatePatch } from '@temporalio/workflow'; 2 | 3 | export async function deprecatePatchWorkflow(): Promise { 4 | deprecatePatch('my-change-id'); 5 | console.log('has change'); 6 | } 7 | -------------------------------------------------------------------------------- /packages/test/src/workflows/echo-binary-protobuf.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationFailure } from '@temporalio/common'; 2 | import type { BinaryMessage } from '../../protos/root'; 3 | 4 | export async function echoBinaryProtobuf(input: BinaryMessage): Promise { 5 | if (input.data instanceof Uint8Array) { 6 | return input; 7 | } 8 | throw ApplicationFailure.nonRetryable('input.data is not a Uint8Array'); 9 | } 10 | -------------------------------------------------------------------------------- /packages/test/src/workflows/fail-signal.ts: -------------------------------------------------------------------------------- 1 | import { setHandler, sleep, ApplicationFailure } from '@temporalio/workflow'; 2 | import { failSignal } from './definitions'; 3 | 4 | export async function failSignalWorkflow(): Promise { 5 | setHandler(failSignal, () => { 6 | throw ApplicationFailure.nonRetryable('Signal failed'); 7 | }); 8 | // Don't complete to allow Workflow to be interrupted by fail() signal 9 | await sleep(100000); 10 | } 11 | -------------------------------------------------------------------------------- /packages/test/src/workflows/fail-unless-signaled-before-start.ts: -------------------------------------------------------------------------------- 1 | import { defineSignal, setHandler } from '@temporalio/workflow'; 2 | 3 | export const someShallPassSignal = defineSignal('someShallPass'); 4 | 5 | export async function failUnlessSignaledBeforeStart(): Promise { 6 | let pass = false; 7 | setHandler(someShallPassSignal, () => void (pass = true)); 8 | if (!pass) { 9 | throw new Error('None shall pass'); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/test/src/workflows/fails-workflow-task.ts: -------------------------------------------------------------------------------- 1 | export async function failsWorkflowTask(): Promise { 2 | throw new Error('Intentionally failing workflow task'); 3 | } 4 | -------------------------------------------------------------------------------- /packages/test/src/workflows/global-overrides.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | 3 | /** 4 | * Test that we get meaningful errors when trying to use the non-deterministic 5 | * WeakRef and FinalizationRegistry constructors. 6 | * NOTE: the @ts-ignore comments in the constructor calls are there because 7 | * WeakRef and FinalizationRegistry isn't defined in es2020 (lib in 8 | * @tsconfig/node14). 9 | */ 10 | const startupErrors: string[] = []; 11 | 12 | try { 13 | // @ts-ignore 14 | new WeakRef(); 15 | } catch (err: any) { 16 | startupErrors.push(err.toString()); 17 | } 18 | 19 | export async function globalOverrides(): Promise { 20 | for (const err of startupErrors) { 21 | console.log(err); 22 | } 23 | try { 24 | // @ts-ignore 25 | new FinalizationRegistry(); 26 | } catch (err: any) { 27 | console.log(err.toString()); 28 | } 29 | try { 30 | // @ts-ignore 31 | new WeakRef(); 32 | } catch (err: any) { 33 | console.log(err.toString()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/test/src/workflows/handle-external-workflow-cancellation-while-activity-running.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 2 | /** 3 | * Demonstrates how to clean up after cancellation. 4 | * Used in the documentation site. 5 | */ 6 | // @@@SNIPSTART typescript-handle-external-workflow-cancellation-while-activity-running 7 | import { CancellationScope, proxyActivities, isCancellation } from '@temporalio/workflow'; 8 | import type * as activities from '../activities'; 9 | 10 | const { httpPostJSON, cleanup } = proxyActivities({ 11 | startToCloseTimeout: '10m', 12 | }); 13 | 14 | export async function handleExternalWorkflowCancellationWhileActivityRunning(url: string, data: any): Promise { 15 | try { 16 | await httpPostJSON(url, data); 17 | } catch (err) { 18 | if (isCancellation(err)) { 19 | console.log('Workflow cancelled'); 20 | // Cleanup logic must be in a nonCancellable scope 21 | // If we'd run cleanup outside of a nonCancellable scope it would've been cancelled 22 | // before being started because the Workflow's root scope is cancelled. 23 | await CancellationScope.nonCancellable(() => cleanup(url)); 24 | } 25 | throw err; // <-- Fail the Workflow 26 | } 27 | } 28 | // @@@SNIPEND 29 | -------------------------------------------------------------------------------- /packages/test/src/workflows/http.ts: -------------------------------------------------------------------------------- 1 | import { proxyActivities } from '@temporalio/workflow'; 2 | import type * as activities from '../activities'; 3 | 4 | const { httpGet } = proxyActivities({ 5 | startToCloseTimeout: '1 minute', 6 | }); 7 | 8 | export async function http(): Promise { 9 | return await httpGet('https://temporal.io'); 10 | } 11 | -------------------------------------------------------------------------------- /packages/test/src/workflows/importer.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from './sleep-impl'; 2 | 3 | export async function importer(): Promise { 4 | await sleep(10); 5 | console.log('slept'); 6 | } 7 | -------------------------------------------------------------------------------- /packages/test/src/workflows/internals-interceptor-example.ts: -------------------------------------------------------------------------------- 1 | import { proxySinks, WorkflowInterceptors, Sinks, sleep } from '@temporalio/workflow'; 2 | 3 | export interface MyLoggerSinks extends Sinks { 4 | logger: { 5 | log(event: string): void; 6 | }; 7 | } 8 | 9 | const { logger } = proxySinks(); 10 | 11 | export async function internalsInterceptorExample(): Promise { 12 | await sleep(10); 13 | } 14 | 15 | export const interceptors = (): WorkflowInterceptors => ({ 16 | internals: [ 17 | { 18 | activate(input, next) { 19 | logger.log(`activate: ${input.batchIndex}`); 20 | return next(input); 21 | }, 22 | }, 23 | { 24 | concludeActivation(input, next) { 25 | logger.log(`concludeActivation: ${input.commands.length}`); 26 | return next(input); 27 | }, 28 | }, 29 | ], 30 | inbound: [], 31 | outbound: [], 32 | }); 33 | -------------------------------------------------------------------------------- /packages/test/src/workflows/interrupt-signal.ts: -------------------------------------------------------------------------------- 1 | import { defineSignal, setHandler, ApplicationFailure } from '@temporalio/workflow'; 2 | 3 | export const interruptSignal = defineSignal<[string]>('interrupt'); 4 | 5 | export async function interruptableWorkflow(): Promise { 6 | // When this Promise is rejected Workflow execution will fail 7 | await new Promise((_resolve, reject) => { 8 | setHandler(interruptSignal, (reason) => reject(ApplicationFailure.retryable(reason))); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /packages/test/src/workflows/invalid-or-failed-queries.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests: 3 | * - SDK throws a DeterminismViolationError when a query handler returns a Promise 4 | * - Failed query 5 | * @module 6 | */ 7 | 8 | import { defineQuery, setHandler } from '@temporalio/workflow'; 9 | 10 | export const invalidAsyncQuery = defineQuery>('invalidAsyncMethod'); 11 | export const failQuery = defineQuery('fail'); 12 | 13 | export async function invalidOrFailedQueries(): Promise { 14 | setHandler(invalidAsyncQuery, async () => true); 15 | setHandler(failQuery, () => { 16 | throw new Error('fail'); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /packages/test/src/workflows/log-before-timing-out.ts: -------------------------------------------------------------------------------- 1 | import { log } from '@temporalio/workflow'; 2 | 3 | export async function logAndTimeout(): Promise { 4 | log.info('logging before getting stuck'); 5 | for (;;) { 6 | /* Workflow should never complete */ 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/test/src/workflows/log-sample.ts: -------------------------------------------------------------------------------- 1 | // @@@SNIPSTART typescript-logger-sink-workflow 2 | import * as wf from '@temporalio/workflow'; 3 | 4 | export async function logSampleWorkflow(): Promise { 5 | wf.log.info('Workflow execution started'); 6 | } 7 | // @@@SNIPEND 8 | -------------------------------------------------------------------------------- /packages/test/src/workflows/log-sink-tester.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Workflow used in test-sinks.ts to verify sink replay behavior 3 | * 4 | * Also tests workflow.taskInfo() 5 | * @module 6 | */ 7 | 8 | import * as wf from '@temporalio/workflow'; 9 | // @@@SNIPSTART typescript-logger-sink-interface 10 | import type { Sinks } from '@temporalio/workflow'; 11 | 12 | export interface CustomLoggerSinks extends Sinks { 13 | customLogger: { 14 | info(message: string): void; 15 | }; 16 | } 17 | // @@@SNIPEND 18 | 19 | const { customLogger } = wf.proxySinks(); 20 | 21 | export async function logSinkTester(): Promise { 22 | customLogger.info( 23 | `Workflow execution started, replaying: ${wf.workflowInfo().unsafe.isReplaying}, hl: ${ 24 | wf.workflowInfo().historyLength 25 | }` 26 | ); 27 | // We rely on this test to run with workflow cache disabled. This sleep() 28 | // therefore ends the current WFT, evicting the workflow from cache, and thus 29 | // causing replay of the first sink call. 30 | await wf.sleep(1); 31 | customLogger.info( 32 | `Workflow execution completed, replaying: ${wf.workflowInfo().unsafe.isReplaying}, hl: ${ 33 | wf.workflowInfo().historyLength 34 | }` 35 | ); 36 | } 37 | 38 | // eslint-disable-next-line deprecation/deprecation 39 | const { defaultWorkerLogger } = wf.proxySinks(); 40 | 41 | export async function useDepreatedLoggerSinkWorkflow(): Promise { 42 | defaultWorkerLogger.info('Log message from workflow', { workflowId: wf.workflowInfo().workflowId }); 43 | } 44 | -------------------------------------------------------------------------------- /packages/test/src/workflows/long-history-generator.ts: -------------------------------------------------------------------------------- 1 | import * as wf from '@temporalio/workflow'; 2 | import type * as activities from '../activities'; 3 | 4 | const { echo } = wf.proxyActivities({ startToCloseTimeout: '1s' }); 5 | 6 | /** 7 | * Simple workflow that generates a lot of sequential activities for testing long histories 8 | */ 9 | export async function longHistoryGenerator(numIterations = 200, pauseAfterCompletion = false): Promise { 10 | let i = 0; 11 | wf.setHandler(wf.defineQuery('iteration'), () => i); 12 | 13 | for (; i < numIterations; ++i) { 14 | await echo('hello'); 15 | } 16 | if (pauseAfterCompletion) { 17 | await wf.CancellationScope.current().cancelRequested; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/test/src/workflows/multiple-activities-single-timeout.ts: -------------------------------------------------------------------------------- 1 | // @@@SNIPSTART typescript-multiple-activities-single-timeout-workflow 2 | import { CancellationScope, proxyActivities } from '@temporalio/workflow'; 3 | import type * as activities from '../activities'; 4 | 5 | export function multipleActivitiesSingleTimeout(urls: string[], timeoutMs: number): Promise { 6 | const { httpGetJSON } = proxyActivities({ 7 | startToCloseTimeout: timeoutMs, 8 | }); 9 | 10 | // If timeout triggers before all activities complete 11 | // the Workflow will fail with a CancelledError. 12 | return CancellationScope.withTimeout(timeoutMs, () => Promise.all(urls.map((url) => httpGetJSON(url)))); 13 | } 14 | // @@@SNIPEND 15 | -------------------------------------------------------------------------------- /packages/test/src/workflows/nested-cancellation.ts: -------------------------------------------------------------------------------- 1 | // @@@SNIPSTART typescript-nested-cancellation-scopes 2 | import { CancellationScope, proxyActivities, isCancellation } from '@temporalio/workflow'; 3 | 4 | import type * as activities from '../activities'; 5 | 6 | const { setup, httpPostJSON, cleanup } = proxyActivities({ 7 | startToCloseTimeout: '10m', 8 | }); 9 | 10 | export async function nestedCancellation(url: string): Promise { 11 | await CancellationScope.cancellable(async () => { 12 | await CancellationScope.nonCancellable(() => setup()); 13 | try { 14 | await CancellationScope.withTimeout(1000, () => httpPostJSON(url, { some: 'data' })); 15 | } catch (err) { 16 | if (isCancellation(err)) { 17 | await CancellationScope.nonCancellable(() => cleanup(url)); 18 | } 19 | throw err; 20 | } 21 | }); 22 | } 23 | // @@@SNIPEND 24 | -------------------------------------------------------------------------------- /packages/test/src/workflows/noncancellable-awaited-in-root-scope.ts: -------------------------------------------------------------------------------- 1 | // @@@SNIPSTART typescript-noncancellable-awaited-in-root-scope 2 | import { CancellationScope, proxyActivities } from '@temporalio/workflow'; 3 | import type * as activities from '../activities'; 4 | 5 | const { httpGetJSON } = proxyActivities({ startToCloseTimeout: '10m' }); 6 | 7 | export async function nonCancellableAwaitedInRootScope(): Promise { 8 | let p: Promise | undefined = undefined; 9 | 10 | await CancellationScope.nonCancellable(async () => { 11 | p = httpGetJSON('http://example.com'); // <-- Start activity in nonCancellable scope without awaiting completion 12 | }); 13 | // Activity is shielded from cancellation even though it is awaited in the cancellable root scope 14 | return p; 15 | } 16 | // @@@SNIPEND 17 | -------------------------------------------------------------------------------- /packages/test/src/workflows/noncancellable-in-noncancellable.ts: -------------------------------------------------------------------------------- 1 | import { CancellationScope, sleep } from '@temporalio/workflow'; 2 | 3 | export async function nonCancellableInNonCancellable(): Promise { 4 | await CancellationScope.nonCancellable(async () => { 5 | const timer = sleep(2); 6 | const child = CancellationScope.nonCancellable(async () => { 7 | const promise = sleep(1); 8 | CancellationScope.current().cancel(); 9 | await promise; 10 | console.log('Timer 1 finished 👍'); 11 | }); 12 | await child; 13 | await timer; 14 | console.log('Timer 0 finished 👍'); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/test/src/workflows/noncancellable-shields-children.ts: -------------------------------------------------------------------------------- 1 | // @@@SNIPSTART typescript-noncancellable-shields-children 2 | import { CancellationScope, proxyActivities } from '@temporalio/workflow'; 3 | import type * as activities from '../activities'; 4 | 5 | const { httpGetJSON } = proxyActivities({ startToCloseTimeout: '10m' }); 6 | 7 | export async function nonCancellable(url: string): Promise { 8 | // Prevent Activity from being cancelled and await completion. 9 | // Note that the Workflow is completely oblivious and impervious to cancellation in this example. 10 | return CancellationScope.nonCancellable(() => httpGetJSON(url)); 11 | } 12 | // @@@SNIPEND 13 | -------------------------------------------------------------------------------- /packages/test/src/workflows/otel-interceptors.ts: -------------------------------------------------------------------------------- 1 | /** Not a workflow, just interceptors */ 2 | 3 | import { WorkflowInterceptors } from '@temporalio/workflow'; 4 | import { 5 | OpenTelemetryInboundInterceptor, 6 | OpenTelemetryOutboundInterceptor, 7 | OpenTelemetryInternalsInterceptor, 8 | } from '@temporalio/interceptors-opentelemetry/lib/workflow'; 9 | 10 | export const interceptors = (): WorkflowInterceptors => ({ 11 | inbound: [new OpenTelemetryInboundInterceptor()], 12 | outbound: [new OpenTelemetryOutboundInterceptor()], 13 | internals: [new OpenTelemetryInternalsInterceptor()], 14 | }); 15 | -------------------------------------------------------------------------------- /packages/test/src/workflows/partial-noncancelable.ts: -------------------------------------------------------------------------------- 1 | import { CancelledFailure, CancellationScope, sleep } from '@temporalio/workflow'; 2 | 3 | export async function partialNonCancellable(): Promise { 4 | try { 5 | await Promise.all([ 6 | CancellationScope.nonCancellable(async () => { 7 | await sleep(5); 8 | await sleep(1); 9 | }), 10 | (async () => { 11 | await sleep(3); 12 | await sleep(2); 13 | })(), 14 | ]); 15 | } catch (e) { 16 | if (e instanceof CancelledFailure) { 17 | console.log('Workflow cancelled'); 18 | } 19 | // Let the shielded sleep be triggered before completion 20 | await CancellationScope.nonCancellable(() => sleep(10)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/test/src/workflows/patch-and-condition-post-patch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Post-patched version of a workflow used to reproduce an issue with patch inside a condition. 3 | * 4 | * Uses a patched statement inside a condition to replicate a bug where the runtime could generate out of order 5 | * commands on replay. 6 | * 7 | * @module 8 | */ 9 | import * as wf from '@temporalio/workflow'; 10 | import { generateCommandSignal } from './patch-and-condition-pre-patch'; 11 | 12 | /** 13 | * Patches the workflow from ./patch-and-condition-pre-patch, adds a patched statement inside a condition. 14 | * 15 | */ 16 | export async function patchInCondition(): Promise { 17 | let numSignals = 0; 18 | // The signal handler here is important for the repro. 19 | // We use it so the workflow generates a command that will conflict with the patch. 20 | wf.setHandler(generateCommandSignal, async () => { 21 | // Ignore completion, it's irrelevant, just generate a command 22 | await wf.sleep('1s'); 23 | numSignals++; 24 | }); 25 | 26 | // Note that the condition always returns false, we don't want it to resolve the promise, just to using it to test the 27 | // edge case. 28 | await Promise.race([wf.sleep('3s'), wf.condition(() => wf.patched('irrelevant') && false)]); 29 | // This is here to ensure that the workflow does not complete in slow CI 30 | await wf.condition(() => numSignals === 2); 31 | } 32 | -------------------------------------------------------------------------------- /packages/test/src/workflows/patch-and-condition-pre-patch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Pre-patched version of a workflow used to reproduce an issue with patch inside a condition. 3 | * 4 | * @module 5 | */ 6 | import * as wf from '@temporalio/workflow'; 7 | 8 | export const generateCommandSignal = wf.defineSignal('generate-command'); 9 | 10 | /** 11 | * Unpatched version of the workflow - just sleep and set up our signal handler 12 | */ 13 | export async function patchInCondition(): Promise { 14 | // The signal handler here is important for the repro. 15 | // We use it so the workflow generates a command that will conflict with the patch. 16 | wf.setHandler(generateCommandSignal, async () => { 17 | // Ignore completion, it's irrelevant, just generate a command 18 | await wf.sleep('1s'); 19 | }); 20 | 21 | await wf.sleep('3s'); 22 | // This is here to ensure that the workflow does not complete in slow CI 23 | await wf.condition(() => false); 24 | } 25 | -------------------------------------------------------------------------------- /packages/test/src/workflows/patched-top-level.ts: -------------------------------------------------------------------------------- 1 | import { patched } from '@temporalio/workflow'; 2 | 3 | const startupErrors: string[] = []; 4 | 5 | try { 6 | patched('should-sleep'); 7 | } catch (err: any) { 8 | startupErrors.push(err.message); 9 | } 10 | 11 | export async function patchedTopLevel(): Promise { 12 | console.log(startupErrors); 13 | } 14 | -------------------------------------------------------------------------------- /packages/test/src/workflows/patched.ts: -------------------------------------------------------------------------------- 1 | import { patched, sleep } from '@temporalio/workflow'; 2 | 3 | export async function patchedWorkflow(): Promise { 4 | if (patched('my-change-id')) { 5 | console.log('has change'); 6 | } else { 7 | console.log('no change'); 8 | } 9 | await sleep(100); 10 | if (patched('my-change-id')) { 11 | console.log('has change 2'); 12 | } else { 13 | console.log('no change 2'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/test/src/workflows/priority.ts: -------------------------------------------------------------------------------- 1 | import { executeChild, proxyActivities, startChild, workflowInfo } from '@temporalio/workflow'; 2 | import type * as activities from '../activities'; 3 | 4 | const { echo } = proxyActivities({ startToCloseTimeout: '5s', priority: { priorityKey: 5 } }); 5 | 6 | export async function priorityWorkflow(stopAfterCheck: boolean, expectedPriority: number | undefined): Promise { 7 | const info = workflowInfo(); 8 | if (!info.priority) { 9 | throw new Error(`undefined priority`); 10 | } 11 | if (info.priority?.priorityKey !== expectedPriority) { 12 | throw new Error( 13 | `workflow priority ${info.priority?.priorityKey} doesn't match expected priority ${expectedPriority}` 14 | ); 15 | } 16 | if (stopAfterCheck) { 17 | return; 18 | } 19 | 20 | await executeChild(priorityWorkflow, { args: [true, 4], priority: { priorityKey: 4 } }); 21 | 22 | const child = await startChild(priorityWorkflow, { args: [true, 2], priority: { priorityKey: 2 } }); 23 | await child.result(); 24 | 25 | await echo('hi'); 26 | } 27 | -------------------------------------------------------------------------------- /packages/test/src/workflows/promise-all.ts: -------------------------------------------------------------------------------- 1 | export async function promiseAll(): Promise { 2 | // None promises 3 | console.log(...(await Promise.all([1, 2, 3]))); 4 | // Normal promises 5 | console.log(...(await Promise.all([1, 2, 3].map((v) => Promise.resolve(v))))); 6 | // From iterable 7 | console.log( 8 | ...(await Promise.all( 9 | new Map([ 10 | ['a', 1], 11 | ['b', 2], 12 | ['c', 3], 13 | ]).values() 14 | )) 15 | ); 16 | try { 17 | // Rejection 18 | await Promise.all([Promise.reject(new Error('wow')), 1, 2]); 19 | } catch (err: any) { 20 | console.log(err.message); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/test/src/workflows/promise-race.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from '@temporalio/workflow'; 2 | 3 | export async function promiseRace(): Promise { 4 | console.log(await Promise.race([1, 2, 3])); 5 | console.log(await Promise.race(new Set([1, 2, 3]).values())); 6 | console.log(await Promise.race([1, 2, 3].map((v) => Promise.resolve(v)))); 7 | console.log(await Promise.race([1, Promise.reject(new Error('wow'))])); 8 | console.log(await Promise.race([sleep(20).then(() => 20), sleep(30).then(() => 30)])); 9 | try { 10 | await Promise.race([Promise.reject(new Error('wow')), 1, 2]); 11 | } catch (err: any) { 12 | console.log(err.message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/test/src/workflows/promise-then-promise.ts: -------------------------------------------------------------------------------- 1 | export async function promiseThenPromise(): Promise { 2 | const res = await Promise.resolve(1).then((value) => new Promise((resolve) => resolve(value + 1))); 3 | console.log(res); 4 | } 5 | -------------------------------------------------------------------------------- /packages/test/src/workflows/protobufs.ts: -------------------------------------------------------------------------------- 1 | import { proxyActivities } from '@temporalio/workflow'; 2 | import type { ProtoActivityInput, ProtoActivityResult } from '../../protos/root'; 3 | import type * as activities from '../activities'; 4 | 5 | const { protoActivity } = proxyActivities({ startToCloseTimeout: '1s' }); 6 | 7 | export async function protobufWorkflow(args: ProtoActivityInput): Promise { 8 | return await protoActivity(args); 9 | } 10 | -------------------------------------------------------------------------------- /packages/test/src/workflows/race.ts: -------------------------------------------------------------------------------- 1 | import * as workflow from '@temporalio/workflow'; 2 | 3 | export async function race(): Promise { 4 | const p1 = Promise.resolve().then(() => console.log(1)); 5 | const p2 = workflow.sleep(10).then(() => console.log(2)); 6 | const p3 = workflow.sleep(11).then(() => console.log(3)); 7 | await Promise.all([p1, p2, p3]); 8 | } 9 | -------------------------------------------------------------------------------- /packages/test/src/workflows/random.ts: -------------------------------------------------------------------------------- 1 | import { uuid4, sleep } from '@temporalio/workflow'; 2 | 3 | export async function random(): Promise { 4 | console.log(Math.random()); 5 | console.log(uuid4()); 6 | await sleep(1); // Tester should update random seed before resolving this timer 7 | console.log(Math.random()); 8 | } 9 | -------------------------------------------------------------------------------- /packages/test/src/workflows/reject-promise.ts: -------------------------------------------------------------------------------- 1 | class CustomError extends Error { 2 | public readonly name: string = 'CustomError'; 3 | } 4 | 5 | export async function rejectPromise(): Promise { 6 | try { 7 | await new Promise((_, reject) => reject(new CustomError('abc'))); 8 | } catch (err) { 9 | console.log(err instanceof CustomError); 10 | } 11 | try { 12 | await Promise.reject(new CustomError('def')); 13 | } catch (err) { 14 | console.log(err instanceof CustomError); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/test/src/workflows/run-activity-in-different-task-queue.ts: -------------------------------------------------------------------------------- 1 | import { proxyActivities } from '@temporalio/workflow'; 2 | import type * as activities from '../activities'; 3 | 4 | export async function runActivityInDifferentTaskQueue(taskQueue: string): Promise { 5 | const { echo } = proxyActivities({ 6 | taskQueue, 7 | scheduleToCloseTimeout: '30 minutes', 8 | }); 9 | 10 | return await echo('hi'); 11 | } 12 | -------------------------------------------------------------------------------- /packages/test/src/workflows/scope-cancelled-while-waiting-on-external-workflow-cancellation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests that scope cancellation is propagated to the workflow while waiting on 3 | * external workflow cancellation (`await ExternalWorkflowHandle.cancel()`). 4 | * 5 | * This test was added along with the behavior fix where waiting for external 6 | * workflow cancellation was the only framework generated promise that was not 7 | * connected to a a cancellation scope. 8 | * 9 | * @module 10 | */ 11 | import { CancelledFailure } from '@temporalio/common'; 12 | import { CancellationScope, getExternalWorkflowHandle } from '@temporalio/workflow'; 13 | 14 | export async function scopeCancelledWhileWaitingOnExternalWorkflowCancellation(): Promise { 15 | try { 16 | await CancellationScope.cancellable(async () => { 17 | const handle = getExternalWorkflowHandle('irrelevant'); 18 | const promise = handle.cancel(); 19 | CancellationScope.current().cancel(); 20 | await promise; 21 | throw new Error('External cancellation was not cancelled'); 22 | }); 23 | throw new Error('Expected CancellationScope to throw CancelledFailure'); 24 | } catch (err) { 25 | if (!(err instanceof CancelledFailure)) { 26 | throw err; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/test/src/workflows/set-timeout-after-microtasks.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from '@temporalio/workflow'; 2 | 3 | async function delayedSleep(ms: number) { 4 | await Promise.resolve(); 5 | await sleep(ms); 6 | } 7 | 8 | export async function setTimeoutAfterMicroTasks(): Promise { 9 | await delayedSleep(100); 10 | console.log('slept'); 11 | } 12 | -------------------------------------------------------------------------------- /packages/test/src/workflows/shared-cancellation-scopes.ts: -------------------------------------------------------------------------------- 1 | // @@@SNIPSTART typescript-shared-cancellation-scopes 2 | import { CancellationScope, proxyActivities } from '@temporalio/workflow'; 3 | import type * as activities from '../activities'; 4 | 5 | const { httpGetJSON } = proxyActivities({ startToCloseTimeout: '10m' }); 6 | 7 | export async function sharedCancellationScopes(): Promise { 8 | // Start activities in the root scope 9 | const p1 = httpGetJSON('http://url1.ninja'); 10 | const p2 = httpGetJSON('http://url2.ninja'); 11 | 12 | const cancellableScope = CancellationScope.cancellable(async () => { 13 | const first = await Promise.race([p1, p2]); 14 | // Does not cancel activity1 or activity2 as they're linked to the root scope 15 | CancellationScope.current().cancel(); 16 | return first; 17 | }); 18 | return await cancellableScope; 19 | // The Activity that did not complete will effectively be cancelled when 20 | // Workflow completes unless the Activity is awaited: 21 | // await Promise.all([p1, p2]); 22 | } 23 | // @@@SNIPEND 24 | -------------------------------------------------------------------------------- /packages/test/src/workflows/signal-handlers-clear.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Workflow used by integration-tests.ts: signal handlers can be cleared 3 | * 4 | * @module 5 | */ 6 | 7 | import { setHandler, sleep } from '@temporalio/workflow'; 8 | import { unblockSignal } from './definitions'; 9 | 10 | export async function signalHandlersCanBeCleared(): Promise { 11 | let counter = 0; 12 | 13 | // Give time for signals to be received and buffered BEFORE the handler is configured. See issue #612 for details. 14 | await sleep('20ms'); 15 | 16 | // Each handler below should catch a single occurence of the unblock signal. 17 | // sleep() between each to make sure that any pending microtask has been run before the next handler is set 18 | // If everything went ok, counter should equals 111 at the end. 19 | 20 | setHandler(unblockSignal, () => { 21 | counter += 1; 22 | setHandler(unblockSignal, undefined); 23 | }); 24 | 25 | await sleep('1ms'); 26 | 27 | setHandler(unblockSignal, () => { 28 | counter += 10; 29 | setHandler(unblockSignal, undefined); 30 | }); 31 | 32 | await sleep('1ms'); 33 | 34 | setHandler(unblockSignal, () => { 35 | counter += 100; 36 | }); 37 | 38 | return counter; 39 | } 40 | -------------------------------------------------------------------------------- /packages/test/src/workflows/signal-query-patch-post-patch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Post-patched version of a workflow used to reproduce an issue with signal+query+patch 3 | * 4 | * @module 5 | */ 6 | import * as wf from '@temporalio/workflow'; 7 | 8 | export async function patchQuerySignal(): Promise { 9 | let enteredPatchBlock = false; 10 | 11 | wf.setHandler(wf.defineQuery('query'), () => true); 12 | wf.setHandler(wf.defineSignal('signal'), () => { 13 | // This block should not execute, since the patch did not get registered when the signal was first handled. 14 | if (wf.patched('should_never_be_set')) { 15 | enteredPatchBlock = true; 16 | } 17 | }); 18 | 19 | let blocked = true; 20 | wf.setHandler(wf.defineSignal('unblock'), () => { 21 | blocked = false; 22 | }); 23 | await wf.condition(() => !blocked); 24 | 25 | return enteredPatchBlock; 26 | } 27 | -------------------------------------------------------------------------------- /packages/test/src/workflows/signal-query-patch-pre-patch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Pre-patched version of a workflow used to reproduce an issue with signal+query+patch 3 | * 4 | * @module 5 | */ 6 | import * as wf from '@temporalio/workflow'; 7 | 8 | export async function patchQuerySignal(): Promise { 9 | wf.setHandler(wf.defineQuery('query'), () => true); 10 | wf.setHandler(wf.defineSignal('signal'), () => { 11 | // Nothing. In post-patch version, there will be a block here, surrounded by a patch check. 12 | }); 13 | 14 | let blocked = true; 15 | wf.setHandler(wf.defineSignal('unblock'), () => { 16 | blocked = false; 17 | }); 18 | await wf.condition(() => !blocked); 19 | 20 | throw new Error('Execution should not reach this point in pre-patch version'); 21 | } 22 | -------------------------------------------------------------------------------- /packages/test/src/workflows/signal-target.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Workflow that can be failed and unblocked using signals. 3 | * Useful for testing Workflow interactions. 4 | * 5 | * @module 6 | */ 7 | import { condition, setHandler } from '@temporalio/workflow'; 8 | import { argsTestSignal, failWithMessageSignal, unblockSignal } from './definitions'; 9 | 10 | export async function signalTarget(): Promise { 11 | let unblocked = false; 12 | 13 | // Verify arguments are sent correctly 14 | setHandler(argsTestSignal, (num, str) => { 15 | if (!(num === 123 && str === 'kid')) { 16 | throw new Error('Invalid arguments'); 17 | } 18 | }); 19 | setHandler(failWithMessageSignal, (message) => { 20 | throw new Error(message); 21 | }); 22 | setHandler(unblockSignal, () => void (unblocked = true)); 23 | 24 | await condition(() => unblocked); 25 | } 26 | -------------------------------------------------------------------------------- /packages/test/src/workflows/signal-update-ordering.ts: -------------------------------------------------------------------------------- 1 | import * as wf from '@temporalio/workflow'; 2 | 3 | export const fooSignal = wf.defineSignal('fooSignal'); 4 | export const fooUpdate = wf.defineUpdate('fooUpdate'); 5 | 6 | // Repro for https://github.com/temporalio/sdk-typescript/issues/1474 7 | export async function signalUpdateOrderingWorkflow(): Promise { 8 | let numFoos = 0; 9 | wf.setHandler(fooSignal, () => { 10 | numFoos++; 11 | }); 12 | wf.setHandler(fooUpdate, () => { 13 | numFoos++; 14 | return numFoos; 15 | }); 16 | await wf.condition(() => numFoos > 1); 17 | return numFoos; 18 | } 19 | -------------------------------------------------------------------------------- /packages/test/src/workflows/signals-are-always-processed.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Workflow used by test-signals-are-always-processed.ts 3 | * 4 | * @module 5 | */ 6 | 7 | import { proxySinks, Sinks, setHandler, defineSignal } from '@temporalio/workflow'; 8 | 9 | export const incrementSignal = defineSignal('increment'); 10 | 11 | export interface SignalProcessTestSinks extends Sinks { 12 | controller: { 13 | sendSignal(): void; 14 | }; 15 | } 16 | 17 | const { controller } = proxySinks(); 18 | 19 | export async function signalsAreAlwaysProcessed(): Promise { 20 | let counter = 0; 21 | // We expect the signal to be buffered by the runtime and delivered as soon 22 | // as we set the handler. 23 | setHandler(incrementSignal, () => void ++counter); 24 | // Use a sink to block the workflow from completing and send a signal 25 | if (counter === 0) { 26 | controller.sendSignal(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/test/src/workflows/sinks.ts: -------------------------------------------------------------------------------- 1 | import { proxySinks, Sinks } from '@temporalio/workflow'; 2 | 3 | export interface TestSinks extends Sinks { 4 | success: { 5 | runAsync(counter: number): void; 6 | runSync(counter: number): void; 7 | }; 8 | error: { 9 | throwAsync(counter: number): void; 10 | throwSync(counter: number): void; 11 | }; 12 | } 13 | 14 | const { success, error } = proxySinks(); 15 | 16 | export async function sinksWorkflow(): Promise { 17 | let i = 0; 18 | success.runSync(i++); 19 | success.runAsync(i++); 20 | error.throwSync(i++); 21 | error.throwAsync(i++); 22 | } 23 | -------------------------------------------------------------------------------- /packages/test/src/workflows/sleep-impl.ts: -------------------------------------------------------------------------------- 1 | export async function sleep(ms: number): Promise { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/test/src/workflows/sleep.ts: -------------------------------------------------------------------------------- 1 | // @@@SNIPSTART typescript-sleep-workflow 2 | import { sleep } from '@temporalio/workflow'; 3 | 4 | export async function sleeper(ms = 100): Promise { 5 | await sleep(ms); 6 | console.log('slept'); 7 | } 8 | // @@@SNIPEND 9 | -------------------------------------------------------------------------------- /packages/test/src/workflows/stack-tracer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Workflow used in integration-tests: `Stack trace query returns stack that makes sense` 3 | * @module 4 | */ 5 | import * as wf from '@temporalio/workflow'; 6 | import type { EnhancedStackTrace } from '@temporalio/workflow/lib/interfaces'; 7 | import type * as activities from '../activities'; 8 | import { unblockOrCancel } from './unblock-or-cancel'; 9 | 10 | const { queryOwnWf } = wf.proxyActivities({ 11 | startToCloseTimeout: '1 minute', 12 | }); 13 | 14 | export async function stackTracer(): Promise<[string, string]> { 15 | const { executeChild, sleep } = wf; 16 | const trigger = new wf.Trigger(); 17 | const [first] = await Promise.all([ 18 | trigger, 19 | Promise.race([ 20 | queryOwnWf(wf.stackTraceQuery).then((stack) => trigger.resolve(stack)), 21 | executeChild(unblockOrCancel), 22 | sleep(100_000), 23 | ]), 24 | ]); 25 | const second = await queryOwnWf(wf.stackTraceQuery); 26 | return [first, second]; 27 | } 28 | 29 | export async function enhancedStackTracer(): Promise { 30 | const { executeChild, sleep } = wf; 31 | const trigger = new wf.Trigger(); 32 | const [enhStack] = await Promise.all([ 33 | trigger, 34 | Promise.race([ 35 | queryOwnWf(wf.enhancedStackTraceQuery).then((stack) => trigger.resolve(stack)), 36 | executeChild(unblockOrCancel), 37 | sleep(100_000), 38 | ]), 39 | ]); 40 | return enhStack; 41 | } 42 | -------------------------------------------------------------------------------- /packages/test/src/workflows/success-string.ts: -------------------------------------------------------------------------------- 1 | export async function successString(): Promise { 2 | return 'success'; 3 | } 4 | -------------------------------------------------------------------------------- /packages/test/src/workflows/swc.ts: -------------------------------------------------------------------------------- 1 | interface Constructor { 2 | new (): any; 3 | } 4 | 5 | function sealed(constructor: Constructor) { 6 | Object.seal(constructor); 7 | Object.seal(constructor.prototype); 8 | } 9 | 10 | function decorate() { 11 | return function (_target: any, _propertyKey: string, _descriptor: PropertyDescriptor) { 12 | // empty 13 | }; 14 | } 15 | 16 | @sealed 17 | class ExampleClass { 18 | @decorate() 19 | method() { 20 | return 'hi'; 21 | } 22 | } 23 | 24 | export async function swc(): Promise { 25 | new ExampleClass().method(); 26 | } 27 | -------------------------------------------------------------------------------- /packages/test/src/workflows/tasks-and-microtasks.ts: -------------------------------------------------------------------------------- 1 | // Taken from https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ 2 | export async function tasksAndMicrotasks(): Promise { 3 | console.log('script start'); 4 | 5 | const p1 = new Promise((resolve) => { 6 | setTimeout(function () { 7 | console.log('setTimeout'); 8 | resolve(undefined); 9 | }, 0); 10 | }); 11 | 12 | const p2 = Promise.resolve() 13 | .then(function () { 14 | console.log('promise1'); 15 | }) 16 | .then(function () { 17 | console.log('promise2'); 18 | }); 19 | 20 | console.log('script end'); 21 | await Promise.all([p1, p2]); 22 | } 23 | -------------------------------------------------------------------------------- /packages/test/src/workflows/text-encoder-decoder.ts: -------------------------------------------------------------------------------- 1 | import { TextEncoder as TextEncoderFromImport, TextDecoder as TextDecoderFromImport } from 'util'; 2 | 3 | export async function textEncoderDecoder(text: string): Promise { 4 | // we don't import these - they are exposed as globals 5 | const encoder = new TextEncoder(); 6 | const decoder = new TextDecoder(); 7 | const encoded = encoder.encode(text); 8 | const decoded = decoder.decode(encoded); 9 | return decoded; 10 | } 11 | 12 | export async function textEncoderDecoderFromImport(text: string): Promise { 13 | const encoder = new TextEncoderFromImport(); 14 | const decoder = new TextDecoderFromImport(); 15 | const encoded = encoder.encode(text); 16 | const decoded = decoder.decode(encoded); 17 | return decoded; 18 | } 19 | -------------------------------------------------------------------------------- /packages/test/src/workflows/throw-async.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationFailure } from '@temporalio/workflow'; 2 | 3 | export async function throwAsync(variant: 'retryable' | 'nonRetryable' = 'nonRetryable'): Promise { 4 | throw ApplicationFailure[variant]('failure'); 5 | } 6 | -------------------------------------------------------------------------------- /packages/test/src/workflows/trailing-timer.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from '@temporalio/workflow'; 2 | 3 | export async function trailingTimer(): Promise { 4 | return await Promise.race([ 5 | sleep(1).then(() => 'first'), 6 | sleep(1).then(() => { 7 | // This generates a command that will **not** be executed 8 | void sleep(0); 9 | return 'second'; 10 | }), 11 | ]); 12 | } 13 | -------------------------------------------------------------------------------- /packages/test/src/workflows/try-to-continue-after-completion.ts: -------------------------------------------------------------------------------- 1 | import { continueAsNew, ApplicationFailure } from '@temporalio/workflow'; 2 | 3 | /** 4 | * Verifies that Workflow does not generate any more commands after it is considered complete 5 | */ 6 | export async function tryToContinueAfterCompletion(): Promise { 7 | await Promise.race([ 8 | // Note that continueAsNew only throws after microtasks and as a result, loses the race 9 | Promise.resolve().then(() => continueAsNew()), 10 | Promise.reject(ApplicationFailure.nonRetryable('fail before continue')), 11 | ]); 12 | } 13 | -------------------------------------------------------------------------------- /packages/test/src/workflows/two-strings.ts: -------------------------------------------------------------------------------- 1 | import { proxyActivities, proxySinks, Sinks } from '@temporalio/workflow'; 2 | import type { createConcatActivity } from '../activities/create-concat-activity'; 3 | 4 | export interface LogSinks extends Sinks { 5 | logger: { 6 | log(message: string): void; 7 | }; 8 | } 9 | 10 | const { logger } = proxySinks(); 11 | 12 | const { concat } = proxyActivities>({ startToCloseTimeout: '1s' }); 13 | 14 | export async function twoStrings(string1: string, string2: string): Promise { 15 | logger.log(string1 + string2); 16 | return string1 + string2; 17 | } 18 | 19 | export async function twoStringsActivity(): Promise { 20 | const result = await concat('str1', 'str2'); 21 | logger.log(result); 22 | return result; 23 | } 24 | -------------------------------------------------------------------------------- /packages/test/src/workflows/unblock-or-cancel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * All-in-one sample showing cancellation, signals and queries 3 | * @module 4 | */ 5 | 6 | // @@@SNIPSTART typescript-workflow-signal-implementation 7 | import { CancelledFailure, defineQuery, setHandler, condition } from '@temporalio/workflow'; 8 | import { unblockSignal } from './definitions'; 9 | 10 | export const isBlockedQuery = defineQuery('isBlocked'); 11 | 12 | export async function unblockOrCancel(): Promise { 13 | let isBlocked = true; 14 | setHandler(unblockSignal, () => void (isBlocked = false)); 15 | setHandler(isBlockedQuery, () => isBlocked); 16 | try { 17 | console.log('Blocked'); 18 | await condition(() => !isBlocked); 19 | isBlocked = false; 20 | console.log('Unblocked'); 21 | } catch (err) { 22 | if (!(err instanceof CancelledFailure)) { 23 | throw err; 24 | } 25 | console.log('Cancelled'); 26 | } 27 | } 28 | // @@@SNIPEND 29 | -------------------------------------------------------------------------------- /packages/test/src/workflows/unhandled-rejection.ts: -------------------------------------------------------------------------------- 1 | import { proxyActivities } from '@temporalio/workflow'; 2 | import type * as activities from '../activities'; 3 | 4 | const { echo } = proxyActivities({ 5 | startToCloseTimeout: '1 minute', 6 | }); 7 | 8 | export async function throwUnhandledRejection({ crashWorker }: { crashWorker: boolean }): Promise { 9 | const p1 = (async () => { 10 | await echo('a'); 11 | })(); 12 | 13 | const p2 = (async () => { 14 | if (crashWorker) { 15 | // Create a Promise associated with the worker thread context 16 | const Promise = globalThis.constructor.constructor('return Promise')(); 17 | Promise.reject(new Error('error to crash the worker')); 18 | } else { 19 | const cause = new Error('root failure'); 20 | const e: any = new Error('unhandled rejection'); 21 | e.cause = cause; 22 | throw e; 23 | } 24 | })(); 25 | 26 | await p1; 27 | await p2; 28 | } 29 | -------------------------------------------------------------------------------- /packages/test/src/workflows/upsert-and-read-memo.ts: -------------------------------------------------------------------------------- 1 | import { upsertMemo, workflowInfo } from '@temporalio/workflow'; 2 | 3 | export async function upsertAndReadMemo(memo: Record): Promise | undefined> { 4 | upsertMemo(memo); 5 | return workflowInfo().memo; 6 | } 7 | -------------------------------------------------------------------------------- /packages/test/src/workflows/upsert-and-read-search-attributes.ts: -------------------------------------------------------------------------------- 1 | import { SearchAttributes, upsertSearchAttributes, workflowInfo } from '@temporalio/workflow'; 2 | 3 | // eslint-disable-next-line deprecation/deprecation 4 | export async function upsertAndReadSearchAttributes(msSinceEpoch: number): Promise { 5 | upsertSearchAttributes({ 6 | CustomIntField: [123], 7 | CustomBoolField: [true], 8 | }); 9 | upsertSearchAttributes({ 10 | CustomIntField: [], // clear 11 | CustomKeywordField: ['durable code'], 12 | CustomTextField: ['is useful'], 13 | CustomDatetimeField: [new Date(msSinceEpoch)], 14 | CustomDoubleField: [3.14], 15 | }); 16 | return workflowInfo().searchAttributes; // eslint-disable-line deprecation/deprecation 17 | } 18 | -------------------------------------------------------------------------------- /packages/test/src/workflows/wait-on-signal-then-activity.ts: -------------------------------------------------------------------------------- 1 | import * as wf from '@temporalio/workflow'; 2 | import type * as activities from '../activities'; 3 | 4 | const { echo } = wf.proxyActivities({ startToCloseTimeout: '5s' }); 5 | export const mySignal = wf.defineSignal<[string]>('my-signal'); 6 | export async function waitOnSignalThenActivity(): Promise { 7 | let lastSignal = ''; 8 | 9 | wf.setHandler(mySignal, (value: string) => { 10 | lastSignal = value; 11 | }); 12 | 13 | await wf.condition(() => lastSignal === 'finish'); 14 | await echo('hi'); 15 | } 16 | -------------------------------------------------------------------------------- /packages/test/src/workflows/wait-on-user.ts: -------------------------------------------------------------------------------- 1 | // @@@SNIPSTART typescript-trigger-workflow 2 | import { defineSignal, setHandler, sleep, Trigger } from '@temporalio/workflow'; 3 | 4 | const completeUserInteraction = defineSignal('completeUserInteraction'); 5 | 6 | export async function waitOnUser(userId: string): Promise { 7 | const userInteraction = new Trigger(); 8 | 9 | // programmatically resolve Trigger 10 | setHandler(completeUserInteraction, () => userInteraction.resolve(true)); 11 | 12 | const userInteracted = await Promise.race([userInteraction, sleep('30 days')]); 13 | if (!userInteracted) { 14 | await sendReminderEmail(userId); 15 | } 16 | } 17 | // @@@SNIPEND 18 | 19 | async function sendReminderEmail(userId: string) { 20 | console.log(`reminder email for ${userId}`); 21 | } 22 | -------------------------------------------------------------------------------- /packages/test/src/workflows/workflow-cancellation-scenarios.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationFailure, CancellationScope, CancelledFailure, sleep } from '@temporalio/workflow'; 2 | 3 | export type WorkflowCancellationScenarioOutcome = 'complete' | 'cancel' | 'fail'; 4 | export type WorkflowCancellationScenarioTiming = 'immediately' | 'after-cleanup'; 5 | 6 | export async function workflowCancellationScenarios( 7 | outcome: WorkflowCancellationScenarioOutcome, 8 | when: WorkflowCancellationScenarioTiming 9 | ): Promise { 10 | try { 11 | await CancellationScope.current().cancelRequested; 12 | } catch (e) { 13 | if (!(e instanceof CancelledFailure)) { 14 | throw e; 15 | } 16 | if (when === 'after-cleanup') { 17 | await CancellationScope.nonCancellable(async () => sleep(1)); 18 | } 19 | switch (outcome) { 20 | case 'cancel': 21 | throw e; 22 | case 'complete': 23 | return; 24 | case 'fail': 25 | throw ApplicationFailure.nonRetryable('Expected failure'); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/test/src/zeroes-http-server.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | import http from 'node:http'; 3 | import { sleep } from './helpers'; 4 | 5 | /** 6 | * Creates an HTTP server which responds with zeroes on /zeroes. 7 | * @param callback called with the port and a promise that resolves when /finish is called once the server is ready, the server will close when the callback completes 8 | * @param numIterations write zeroes into response every iteration 9 | * @param bytesPerIteration number of bytes to respond with on each iteration 10 | * @param msForCompleteResponse approximate number of milliseconds the response should take 11 | */ 12 | export async function withZeroesHTTPServer( 13 | callback: (port: number) => Promise, 14 | numIterations = 100, 15 | bytesPerIteration = 1024, 16 | msForCompleteResponse = 5000 17 | ): Promise { 18 | const server = http.createServer(async (_req, res) => { 19 | res.writeHead(200, 'OK', { 'Content-Length': `${bytesPerIteration * numIterations}` }); 20 | for (let i = 0; i < numIterations; ++i) { 21 | await sleep(msForCompleteResponse / numIterations); 22 | res.write(Buffer.alloc(bytesPerIteration)); 23 | } 24 | res.end(); 25 | }); 26 | server.listen(); 27 | const addr = server.address(); 28 | if (typeof addr === 'string' || addr === null) { 29 | throw new Error('Unexpected server address type'); 30 | } 31 | try { 32 | await callback(addr.port); 33 | } finally { 34 | server.close(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/test/tls_certs/mkcerts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf test-*.{crt,key,csr,srl} 4 | 5 | 6 | # Create a self-signed root CA 7 | 8 | openssl req \ 9 | -newkey rsa:4096 -nodes -keyout test-ca.key \ 10 | -new -x509 -days 3650 -out test-ca.crt \ 11 | -subj "/C=US/ST=WA/CN=Test Root CA - DO NOT TRUST" \ 12 | -addext basicConstraints=critical,CA:TRUE \ 13 | 14 | 15 | # Create the server certificate 16 | 17 | openssl req \ 18 | -newkey rsa:2048 -nodes -keyout test-server.key \ 19 | -new -x509 -days 3650 -out test-server.crt \ 20 | -subj "/CN=server" \ 21 | -CA test-ca.crt -CAkey test-ca.key \ 22 | -extensions v3_ca -addext "basicConstraints = CA:FALSE" \ 23 | -addext "subjectAltName = IP:127.0.0.1,DNS:localhost,DNS:server" 24 | 25 | cat test-server.crt test-ca.crt > test-server-chain.crt 26 | 27 | 28 | # Create the client certificate 29 | 30 | openssl req \ 31 | -newkey rsa:2048 -nodes -keyout test-client.key \ 32 | -new -x509 -days 3650 -out test-client.crt \ 33 | -subj "/CN=client" \ 34 | -CA test-ca.crt -CAkey test-ca.key \ 35 | -extensions v3_ca -addext "basicConstraints = CA:FALSE" \ 36 | -addext "subjectAltName = IP:127.0.0.1,DNS:localhost,DNS:client" 37 | 38 | cat test-client.crt test-ca.crt > test-client-chain.crt 39 | 40 | 41 | # Delete files we don't need, to avoid confusion. Also delete the CA's key to discourage reuse of this 42 | # unsafe CA for anything else. Anyway, one can simply rerun this script to create a new CA if needed. 43 | 44 | rm -rf test-ca.key test-client.crt test-server.crt 45 | -------------------------------------------------------------------------------- /packages/test/tls_certs/test-client.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEUDCCAjigAwIBAgIUXKWggUracXLyLO6nphsPNCt3hEgwDQYJKoZIhvcNAQEL 3 | BQAwQDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMSQwIgYDVQQDDBtUZXN0IFJv 4 | b3QgQ0EgLSBETyBOT1QgVFJVU1QwHhcNMjQxMjE3MTgyNDQwWhcNMzQxMjE1MTgy 5 | NDQwWjARMQ8wDQYDVQQDDAZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 6 | ggEKAoIBAQC/SQHROzzVlKTl7lWLMI4Dqez/cuHPfeToZAyW6Jzdzx/0BLbetiWG 7 | Q1TPhP19Hi0ySdpIS5jH3JO04yf1NPpYRvrvsoOvwG2SH8cevWs/2d0r0vwWVi+I 8 | cLBgl/wnAIn1qNnB5fUXCTzBqoFtphjI+wfHAnvTzp+XgEHiOoHyptoe1V1dDWH4 9 | /UPDcsAAbkEN9NOGW8bSNkahuiI4b7K7b1NJAvOQ8+AixNBc1MGFaNPknGWPiOKy 10 | 1luo5ILPyQVQHNA3td6Uv/ffVr3h9BgheUVf9d1mfzu2umwz5jim8hMN4X/4raLz 11 | AaVVaSnfee6VPqeJMJ1Ao2HcP+56vujbAgMBAAGjcTBvMB0GA1UdDgQWBBTQGasW 12 | u/htcHpZyauF+mLCC5/QrjAfBgNVHSMEGDAWgBRTPhX63E4NPVZDI7Vo4xTPvDVX 13 | zzAJBgNVHRMEAjAAMCIGA1UdEQQbMBmHBH8AAAGCCWxvY2FsaG9zdIIGY2xpZW50 14 | MA0GCSqGSIb3DQEBCwUAA4ICAQCvX1WxsAiOY32Jhe9ysZ/2xyMeiviLMDF+2d2V 15 | TtLa10AGgBWuuboDkYdby/yQ9sESd4pIEXXGxX60gfgwOKaoot/WSiGuE/nyCmVd 16 | L8/GJcI8hV0A+MuQp7FOb0MlW/dfLlJpgk0YxN2eRCWcw6cdnM9Cu/9Cxb9vhAXX 17 | AyWYLKWlrZcknKFGN/+jujIGtE9TcU0EGbUIRruYwnz6WsQtCQ3aWU5Mj4o1JlKW 18 | 77LYUwlOniDrIpys+QsqT++Jf/2CI0CsRFw+Bc4yn2087WxEORM/jS8KPclfphNr 19 | b/ZayrkeICFzor9rTvRIsBQ8tcB3Vh6z9//J8a8y0mq4jerxqNMDgSQaHUrInNzY 20 | G/16Ar2+NbGURbSOj+MEkfAEqtOW55XAy6er6Brs4buye4PeJNbDamJ429TF3eRy 21 | GYBy6Nrae/rsuUmAl+MveCbUqk1+eJunu8mJAG4G+L52r+ZxRU0gBMgmGdgHl/G5 22 | yMqfY4yV1uQTkv2NvcbVFtKay9IRVqwHgWVHADgF5RkersgNxtu7pcaB1zn6TfRL 23 | MC0AFvkxMZw1sQjtc9Bsmj59Jil0wMFlOFGquaqRK9jOUQIn+5fnUyUXEmgPcL7A 24 | bBWb1tfvhe3hLHRLcJx+M7brokGQXzJ3D9+99tGaXuC2gaFiwfFYfO1Bx6jhmRL2 25 | NhzxaA== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /packages/test/tls_certs/test-server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEUDCCAjigAwIBAgIUO8z5jdrgeoX58fru9eeuZrOuYJowDQYJKoZIhvcNAQEL 3 | BQAwQDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMSQwIgYDVQQDDBtUZXN0IFJv 4 | b3QgQ0EgLSBETyBOT1QgVFJVU1QwHhcNMjQxMjE3MTgyNDQwWhcNMzQxMjE1MTgy 5 | NDQwWjARMQ8wDQYDVQQDDAZzZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 6 | ggEKAoIBAQCK70f+pbTr+EpybjfRH18uYlx58YQfKKirlGI9U4+SW2K/4sqZ8E7Z 7 | SbtD+Wlv7fRWy09FU9V2Dvr7bzsFivsmlJ0G1gl+hyUdhfvEstIRbjtQJ8keNUCX 8 | nI7O0TOgFhsbLXndNlzZZo8uERNYVCzF5Ip0kz5UQximf2LOlsAvO6B0xJ1DQsj4 9 | ibloulXGpXy18fJYoEi7ygP8lLFvDOnUT1kL3k5vBeIDfTdDrpSDrGTRqWlVWgRv 10 | eq3AkNlfcQdUuCNm4DcdoKC1e9g9ZhvsnF7DMAGe2jZ4crZh/xO7mTRBJu+kWLaw 11 | YjcBW9EhWf09Za/RRZHiRIf3tx/FOl+jAgMBAAGjcTBvMB0GA1UdDgQWBBSHfhq4 12 | kxKNLjNtxhNsBgkW5/2/TjAfBgNVHSMEGDAWgBRTPhX63E4NPVZDI7Vo4xTPvDVX 13 | zzAJBgNVHRMEAjAAMCIGA1UdEQQbMBmHBH8AAAGCCWxvY2FsaG9zdIIGc2VydmVy 14 | MA0GCSqGSIb3DQEBCwUAA4ICAQC5p9ibg2hUMH0Taql+Lm5ZqEiRmJVGbt0ooEXD 15 | RpSej7ZPAoD5qjBjjt53bVrLQVEhrZdM6TTRD5B7oOEqKt3/FdYGWcbmxFIbCGJq 16 | NeLWOpO8mJGDpJyQECjviTAoujwtCcuO6guintiO52ABy8PoEY9vugtKoeF+IyOJ 17 | 8cASjdrkNhUPaMExTqklR8Q8YnK+bFArxxzAPsCOvIFEpwTgrXPQl0YUjmIvzjnS 18 | ewySxWboKMA2QzQQjJS5W/DVk18mjvQ/Demm9oV5trxkIr0Ps4H3tpvSOEz8lkiJ 19 | nUkBQnG5ng0uwIQ5Fn/XV1L/WdktM840Wjgt+qRgnrwEYunz3bqBLCve7r84vDJp 20 | fUc/pWMmpPcxk1GYHek9N9eZyTeg58DWrGvKEhs4/H/ZY7CrRC6XJbVRvTtfdFZJ 21 | ySBTbzIx+zFlZAnWRppXyf6uaNvuu9da/GbP0a6dBc7kECGggJT14k5AswdbSFOv 22 | fmzT4rqZwGrXTxM1cP5deGq/MBEsLD+BtE3fLS1iJBd+cscZLClXItJiEpccox6G 23 | gjtET5wDNVfDXj7eDqjj2lWInzP3HO0GnAdiwRkATKCSY4E/u60QepQ9g/sgG/Pn 24 | Gi/PpotOyx1DqTAAGi78m37qVPIgfgH41ixl9zkUCAN1upMZh/M0ISv3wmVZyX/F 25 | lG9PeQ== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /packages/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "rootDir": "./src", 6 | "resolveJsonModule": true, 7 | "experimentalDecorators": true 8 | }, 9 | "references": [ 10 | { "path": "../proto" }, 11 | { "path": "../activity" }, 12 | { "path": "../client" }, 13 | { "path": "../cloud" }, 14 | { "path": "../common" }, 15 | { "path": "../interceptors-opentelemetry" }, 16 | { "path": "../testing" }, 17 | { "path": "../worker" }, 18 | { "path": "../workflow" }, 19 | { "path": "../nyc-test-coverage" } 20 | ], 21 | "include": ["./src/**/*.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/testing/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/testing` 2 | 3 | [![NPM](https://img.shields.io/npm/v/@temporalio/testing?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/testing) 4 | 5 | Part of [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/). 6 | 7 | - [Testing docs](https://docs.temporal.io/typescript/testing) 8 | - [API reference](https://typescript.temporal.io/api/namespaces/testing) 9 | - [Sample projects](https://github.com/temporalio/samples-typescript) 10 | -------------------------------------------------------------------------------- /packages/testing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@temporalio/testing", 3 | "version": "1.12.0-rc.0", 4 | "description": "Temporal.io SDK Testing sub-package", 5 | "main": "lib/index.js", 6 | "types": "./lib/index.d.ts", 7 | "keywords": [ 8 | "temporal", 9 | "workflow", 10 | "testing" 11 | ], 12 | "author": "Temporal Technologies Inc. ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "@temporalio/activity": "file:../activity", 16 | "@temporalio/client": "file:../client", 17 | "@temporalio/common": "file:../common", 18 | "@temporalio/core-bridge": "file:../core-bridge", 19 | "@temporalio/proto": "file:../proto", 20 | "@temporalio/worker": "file:../worker", 21 | "@temporalio/workflow": "file:../workflow", 22 | "abort-controller": "^3.0.0" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/temporalio/sdk-typescript/issues" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/temporalio/sdk-typescript.git", 30 | "directory": "packages/testing" 31 | }, 32 | "homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/testing", 33 | "engines": { 34 | "node": ">= 18.0.0" 35 | }, 36 | "files": [ 37 | "lib", 38 | "src" 39 | ], 40 | "publishConfig": { 41 | "access": "public" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/testing/proto/api-linter.yaml: -------------------------------------------------------------------------------- 1 | - included_paths: 2 | - '**/*.proto' 3 | disabled_rules: 4 | - 'core::0192::has-comments' 5 | 6 | - included_paths: 7 | - '**/message.proto' 8 | disabled_rules: 9 | - 'core::0122::name-suffix' 10 | - 'core::0123::resource-annotation' 11 | 12 | - included_paths: 13 | - '**/testservice/v1/request_response.proto' 14 | disabled_rules: 15 | - 'core::0122::name-suffix' 16 | - 'core::0131::request-name-required' 17 | - 'core::0131::request-unknown-fields' 18 | - 'core::0132::request-parent-required' 19 | - 'core::0132::request-unknown-fields' 20 | - 'core::0132::response-unknown-fields' 21 | - 'core::0134::request-unknown-fields' 22 | - 'core::0158::request-page-size-field' 23 | - 'core::0158::request-page-token-field' 24 | - 'core::0158::response-next-page-token-field' 25 | - 'core::0158::response-plural-first-field' 26 | - 'core::0158::response-repeated-first-field' 27 | 28 | - included_paths: 29 | - '**/testservice/v1/service.proto' 30 | disabled_rules: 31 | - 'core::0127::http-annotation' 32 | - 'core::0131::method-signature' 33 | - 'core::0131::response-message-name' 34 | 35 | - included_paths: 36 | - 'dependencies/gogoproto/gogo.proto' 37 | disabled_rules: 38 | - 'all' 39 | -------------------------------------------------------------------------------- /packages/testing/proto/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | build: 3 | roots: 4 | - . 5 | lint: 6 | ignore: 7 | - dependencies 8 | use: 9 | - DEFAULT 10 | breaking: 11 | ignore: 12 | use: 13 | - PACKAGE -------------------------------------------------------------------------------- /packages/testing/src/assert-to-failure-interceptor.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { ApplicationFailure, WorkflowInterceptors } from '@temporalio/workflow'; 3 | 4 | /** 5 | * Simple interceptor that transforms {@link assert.AssertionError} into non retryable failures. 6 | * 7 | * This allows conveniently using `assert` directly from Workflows. 8 | */ 9 | export function interceptors(): WorkflowInterceptors { 10 | return { 11 | inbound: [ 12 | { 13 | async handleSignal(input, next) { 14 | try { 15 | return await next(input); 16 | } catch (err) { 17 | if (err instanceof assert.AssertionError) { 18 | const appErr = ApplicationFailure.nonRetryable(err.message); 19 | appErr.stack = err.stack; 20 | throw appErr; 21 | } 22 | throw err; 23 | } 24 | }, 25 | async execute(input, next) { 26 | try { 27 | return await next(input); 28 | } catch (err) { 29 | if (err instanceof assert.AssertionError) { 30 | const appErr = ApplicationFailure.nonRetryable(err.message); 31 | appErr.stack = err.stack; 32 | throw appErr; 33 | } 34 | throw err; 35 | } 36 | }, 37 | }, 38 | ], 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /packages/testing/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * `npm i @temporalio/testing` 3 | * 4 | * Testing library for the SDK. 5 | * 6 | * [Documentation](https://docs.temporal.io/typescript/testing) 7 | * 8 | * @module 9 | */ 10 | 11 | import path from 'node:path'; 12 | 13 | export { 14 | TestWorkflowEnvironment, 15 | type LocalTestWorkflowEnvironmentOptions, 16 | type TimeSkippingTestWorkflowEnvironmentOptions, 17 | type ExistingServerTestWorkflowEnvironmentOptions, 18 | } from './testing-workflow-environment'; 19 | 20 | export { 21 | type DevServerConfig, 22 | type TimeSkippingServerConfig, 23 | type EphemeralServerExecutable, 24 | } from './ephemeral-server'; 25 | 26 | export { 27 | // FIXME: Revise the pertinence of these types 28 | type ClientOptionsForTestEnv, 29 | type TestEnvClientOptions, 30 | type TimeSkippingWorkflowClientOptions, 31 | TestEnvClient, 32 | TimeSkippingWorkflowClient, 33 | } from './client'; 34 | 35 | export { 36 | type MockActivityEnvironmentOptions, 37 | MockActivityEnvironment, 38 | defaultActivityInfo, 39 | } from './mocking-activity-environment'; 40 | 41 | /** 42 | * Convenience workflow interceptors 43 | * 44 | * Contains a single interceptor for transforming `AssertionError`s into non 45 | * retryable `ApplicationFailure`s. 46 | */ 47 | export const workflowInterceptorModules = [path.join(__dirname, 'assert-to-failure-interceptor')]; 48 | -------------------------------------------------------------------------------- /packages/testing/src/pkg.ts: -------------------------------------------------------------------------------- 1 | // ../package.json is outside of the TS project rootDir which causes TS to complain about this import. 2 | // We do not want to change the rootDir because it messes up the output structure. 3 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 4 | // @ts-ignore 5 | import pkg from '../package.json'; 6 | 7 | export default pkg as { name: string; version: string }; 8 | -------------------------------------------------------------------------------- /packages/testing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true, 5 | "outDir": "./lib", 6 | "rootDir": "./src" 7 | }, 8 | "references": [{ "path": "../client" }, { "path": "../common" }, { "path": "../worker" }, { "path": "../activity" }], 9 | "include": ["./src/**/*.ts"], 10 | "exclude": ["node_modules"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/worker/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/worker` 2 | 3 | [![NPM](https://img.shields.io/npm/v/@temporalio/worker?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/worker) 4 | 5 | Part of [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/). 6 | 7 | - [Worker docs](https://docs.temporal.io/typescript/workers) 8 | - [API reference](https://typescript.temporal.io/api/namespaces/worker) 9 | - [Sample projects](https://github.com/temporalio/samples-typescript) 10 | -------------------------------------------------------------------------------- /packages/worker/src/debug-replayer/inbound-interceptor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Debug replayer workflow inbound interceptors. 3 | * Notify the runner when workflow starts or a signal is received, required for setting breakpoints on workflow tasks. 4 | * 5 | * @module 6 | */ 7 | import { WorkflowInterceptorsFactory } from '@temporalio/workflow'; 8 | import { notifyRunner } from './workflow-notifier'; 9 | 10 | export const interceptors: WorkflowInterceptorsFactory = () => ({ 11 | inbound: [ 12 | { 13 | execute(input, next) { 14 | notifyRunner(); 15 | return next(input); 16 | }, 17 | handleSignal(input, next) { 18 | notifyRunner(); 19 | return next(input); 20 | }, 21 | }, 22 | ], 23 | }); 24 | -------------------------------------------------------------------------------- /packages/worker/src/debug-replayer/outbound-interceptor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Debug replayer workflow outbound interceptors. 3 | * Notify the runner when outbound operations resolve, required for setting breakpoints on workflow tasks. 4 | * 5 | * @module 6 | */ 7 | import { WorkflowInterceptorsFactory } from '@temporalio/workflow'; 8 | import { notifyRunner } from './workflow-notifier'; 9 | 10 | export const interceptors: WorkflowInterceptorsFactory = () => ({ 11 | outbound: [ 12 | { 13 | async scheduleActivity(input, next) { 14 | try { 15 | return await next(input); 16 | } finally { 17 | notifyRunner(); 18 | } 19 | }, 20 | async scheduleLocalActivity(input, next) { 21 | try { 22 | return await next(input); 23 | } finally { 24 | notifyRunner(); 25 | } 26 | }, 27 | async startTimer(input, next) { 28 | try { 29 | return await next(input); 30 | } finally { 31 | notifyRunner(); 32 | } 33 | }, 34 | async signalWorkflow(input, next) { 35 | try { 36 | return await next(input); 37 | } finally { 38 | notifyRunner(); 39 | } 40 | }, 41 | async startChildWorkflowExecution(input, next) { 42 | const [startPromise, completePromise] = await next(input); 43 | startPromise.finally(notifyRunner).catch(() => {}); 44 | completePromise.finally(notifyRunner).catch(() => {}); 45 | return [startPromise, completePromise]; 46 | }, 47 | }, 48 | ], 49 | }); 50 | -------------------------------------------------------------------------------- /packages/worker/src/debug-replayer/workflow-notifier.ts: -------------------------------------------------------------------------------- 1 | import { workflowInfo } from '@temporalio/workflow'; 2 | 3 | class WorkflowNotifier { 4 | static _instance?: WorkflowNotifier; 5 | 6 | /** 7 | * Access the singleton instance - one per workflow context 8 | */ 9 | static instance(): WorkflowNotifier { 10 | if (this._instance === undefined) { 11 | this._instance = new this(); 12 | } 13 | return this._instance; 14 | } 15 | 16 | private constructor() { 17 | // Dear eslint, 18 | // I left this empty to mark the constructor private, OK? 19 | // 20 | // Best regards, 21 | // - An anonymous developer 22 | } 23 | 24 | lastNotifiedStartEvent = -1; 25 | 26 | notifyRunner(): void { 27 | const eventId = workflowInfo().historyLength; 28 | if (this.lastNotifiedStartEvent >= eventId) return; 29 | this.lastNotifiedStartEvent = eventId; 30 | try { 31 | // Use global `notifyRunner` function, should be injected outside of workflow context. 32 | // Using globalThis.constructor.constructor, we break out of the workflow context to Node.js land. 33 | const notifyRunner = globalThis.constructor.constructor('return notifyRunner')(); 34 | notifyRunner(eventId); 35 | } catch { 36 | // ignore 37 | } 38 | } 39 | } 40 | 41 | /** 42 | * Notify a runner process when a workflow task is picked up 43 | */ 44 | export function notifyRunner(): void { 45 | WorkflowNotifier.instance().notifyRunner(); 46 | } 47 | -------------------------------------------------------------------------------- /packages/worker/src/pkg.ts: -------------------------------------------------------------------------------- 1 | // ../package.json is outside of the TS project rootDir which causes TS to complain about this import. 2 | // We do not want to change the rootDir because it messes up the output structure. 3 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 4 | // @ts-ignore 5 | import pkg from '../package.json'; 6 | 7 | export default pkg as { name: string; version: string }; 8 | -------------------------------------------------------------------------------- /packages/worker/src/workflow/interface.ts: -------------------------------------------------------------------------------- 1 | import { type coresdk } from '@temporalio/proto'; 2 | import { type SinkCall } from '@temporalio/workflow'; 3 | import { type WorkflowCreateOptions } from '@temporalio/workflow/lib/interfaces'; 4 | 5 | export { WorkflowCreateOptions }; 6 | 7 | export interface Workflow { 8 | /** 9 | * Activate the Workflow. 10 | * 11 | * This method should return a completion after processing a single activation. 12 | * It is guaranteed that only a single activation will be processed concurrently for a Workflow execution. 13 | */ 14 | activate( 15 | activation: coresdk.workflow_activation.IWorkflowActivation 16 | ): Promise; 17 | 18 | /** 19 | * Gets any sink calls recorded during an activation. 20 | * 21 | * This is separate from `activate` so it can be called even if activation fails 22 | * in order to extract all logs and metrics from the Workflow context. 23 | */ 24 | getAndResetSinkCalls(): Promise; 25 | 26 | /** 27 | * Dispose this instance, and release its resources. 28 | * 29 | * Do not use this Workflow instance after this method has been called. 30 | */ 31 | dispose(): Promise; 32 | } 33 | 34 | export interface WorkflowCreator { 35 | /** 36 | * Create a Workflow for the Worker to activate 37 | * 38 | * Note: this needs to be async because of the ThreadedWorkflowCreator. 39 | */ 40 | createWorkflow(options: WorkflowCreateOptions): Promise; 41 | 42 | /** 43 | * Destroy and cleanup any resources 44 | */ 45 | destroy(): Promise; 46 | } 47 | -------------------------------------------------------------------------------- /packages/worker/src/workflow/logger.ts: -------------------------------------------------------------------------------- 1 | import { type LoggerSinksInternal } from '@temporalio/workflow/lib/logs'; 2 | import { SdkComponent } from '@temporalio/common'; 3 | import { LoggerWithComposedMetadata } from '@temporalio/common/lib/logger'; 4 | import { type InjectedSinks } from '../sinks'; 5 | import { type Logger } from '../logger'; 6 | 7 | /** 8 | * Injects a logger sink that forwards to the worker's logger 9 | */ 10 | export function initLoggerSink(logger: Logger): InjectedSinks { 11 | logger = LoggerWithComposedMetadata.compose(logger, { sdkComponent: SdkComponent.workflow }); 12 | return { 13 | __temporal_logger: { 14 | trace: { 15 | fn(_, message, attrs) { 16 | logger.trace(message, attrs); 17 | }, 18 | }, 19 | debug: { 20 | fn(_, message, attrs) { 21 | logger.debug(message, attrs); 22 | }, 23 | }, 24 | info: { 25 | fn(_, message, attrs) { 26 | logger.info(message, attrs); 27 | }, 28 | }, 29 | warn: { 30 | fn(_, message, attrs) { 31 | logger.warn(message, attrs); 32 | }, 33 | }, 34 | error: { 35 | fn(_, message, attrs) { 36 | logger.error(message, attrs); 37 | }, 38 | }, 39 | }, 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /packages/worker/src/workflow/module-overrides/assert.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/unambiguous */ 2 | // proto3-json-serializer assumes it's running in Node and that `assert` is present, so we need to add it 3 | // Don't use `export default` because then `require('assert')` will be `{ default: assertFn }`. It needs to be `assertFn`. 4 | module.exports = (global as any).assert; 5 | -------------------------------------------------------------------------------- /packages/worker/src/workflow/module-overrides/url.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/unambiguous */ 2 | // Only expose the WHATWG URL API 3 | module.exports = { URL, URLSearchParams }; 4 | -------------------------------------------------------------------------------- /packages/worker/src/workflow/module-overrides/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/unambiguous */ 2 | // Only expose the TextEncoder and TextDecoder APIs 3 | module.exports = { TextEncoder, TextDecoder }; 4 | -------------------------------------------------------------------------------- /packages/worker/src/workflow/workflow-worker-thread/output.ts: -------------------------------------------------------------------------------- 1 | import { type coresdk } from '@temporalio/proto'; 2 | import { type SinkCall } from '@temporalio/workflow/lib/sinks'; 3 | 4 | /** 5 | * An activation completion. 6 | * 7 | * Used as response to an `ActivateWorkflow` request. 8 | */ 9 | export interface ActivationCompletion { 10 | type: 'activation-completion'; 11 | completion: coresdk.workflow_completion.IWorkflowActivationCompletion; 12 | } 13 | 14 | /** 15 | * Response to a `ExtractSinkCalls` request. 16 | */ 17 | export interface SinkCallList { 18 | type: 'sink-calls'; 19 | calls: SinkCall[]; 20 | } 21 | 22 | export type WorkerThreadOutput = ActivationCompletion | SinkCallList | undefined; 23 | 24 | /** 25 | * Successful result for a given request 26 | */ 27 | export interface WorkerThreadOkResult { 28 | type: 'ok'; 29 | output?: WorkerThreadOutput; 30 | } 31 | 32 | /** 33 | * Error result for a given request 34 | */ 35 | export interface WorkflowThreadErrorResult { 36 | type: 'error'; 37 | /** Error class name */ 38 | name: string; 39 | message: string; 40 | stack: string; 41 | } 42 | 43 | /** 44 | * Response to a WorkerThreadRequest. 45 | */ 46 | export interface WorkerThreadResponse { 47 | /** 48 | * ID provided in the originating `WorkerThreadRequest` 49 | */ 50 | requestId: bigint; 51 | 52 | result: WorkerThreadOkResult | WorkflowThreadErrorResult; 53 | } 54 | -------------------------------------------------------------------------------- /packages/worker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true, 5 | "outDir": "./lib", 6 | "rootDir": "./src" 7 | }, 8 | "references": [ 9 | { "path": "../client" }, 10 | { "path": "../core-bridge" }, 11 | { "path": "../workflow" }, 12 | { "path": "../activity" }, 13 | { "path": "../common" } 14 | ], 15 | "include": ["./src/**/*.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /packages/workflow/README.md: -------------------------------------------------------------------------------- 1 | # `@temporalio/workflow` 2 | 3 | [![NPM](https://img.shields.io/npm/v/@temporalio/workflow?style=for-the-badge)](https://www.npmjs.com/package/@temporalio/workflow) 4 | 5 | Part of [Temporal](https://temporal.io)'s [TypeScript SDK](https://docs.temporal.io/typescript/introduction/). 6 | 7 | - [Workflow docs](https://docs.temporal.io/typescript/workflows) 8 | - [API reference](https://typescript.temporal.io/api/namespaces/workflow) 9 | - [Sample projects](https://github.com/temporalio/samples-typescript) 10 | -------------------------------------------------------------------------------- /packages/workflow/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@temporalio/workflow", 3 | "version": "1.12.0-rc.0", 4 | "description": "Temporal.io SDK Workflow sub-package", 5 | "keywords": [ 6 | "temporal", 7 | "workflow", 8 | "isolate" 9 | ], 10 | "bugs": { 11 | "url": "https://github.com/temporalio/sdk-typescript/issues" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/temporalio/sdk-typescript.git", 16 | "directory": "packages/workflow" 17 | }, 18 | "homepage": "https://github.com/temporalio/sdk-typescript/tree/main/packages/workflow", 19 | "license": "MIT", 20 | "author": "Temporal Technologies Inc. ", 21 | "main": "lib/index.js", 22 | "types": "lib/index.d.ts", 23 | "scripts": {}, 24 | "dependencies": { 25 | "@temporalio/common": "file:../common", 26 | "@temporalio/proto": "file:../proto" 27 | }, 28 | "devDependencies": { 29 | "source-map": "^0.7.4" 30 | }, 31 | "publishConfig": { 32 | "access": "public" 33 | }, 34 | "engines": { 35 | "node": ">= 18.0.0" 36 | }, 37 | "files": [ 38 | "src", 39 | "lib" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /packages/workflow/src/errors.ts: -------------------------------------------------------------------------------- 1 | import { ActivityFailure, CancelledFailure, ChildWorkflowFailure } from '@temporalio/common'; 2 | import { SymbolBasedInstanceOfError } from '@temporalio/common/lib/type-helpers'; 3 | import { coresdk } from '@temporalio/proto'; 4 | 5 | /** 6 | * Base class for all workflow errors 7 | */ 8 | @SymbolBasedInstanceOfError('WorkflowError') 9 | export class WorkflowError extends Error {} 10 | 11 | /** 12 | * Thrown in workflow when it tries to do something that non-deterministic such as construct a WeakRef() 13 | */ 14 | @SymbolBasedInstanceOfError('DeterminismViolationError') 15 | export class DeterminismViolationError extends WorkflowError {} 16 | 17 | /** 18 | * A class that acts as a marker for this special result type 19 | */ 20 | @SymbolBasedInstanceOfError('LocalActivityDoBackoff') 21 | export class LocalActivityDoBackoff extends Error { 22 | constructor(public readonly backoff: coresdk.activity_result.IDoBackoff) { 23 | super(); 24 | } 25 | } 26 | 27 | /** 28 | * Returns whether provided `err` is caused by cancellation 29 | */ 30 | export function isCancellation(err: unknown): boolean { 31 | return ( 32 | err instanceof CancelledFailure || 33 | ((err instanceof ActivityFailure || err instanceof ChildWorkflowFailure) && err.cause instanceof CancelledFailure) 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /packages/workflow/src/global-attributes.ts: -------------------------------------------------------------------------------- 1 | import { IllegalStateError } from '@temporalio/common'; 2 | import { type Activator } from './internals'; 3 | 4 | export function maybeGetActivatorUntyped(): unknown { 5 | return (globalThis as any).__TEMPORAL_ACTIVATOR__; 6 | } 7 | 8 | export function setActivatorUntyped(activator: unknown): void { 9 | (globalThis as any).__TEMPORAL_ACTIVATOR__ = activator; 10 | } 11 | 12 | export function maybeGetActivator(): Activator | undefined { 13 | return maybeGetActivatorUntyped() as Activator | undefined; 14 | } 15 | 16 | export function assertInWorkflowContext(message: string): Activator { 17 | const activator = maybeGetActivator(); 18 | if (activator == null) throw new IllegalStateError(message); 19 | return activator; 20 | } 21 | 22 | export function getActivator(): Activator { 23 | const activator = maybeGetActivator(); 24 | if (activator === undefined) { 25 | throw new IllegalStateError('Workflow uninitialized'); 26 | } 27 | return activator; 28 | } 29 | -------------------------------------------------------------------------------- /packages/workflow/src/pkg.ts: -------------------------------------------------------------------------------- 1 | // ../package.json is outside of the TS project rootDir which causes TS to complain about this import. 2 | // We do not want to change the rootDir because it messes up the output structure. 3 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 4 | // @ts-ignore 5 | import pkg from '../package.json'; 6 | 7 | export default pkg as { name: string; version: string }; 8 | -------------------------------------------------------------------------------- /packages/workflow/src/stack-helpers.ts: -------------------------------------------------------------------------------- 1 | import { maybeGetActivatorUntyped } from './global-attributes'; 2 | import type { PromiseStackStore } from './internals'; 3 | 4 | /** 5 | * Helper function to remove a promise from being tracked for stack trace query purposes 6 | */ 7 | export function untrackPromise(promise: Promise): void { 8 | const store = (maybeGetActivatorUntyped() as any)?.promiseStackStore as PromiseStackStore | undefined; 9 | if (!store) return; 10 | store.childToParent.delete(promise); 11 | store.promiseToStack.delete(promise); 12 | } 13 | -------------------------------------------------------------------------------- /packages/workflow/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib" 6 | }, 7 | "references": [{ "path": "../common" }], 8 | "include": ["./src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /scripts/create-certs-dir.js: -------------------------------------------------------------------------------- 1 | // Create /tmp/temporal-certs and populate it with certs from env vars. 2 | // Used in CI flow to store the Cloud certs from GH secret into local files for testing the mTLS sample. 3 | const fs = require('fs-extra'); 4 | 5 | const targetDir = process.argv[2] ?? '/tmp/temporal-certs'; 6 | 7 | fs.mkdirsSync(targetDir); 8 | fs.writeFileSync(`${targetDir}/client.pem`, process.env.TEMPORAL_CLIENT_CERT); 9 | fs.writeFileSync(`${targetDir}/client.key`, process.env.TEMPORAL_CLIENT_KEY); 10 | -------------------------------------------------------------------------------- /scripts/gen-docs.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const typedoc = require('typedoc'); 3 | 4 | /// Generate docs for a single package. 5 | /// This may not run concurrently because it changes the directory to the package root 6 | async function genDocs(package = 'meta', outputDir) { 7 | const root = path.resolve(__dirname, '../packages', package); 8 | const oldpwd = process.cwd(); 9 | try { 10 | process.chdir(root); 11 | 12 | const app = new typedoc.Application(); 13 | app.options.addReader(new typedoc.TSConfigReader()); 14 | 15 | app.bootstrap({ 16 | tsconfig: 'tsconfig.json', 17 | entryPoints: ['src/index.ts'], 18 | excludePrivate: true, 19 | excludeProtected: true, 20 | hideGenerator: true, 21 | disableSources: true, 22 | hideBreadcrumbs: true, 23 | }); 24 | 25 | const project = app.convert(); 26 | 27 | // Project may not have converted correctly 28 | if (!project) { 29 | throw new Error('Failed to convert app'); 30 | } 31 | 32 | // Rendered docs 33 | await app.generateDocs(project, outputDir); 34 | } finally { 35 | process.chdir(oldpwd); 36 | } 37 | } 38 | 39 | async function main() { 40 | const outputDir = process.argv[2]; 41 | if (!outputDir) { 42 | throw new Error('Usage: gen-docs '); 43 | } 44 | await genDocs('meta', outputDir); 45 | } 46 | 47 | main().catch((err) => { 48 | console.error(err); 49 | process.exit(1); 50 | }); 51 | -------------------------------------------------------------------------------- /scripts/prepublish.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises'; 2 | import path from 'node:path'; 3 | import { URL, fileURLToPath } from 'node:url'; 4 | 5 | const rootPath = fileURLToPath(new URL('..', import.meta.url)); 6 | const packagesPath = path.join(rootPath, 'packages'); 7 | const lernaJsonPath = path.join(rootPath, 'lerna.json'); 8 | 9 | const { version } = JSON.parse(await fs.readFile(lernaJsonPath, 'utf8')); 10 | 11 | const packages = await fs.readdir(packagesPath); 12 | for (const dir of packages) { 13 | const packageJsonPath = path.join(packagesPath, dir, 'package.json'); 14 | const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); 15 | for (const depType of ['dependencies', 'devDependencies', 'peerDependencies']) { 16 | for (const dep of Object.keys(packageJson[depType] ?? {})) { 17 | if (dep.startsWith('@temporalio/')) { 18 | packageJson[depType][dep] = `${version}`; 19 | } 20 | } 21 | } 22 | const replacedContent = JSON.stringify(packageJson, null, 2); 23 | await fs.writeFile(packageJsonPath, replacedContent + '\n'); 24 | } 25 | -------------------------------------------------------------------------------- /scripts/publish-to-verdaccio.js: -------------------------------------------------------------------------------- 1 | const { withRegistry, getArgs } = require('./registry'); 2 | const { spawnNpx } = require('./utils'); 3 | 4 | async function main() { 5 | const { registryDir } = await getArgs(); 6 | await withRegistry(registryDir, async () => { 7 | try { 8 | await spawnNpx(['lerna', 'publish', 'from-package', '--yes', '--registry', 'http://127.0.0.1:4873/'], { 9 | stdio: 'inherit', 10 | stdout: 'inherit', 11 | stderr: 'inherit', 12 | }); 13 | } catch (e) { 14 | console.error(e); 15 | throw new Error('Failed to publish to registry'); 16 | } 17 | }); 18 | } 19 | 20 | main() 21 | .then(() => process.exit(0)) 22 | .catch((err) => { 23 | console.error(err); 24 | process.exit(1); 25 | }); 26 | -------------------------------------------------------------------------------- /scripts/test-example.js: -------------------------------------------------------------------------------- 1 | const { spawn: spawnChild, spawnSync } = require('child_process'); 2 | const arg = require('arg'); 3 | const { shell, kill } = require('./utils'); 4 | 5 | const npm = /^win/.test(process.platform) ? 'npm.cmd' : 'npm'; 6 | 7 | function createWorker(workdir) { 8 | return spawnChild(npm, ['start'], { 9 | cwd: workdir, 10 | stdio: 'inherit', 11 | shell, 12 | detached: true, 13 | }); 14 | } 15 | 16 | async function withWorker(workdir, fn) { 17 | console.log('Starting worker'); 18 | const worker = createWorker(workdir); 19 | try { 20 | return await fn(); 21 | } finally { 22 | await kill(worker); 23 | } 24 | } 25 | 26 | async function test(workdir) { 27 | const { status, output } = spawnSync(npm, ['run', 'workflow'], { 28 | cwd: workdir, 29 | shell, 30 | encoding: 'utf8', 31 | stdio: ['inherit', 'pipe', 'inherit'], 32 | }); 33 | if (status !== 0) { 34 | throw new Error('Failed to run workflow'); 35 | } 36 | if (!output[1].includes('Hello, Temporal!\n')) { 37 | throw new Error(`Invalid output: "${output[1]}"`); 38 | } 39 | } 40 | 41 | async function main() { 42 | const opts = arg({ 43 | '--work-dir': String, 44 | }); 45 | const workdir = opts['--work-dir']; 46 | if (!workdir) { 47 | throw new Error('Missing required option --work-dir'); 48 | } 49 | 50 | await withWorker(workdir, () => test(workdir)); 51 | } 52 | 53 | main() 54 | .then(() => process.exit(0)) 55 | .catch((err) => { 56 | console.error(err); 57 | process.exit(1); 58 | }); 59 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "incremental": true, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "experimentalDecorators": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.prune.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "@temporalio/*": ["packages/*"] 6 | } 7 | }, 8 | "files": [ 9 | "./packages/activity/src/index.ts", 10 | "./packages/client/src/index.ts", 11 | "./packages/cloud/src/index.ts", 12 | "./packages/common/src/index.ts", 13 | "./packages/common/src/type-helpers.ts", 14 | "./packages/create-project/src/index.ts", 15 | "./packages/interceptors-opentelemetry/src/index.ts", 16 | "./packages/nyc-test-coverage/src/index.ts", 17 | "./packages/meta/src/index.ts", 18 | "./packages/testing/src/index.ts", 19 | "./packages/worker/src/index.ts", 20 | "./packages/worker/src/workflow/workflow-worker-thread.ts", 21 | "./packages/workflow/src/index.ts", 22 | "./packages/workflow/src/worker-interface.ts" 23 | ] 24 | } 25 | --------------------------------------------------------------------------------