Skip to main content

resonate-migrate-from-temporal

Port a Temporal application to Resonate, pattern by pattern. Use when migrating Temporal Workflows/Activities, Signals, timers, sagas, continue-as-new loops, fan-out/fan-in, child workflows, distributed mutex, or encryption to the Resonate SDK. Maps canonical temporalio/samples-* examples to their Resonate equivalents across TypeScript, Python, Rust, and Go, with per-SDK API notes and honest coverage gaps. Foundational skill — delegates to the per-SDK pattern skills for idiomatic target code.

Aller à l'installation

Informations de source

Dépôt
resonatehq/resonate-skills
Dernière activité de la source
21 août 2026 à 13:59
Langue détectée de SKILL.md
anglais
Étoiles
6
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
resonate-migrate-from-temporal
description
Port a Temporal application to Resonate, pattern by pattern. Use when migrating Temporal Workflows/Activities, Signals, timers, sagas, continue-as-new loops, fan-out/fan-in, child workflows, distributed mutex, or encryption to the Resonate SDK. Maps canonical temporalio/samples-* examples to their Resonate equivalents across TypeScript, Python, Rust, and Go, with per-SDK API notes and honest coverage gaps. Foundational skill — delegates to the per-SDK pattern skills for idiomatic target code.
license
Apache-2.0
# Migrate from Temporal to Resonate A pattern-by-pattern playbook for porting a Temporal application to Resonate. Identify which Temporal construct each piece of the source uses, apply the matching transform, and reach for the linked per-SDK skill for idiomatic target code. ## Ground rules - **Never invent Temporal code.** Quote it from the user's source or a named `temporalio/samples-*` file. If you can't find it, say so. - **Resonate has no `@workflow`/`@activity` split.** A step is just a function made durable by `ctx.run`. Don't invent decorators. - **Temporal Rust samples live in-repo.** Temporal's Rust SDK ships examples in `temporalio/sdk-rust/crates/sdk/examples/` (no separate `samples-rust` repo) — source Rust migrations from there (hello_world, child_workflows, timer_examples, message_passing, saga, continue_as_new, …). Only mutex and encryption have no Temporal Rust example; for those, source from the Temporal *TypeScript* idiom. - **State coverage honestly.** A missing example ≠ impossible; it means no worked reference exists yet. - **Verify APIs against the pinned version before emitting** — the SDK surface drifts between versions. ## Pinned SDK versions (latest released at time of writing) | SDK | Version | Source | |---|---|---| | TypeScript | `@resonatehq/sdk` v0.11.4 | npm | | Python | `resonate-sdk` v0.7.4 | PyPI | | Rust | `resonate-sdk` v0.6.0 | crates.io | | Go | `0.1.0` (tag has no `v` prefix — `go get github.com/resonatehq/resonate-sdk-go@0.1.0`) | GitHub | ## Core mappings (apply everywhere) | Temporal | Resonate | |---|---| | `@workflow.defn` / Workflow type | a registered function (`@resonate.register`, `resonate.register("name", fn)`, `#[resonate::function]`, `resonate.Register(r, "name", fn)`) | | `@activity.defn` / Activity | a plain function invoked via `ctx.run(fn, args)` | | `workflow.execute_activity(fn, …, start_to_close_timeout=…)` | `await ctx.run(fn, args)` (py, ts async engine) / `yield* ctx.run(fn, args)` (ts generator engine) / `ctx.run(fn, args).await?` (rs) / `ctx.Run(fn, args)` then `f.Await(&out)` (go) — no timeout policy required | | Task Queue + Worker wiring | a worker `group` (only when you need distributed dispatch) | | `executeChild` / `ExecuteChildWorkflow(ChildType, …)` | `ctx.rpc("name", args)` or `ctx.run(fn, args)` — invoke by registered name; recursion is trivial | | `Promise.all` / `asyncio.gather` / parallel futures (fan-out) | start each non-blocking (`ctx.beginRun` / `ctx.rfi` / `.spawn()` / `ctx.RPC`), then await each | | `defineSignal` + `setHandler` + `condition` | one latent durable promise: `p = ctx.promise()` then await `p` | | `defineQuery` / query handler | delete — promise/result state is the source of truth | | `handle.signal(sig)` (external) | `resonate.promises.resolve(id, value)` (HTTP-addressable from anywhere) | | `workflow.sleep` / `NewTimer` | `ctx.sleep(duration)` | | saga compensation stack + drain-on-catch | inline `ctx.run(undo, …)` in the error branch, guarded by what completed | | `continueAsNew` | bounded loop: a plain loop. Unbounded loop: `ctx.detached(self, n+1)` tail-recursion | --- ## Pattern: Workflow + activity - **DETECT:** `@workflow.defn`/`@activity.defn` (py), `proxyActivities` (ts), `RegisterWorkflow`+`RegisterActivity` / `workflow.ExecuteActivity` (go). - **TRANSFORM:** Register one function. Turn each activity into a plain function called via `ctx.run`. Drop Task Queue wiring and `start_to_close_timeout`. - **TEMPORAL SOURCE:** `samples-{python,typescript,go}/hello-world` (py: `hello/hello_activity.py`). - **RESONATE TARGET:** `example-hello-world-{ts,py,rs,go}`. - **RELATED SKILL:** `resonate-basic-durable-world-usage-{typescript,python,rust,go}`. - **COVERAGE:** ts ✅ py ✅ rs ✅ go ✅. ## Pattern: Composing functions (child workflows) - **DETECT:** `executeChild` (ts), `workflow.ExecuteChildWorkflow` (go), `workflow.execute_child_workflow` (py). - **TRANSFORM:** Replace the separate child Workflow type with a call to a registered function by name (`ctx.rpc("name", args)` / `ctx.run(fn, args)`). A function can recurse on itself. Optionally pin child ids with `.options(id=…)`. - **TEMPORAL SOURCE:** `samples-typescript/child-workflows`, `samples-go/child-workflow` (no `samples-python` child-workflow example). - **RESONATE TARGET:** `example-recursive-factorial-{ts,py,rs,go}`. - **RELATED SKILL:** `resonate-recursive-fan-out-pattern-{typescript,python,rust,go}`. - **COVERAGE:** ts ✅ py ✅ rs ✅ go ✅. ## Pattern: Fan-out / fan-in (parallel + join) - **DETECT:** `Promise.all` over `executeChild`/activities (ts), `asyncio.gather` (py), multiple `workflow.ExecuteActivity` futures then `.Get()` (go). - **TRANSFORM:** Start each unit non-blocking — `ctx.beginRun` (ts) / `ctx.rfi` (py) / `ctx.run(...).spawn()` (rs) / `ctx.RPC` (go); each returns a future immediately. Then await each future. Start ALL before awaiting ANY, or the work serializes. - **TEMPORAL SOURCE:** `samples-typescript/child-workflows` (`Promise.all`), `samples-python/hello/hello_parallel_activity.py`, `samples-go/splitmerge-future`. - **RESONATE TARGET:** `example-fan-out-fan-in-{ts,py,rs,go}`. - **RELATED SKILL:** `resonate-recursive-fan-out-pattern-{typescript,python,rust,go}`. - **COVERAGE:** ts ✅ py ✅ rs ✅ go ✅. ## Pattern: Durable timers - **DETECT:** `workflow.sleep(timedelta)` (py), `sleep('30 days')` (ts), `workflow.NewTimer` (go). - **TRANSFORM:** Replace with `ctx.sleep(duration)`. **Mind the units:** - TypeScript: **milliseconds** (`ctx.sleep(ms)`). - Python: **seconds** as float (`ctx.sleep(secs)`). - Go: `time.Duration` (`ctx.Sleep(d)` then `f.Await(nil)`). - Rust: `std::time::Duration` (`ctx.sleep(Duration::from_secs(n)).await?`). - **TEMPORAL SOURCE:** `samples-{python,typescript,go}/sleep-for-days`. - **RESONATE TARGET:** `example-durable-sleep-{ts,py,rs,go}`. - **RELATED SKILL:** `resonate-durable-sleep-scheduled-work-{typescript,rust,go}`. - **COVERAGE:** ts ✅ py ✅ rs ✅ go ✅. ## Pattern: Signals → durable promises (human-in-the-loop) - **DETECT:** `defineSignal`/`setHandler`/`condition`/`defineQuery` (ts), `@workflow.signal`/`workflow.wait_condition` (py), `workflow.GetSignalChannel`/`workflow.Await`/`Selector` (go). - **TRANSFORM:** Replace the signal definition + handler + flag + condition with a single latent durable promise (`p = ctx.promise()`; await `p`). Surface `p.id` to whoever will resolve it (email/webhook/log). Replace `handle.signal(...)` with `resonate.promises.resolve(id, value)`. Delete Query handlers. - **RESOLVE API — verify against pinned version:** - ts (0.11.4): `resonate.promises.resolve(id, { data: Buffer.from(JSON.stringify(v)).toString("base64") })` - py (0.7.4): `await resonate.promises.resolve(id, value)` — positional `id` and a `Value`, not `resolve(id=…, ikey=…)`; there is no `ikey` kwarg on `Promises.resolve` in this release. - rs (0.6.0): `resonate.promises.resolve(&id, Value::from_serializable(v)?)` (the example repo may use `json!(v)` — verify it compiles against your released crate version; use the `Value` form if not) - go (0.1.0): `r.Promises().Resolve(ctx, id, v)` — the direct `Promises()` sub-client handles the codec encoding for you. The CLI (`resonate promises resolve <id> --value '{"data":"…"}'`) and the low-level `r.Sender().PromiseSettle(...)` (manual base64-encoded codec value) remain available for cross-process or non-Go settlement. - **TEMPORAL SOURCE:** `samples-typescript/signals-queries`, `samples-python/hello/hello_signal.py`, `samples-go/await-signals`. - **RESONATE TARGET:** `example-human-in-the-loop-{ts,py,rs,go}`. - **RELATED SKILL:** `resonate-human-in-the-loop-pattern-{typescript,python,rust,go}`. - **COVERAGE:** ts ✅ py ✅ rs ✅ go ✅ (`0.1.0` has a direct `Promises().Resolve`; the CLI and low-level `Sender().PromiseSettle` remain as alternates). ## Pattern: Saga / compensation - **DETECT:** a compensation list drained in `catch` (ts), stacked `defer` compensations (go), handler-coordinated compensation (py). - **TRANSFORM:** Run each step with `ctx.run`. On failure, run the undo inline in the `catch`/error branch, guarded by which steps actually completed. No compensation stack, no drain helper. Make compensations idempotent. - **TEMPORAL SOURCE:** `samples-typescript/saga`, `samples-go/saga`, `samples-python/message_passing/waiting_for_handlers_and_compensation`. - **RESONATE TARGET:** `example-saga-booking-ts`, `example-money-transfer-{py,rs}`. - **RELATED SKILL:** `resonate-saga-pattern-{typescript,python,rust,go}`. - **COVERAGE:** ts ✅ py ✅ rs ✅ go ⚠️ (no Go example yet — map by analogy). ## Pattern: Long-running loops - **DETECT:** `continueAsNew` (ts), `workflow.NewContinueAsNewError` (go), `workflow.continue_as_new` (py). - **TRANSFORM:** - Bounded loop → plain `while`/`for` with `ctx.run` + `ctx.sleep`. No `continueAsNew` equivalent needed. - **Truly unbounded loop → `ctx.detached(self, n+1)` tail-recursion**, split *inside* the per-iteration function. A naive infinite loop in a single durable invocation accumulates child promises that get re-walked on replay; once replay time exceeds the task lease, the worker loop stalls. Do not emit a naive unbounded loop (`while(true)` / `while True:` / `loop {}`) for genuinely infinite loops. - **TEMPORAL SOURCE:** `samples-typescript/continue-as-new`, `samples-go/child-workflow-continue-as-new`, `samples-python/hello/hello_continue_as_new.py`. - **RESONATE TARGET:** `example-infinite-workflow-{ts,go}`. - **COVERAGE:** ts ✅ go ✅ py ⚠️ rs ⚠️ (no py/rs example yet — map by analogy). ## Pattern: Distributed mutex (TypeScript only) - **DETECT:** a lock-manager workflow with a signal queue, `uuid4()` release tokens, and `continueAsNew`. - **TRANSFORM:** Delete the lock machinery. Sequential `yield* ctx.run()` calls in a generator are serialized by the runtime — the generator is the lock. No signals, no tokens, no deadlock surface. - **TEMPORAL SOURCE:** `samples-typescript/mutex`. - **RESONATE TARGET:** `example-distributed-mutex-ts`. - **COVERAGE:** ts ✅ (TypeScript only). ## Pattern: Encryption (TypeScript only) - **DETECT:** a `PayloadCodec` (`encode`/`decode` over `Payload[]`) + custom `DataConverter` + codec server. - **TRANSFORM:** Replace with a single `Encryptor` (`encrypt(Value): Value` / `decrypt(Value): Value`) passed as a constructor option: `new Resonate({ encryptor })`. Workflow code is unchanged; the SDK handles promise-store serialization. - **TEMPORAL SOURCE:** `samples-typescript/encryption`. - **RESONATE TARGET:** `example-encryption-ts`.
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub