{"id":13542823,"url":"https://github.com/vikstrous/tempts","last_synced_at":"2025-09-12T14:32:05.541Z","repository":{"id":220850193,"uuid":"752755215","full_name":"vikstrous/tempts","owner":"vikstrous","description":"Type-safe Temporal Go SDK wrapper","archived":false,"fork":false,"pushed_at":"2025-04-24T12:56:29.000Z","size":202,"stargazers_count":17,"open_issues_count":2,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-24T13:43:26.543Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vikstrous.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-02-04T18:04:14.000Z","updated_at":"2025-04-09T10:29:08.000Z","dependencies_parsed_at":"2024-02-16T23:34:04.172Z","dependency_job_id":"da040598-2b84-468a-a022-3bc50c72796c","html_url":"https://github.com/vikstrous/tempts","commit_stats":null,"previous_names":["vikstrous/tstemporal","vikstrous/tempts"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vikstrous/tempts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vikstrous%2Ftempts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vikstrous%2Ftempts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vikstrous%2Ftempts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vikstrous%2Ftempts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vikstrous","download_url":"https://codeload.github.com/vikstrous/tempts/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vikstrous%2Ftempts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274824653,"owners_count":25356650,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-12T02:00:09.324Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T11:00:18.322Z","updated_at":"2025-09-12T14:32:05.245Z","avatar_url":"https://github.com/vikstrous.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":["Libraries"],"readme":"# tempts\n`tempts` stands for \"Temporal Type-Safe\", a Go SDK wrapper for interacting with temporal more safely.\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/vikstrous/tempts.svg)](https://pkg.go.dev/github.com/vikstrous/tempts)\n\n\n## Example Usage\n\nAdd this dependency with\n```\ngo get github.com/vikstrous/tempts@latest\n```\n\nBelow is a simple example demonstrating how to define a workflow and an activity, register them, and execute the workflow using `tempts`.\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"time\"\n\n    \"github.com/vikstrous/tempts\"\n    \"go.temporal.io/sdk/client\"\n    \"go.temporal.io/sdk/worker\"\n    \"go.temporal.io/sdk/workflow\"\n)\n\n// Define a new task queue.\nvar queueMain = tempts.NewQueue(\"main\")\n\n// Define a workflow with no parameters and no return.\nvar workflowTypeHello = tempts.NewWorkflow[struct{}, struct{}](queueMain, \"HelloWorkflow\")\n\n// Define an activity with no parameters and no return.\nvar activityTypeHello = tempts.NewActivity[struct{}, struct{}](queueMain, \"HelloActivity\")\n\nfunc main() {\n    // Create a new client connected to the Temporal server.\n    c, err := tempts.Dial(client.Options{})\n    if err != nil {\n        panic(err)\n    }\n    defer c.Close()\n\n    // Register the workflow and activity in a new worker.\n    wrk, err := tempts.NewWorker(queueMain, []tempts.Registerable{\n        workflowTypeHello.WithImplementation(helloWorkflow),\n        activityTypeHello.WithImplementation(helloActivity),\n    })\n    if err != nil {\n        panic(err)\n    }\n    ctx := context.Background()\n    ctx, cancel := context.WithCancel(ctx)\n    defer cancel()\n    go func() {\n        err = wrk.Run(ctx, c, worker.Options{})\n        if err != nil {\n            panic(err)\n        }\n    }()\n\n    // Execute the workflow and wait for it to complete.\n    _, err = workflowTypeHello.Run(ctx, c, client.StartWorkflowOptions{}, struct{}{})\n    if err != nil {\n        panic(err)\n    }\n\n    fmt.Println(\"Workflow completed.\")\n}\n\n// helloWorkflow is a workflow function that calls the HelloActivity.\nfunc helloWorkflow(ctx workflow.Context, _ struct{}) (struct{}, error) {\n    return activityTypeHello.Run(ctx, struct{}{})\n}\n\n// helloActivity is an activity function that prints \"Hello, Temporal!\".\nfunc helloActivity(ctx context.Context, _ struct{}) (struct{}, error) {\n    fmt.Println(\"Hello, Temporal!\")\n    return struct{}{}, nil\n}\n\n```\n\nThis example sets up a workflow and an activity that simply prints a greeting. It demonstrates the basic setup and execution flow using `tempts`. To see a more complex example, look in the example directory.\n\n**WARNING: This library can change without notice while I respond to feedback and improve the API. I'll remove this warning when I'm happy with the API and can promise it won't change.**\n\n## Guarantees\n\nList of guarantees provided by this wrapper:\n\nWorkers:\n\n* Have all the right activities and workflows registered before starting\n\nActivities:\n\n* Are called on the right queue\n* Are called with the right parameter types\n* Return the right response types\n* Registered functions match the right type signature\n\nWorkflows:\n\n* Are called on the right queue\n* Are called with the right parameter types\n* Return the right response types\n* Registered functions match the right type signature\n\nSchedules:\n\n* Set arguments with the right types\n* Can be set upon application startup, automatically applying the intended effect to the schedule's state on the cluster\n\nQueries and updates:\n\n* Are called with the right types\n* Return the right types\n* Registered functions match the right type signature\n\n### Tools\n\nThere are two functions in this library that make it easy to write fixture based replyabaility tests for your tempts workflows and activities.\nSee `example/main_test.go` for an example of how to use them.\n```go\nfunc GetWorkflowHistoriesBundle(ctx context.Context, client *tempts.Client, w *tempts.WorkflowWithImpl) ([]byte, error)\nfunc ReplayWorkflow(historiesBytes []byte, w *tempts.WorkflowWithImpl, opts worker.WorkflowReplayerOptions) error\n```\n\n## User guide by example\n\nThese examples assume that you are already familiar with the Go SDK and just need to know how to do the equivalent thing using this library.\n\n### Connect to temporal\n\nInstead of:\n```go\nc, err := tempts.Dial(client.Options{})\n```\nDo:\n```go\nc, err := client.Dial(client.Options{})\n```\n\n### Run a workflow to completion\n\nInstead of:\n```go\nvar ret ReturnType\nc.ExecuteWorkflow(ctx, opts, name, param).Get(ctx, \u0026ret)\n```\nDo:\n```go\n// Globally define the queue and workflow type (one time, in a centralized package for the queue)\nvar queueMain = tempts.NewQueue(\"main\")\ntype exampleWorkflowParamType struct{\n    Param string\n}\ntype exampleWorkflowReturnType struct{\n    Return string\n}\nvar exampleWorkflowType = tempts.NewWorkflow[exampleWorkflowParamType, exampleWorkflowReturnType](queueMain, \"ExampleWorkflow\")\n\n// Run and get the result of the workflow\nret, err := exampleWorkflowType.Run(ctx, c, opts, param)\n// Use Execute instead of Run to not wait for completion\n```\nThis ensures that the workflow is run on the right queue, with the right name, with the right parameter types and it returns the right type.\n\n### Run an activity to completion\n\nInstead of:\n```go\nvar ret ReturnType\nworkflow.ExecuteActivity(ctx, name, param).Get(ctx, \u0026ret)\n```\nDo:\n```go\n// Globally define the queue and activity type (one time, in a centralized package for the queue)\nvar queueMain = tempts.NewQueue(\"main\")\ntype exampleActivityParamType struct{\n    Param string\n}\ntype exampleActivityReturnType struct{\n    Return string\n}\nvar exampleActivityType = tempts.NewActivity[exampleActivityParamType, exampleActivityReturnType](queueMain, \"ExampleActivity\")\n\n// Run and get the result of the activity from a workflow\nret, err := exampleActivityType.Run(ctx, param)\n// Use Execute instead of Run to not wait for completion\n```\n\nThis ensures that the activity is run on the right queue, with the right name, with the right parameter types and it returns the right type.\n\n### Create a worker\n\nInstead of:\n```go\nwrk := worker.New(c, queueName, options)\nerr = wrk.Run(worker.InterruptCh())\n```\nDo:\n```go\n// Globally define the queue (one time, in a centralized package for the queue)\nvar queueMain = tempts.NewQueue(nsDefault, \"main\")\n\n// On start up of your service\nwrk, err := tempts.NewWorker(queueMain, []tempts.Registerable{\n    exampleWorkflowType.WithImplementation(exampleWorkflow),\n    exampleActivityType.WithImplementation(exampleActivity),\n})\n\nerr = wrk.Run(ctx, c, worker.Options{})\n```\nThis ensures that all the right workflows and activities are registered for this worker to satisfy the expectations for this queue. No more and no less.\n\n### Query a workflow\n\nInstead of:\n```go\n// In the workflow\nworkflow.SetQueryHandler(ctx, queryName, func(queryParamType) (queryReturnType, error) {\n    return queryReturnType{}, nil\n})\n\n// Querying it from your application\nresponse, err := c.QueryWorkflow(ctx, workflowID, runID, queryName, param)\n\nvar value Return\nerr = response.Get(\u0026value)\n```\nDo:\n```go\n// Globally define the query type\nvar exampleQueryType = tempts.NewQueryHandler[queryParamType, queryReturnType](\"exampleQuery\")\n\n// In the workflow\nexampleQueryType.SetHandler(ctx, func(queryParamType) (queryReturnType, error) {\n    return queryReturnType{}, nil\n})\n\n// Query it from your application\nret, err := exampleQueryType.Query(ctx, c, workflowID, runID, param)\n```\n\nThis ensures that the types match in the application calling the workflow and in the workflow handler function. Unfortunately, we don't guarantee that the workflow is the expected type.\n\n### Create a schedule\n\nInstead of:\n```go\n_, err = c.ScheduleClient().Create(ctx, client.ScheduleOptions{\n    ID:   scheduleID,\n    Spec: client.ScheduleSpec{\n        Intervals: []client.ScheduleIntervalSpec{\n            {\n                Every: time.Second * 5,\n            },\n        },\n    },\n    Action: \u0026client.ScheduleWorkflowAction{\n        ID:        workflowID,\n        Workflow:  workflowName,\n        TaskQueue: queueName,\n        Args:      []any{param},\n    },\n})\n// Ignore the error if it exists already\n```\nDo:\n```go\n// This assumes the workflow is already globally registered\n\n// Call this to make sure the schedule matches what your service expects\nerr = workflowTypeFormatAndGreet.SetSchedule(ctx, c, client.ScheduleOptions{\n    ID: scheduleID,\n    Spec: client.ScheduleSpec{\n        Intervals: []client.ScheduleIntervalSpec{\n            {\n                Every: time.Second * 5,\n            },\n        },\n    },\n}, param)\n```\n\nThis ensures that the queue is correct for the workflow. It also ensures that the parameter is the right type and that the schedule is updated to match the one defined in your code. It doesn't handle every possible difference yet because temporal doesn't support arbitrary changes to schedules. This feature is a bit less polished than the rest of the package, so let me know how to improve it!\n\n### Fixture based tests\n\nInstead of: coming up with your own strategy to build fixture based tests\nDo:\n```go\nc, err := tempts.Dial(client.Options{})\nif err != nil {\n    t.Fatal(err)\n}\nhistoriesData, err = tempts.GetWorkflowHistoriesBundle(ctx, c, exampleWorkflowType)\nif err != nil {\n    t.Fatal(err)\n}\n// Now store historiesData somewhere! (Or don't and make sure your test is always connected to a temporal instance with example workflow runs)\nerr := tempts.ReplayWorkflow(historiesData, exampleWorkflow, worker.WorkflowReplayerOptions{})\nif err != nil {\n    t.Fatal(err)\n}\n```\n\nThis is really just the cherry on top once you have your type safety in place. By running fixture based tests, you can make sure to not introduce backwards incompatible changes without versioning them correctly. Even if you don't end up using this package, feel free to adapt this pattern for your own needs. It's not a lot of code for how much extra safety it provides.\n\n## Migration for Go SDK users\n\nSince this library is opinionated, it doesn't support all temporal features. To use this library effectively, the temporal queue you are migrating must meet these pre-requisities:\n* Queues names must be static (assuming that your types are defined as global variables so they can be used anywhere).\n* All workflows and activities for a given queue must be migrated at once.\n* All types must be defined as Go structs that follow the Temporal Go SDK's marshaling and unmarshaling logic.\n\nThere maybe more restrictions that I'm not aware of yet. Open an issue if any are missed.\n\nTo simplify migration, if your workflows and activities don't use a single struct input type, use `NewWorkflowPositional`/`NewActivityPositional`. Don't use these functions in new code.\n\n## Potential future improvements\n\n* Wrap the APIs for channels and signals\n* Return typed futures instead of generic ones\n* Ensure that all queries are defined on the right workflows\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvikstrous%2Ftempts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvikstrous%2Ftempts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvikstrous%2Ftempts/lists"}