From 0950eae266e0bc93c5345cad28d1976f319c6f18 Mon Sep 17 00:00:00 2001 From: Wes Date: Fri, 10 Jan 2025 14:24:20 -0700 Subject: [PATCH] fix: lsp autocomplete and hover items to match current FTL (#3965) Fixes #3963 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/lsp/completion.go | 28 ++++++++-------- internal/lsp/hoveritems.go | 2 +- .../{configDeclare.md => config.md} | 5 +-- internal/lsp/markdown/completion/cron.md | 2 +- .../lsp/markdown/completion/cronExpression.md | 5 ++- internal/lsp/markdown/completion/enumType.md | 5 +-- internal/lsp/markdown/completion/enumValue.md | 8 ++--- internal/lsp/markdown/completion/ingress.md | 4 +-- .../lsp/markdown/completion/pubSubSink.md | 17 ---------- .../markdown/completion/pubSubSubscription.md | 15 +++++++-- .../lsp/markdown/completion/pubSubTopic.md | 31 ++++++++++++++++-- internal/lsp/markdown/completion/retry.md | 12 +++++-- .../lsp/markdown/completion/retryWithCatch.md | 32 +++++++++++++++++++ .../{secretDeclare.md => secret.md} | 5 +-- internal/lsp/markdown/completion/typeAlias.md | 5 ++- internal/lsp/markdown/completion/verb.md | 21 ++++++++++-- 16 files changed, 140 insertions(+), 57 deletions(-) rename internal/lsp/markdown/completion/{configDeclare.md => config.md} (59%) delete mode 100644 internal/lsp/markdown/completion/pubSubSink.md create mode 100644 internal/lsp/markdown/completion/retryWithCatch.md rename internal/lsp/markdown/completion/{secretDeclare.md => secret.md} (57%) diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index f13841df91..f3595f3753 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -34,20 +34,20 @@ var cronExpressionCompletionDocs string //go:embed markdown/completion/retry.md var retryCompletionDocs string -//go:embed markdown/completion/configDeclare.md -var declareConfigCompletionDocs string +//go:embed markdown/completion/retryWithCatch.md +var retryWithCatchCompletionDocs string -//go:embed markdown/completion/secretDeclare.md -var declareSecretCompletionDocs string +//go:embed markdown/completion/config.md +var configCompletionDocs string + +//go:embed markdown/completion/secret.md +var secretCompletionDocs string //go:embed markdown/completion/pubSubTopic.md -var declarePubSubTopicCompletionDocs string +var pubSubTopicCompletionDocs string //go:embed markdown/completion/pubSubSubscription.md -var declarePubSubSubscriptionCompletionDocs string - -//go:embed markdown/completion/pubSubSink.md -var definePubSubSinkCompletionDocs string +var pubSubSubscriptionCompletionDocs string // Markdown is split by "---". First half is completion docs, second half is insert text. var completionItems = []protocol.CompletionItem{ @@ -59,11 +59,11 @@ var completionItems = []protocol.CompletionItem{ completionItem("ftl:cron", "FTL Cron", cronCompletionDocs), completionItem("ftl:cron:expression", "FTL Cron with expression", cronExpressionCompletionDocs), completionItem("ftl:retry", "FTL Retry", retryCompletionDocs), - completionItem("ftl:config:declare", "Declare config", declareConfigCompletionDocs), - completionItem("ftl:secret:declare", "Declare secret", declareSecretCompletionDocs), - completionItem("ftl:pubsub:topic", "Declare PubSub topic", declarePubSubTopicCompletionDocs), - completionItem("ftl:pubsub:subscription", "Declare a PubSub subscription", declarePubSubSubscriptionCompletionDocs), - completionItem("ftl:pubsub:sink", "Define a PubSub sink", definePubSubSinkCompletionDocs), + completionItem("ftl:retry:catch", "FTL Retry with catch", retryWithCatchCompletionDocs), + completionItem("ftl:config", "Create a new configuration value", configCompletionDocs), + completionItem("ftl:secret", "Create a new secret value", secretCompletionDocs), + completionItem("ftl:pubsub:topic", "Create a PubSub topic", pubSubTopicCompletionDocs), + completionItem("ftl:pubsub:subscription", "Create a PubSub subscription", pubSubSubscriptionCompletionDocs), } // Track which directives are //ftl: prefixed, so the we can autocomplete them via `/`. diff --git a/internal/lsp/hoveritems.go b/internal/lsp/hoveritems.go index 91a578da34..423ebb4b35 100644 --- a/internal/lsp/hoveritems.go +++ b/internal/lsp/hoveritems.go @@ -6,7 +6,7 @@ var hoverMap = map[string]string{ "//ftl:enum": "## Type enums (sum types)\n\n[Sum types](https://en.wikipedia.org/wiki/Tagged_union) are supported by FTL's type system, but aren't directly supported by Go. However they can be approximated with the use of [sealed interfaces](https://blog.chewxy.com/2018/03/18/golang-interfaces/). To declare a sum type in FTL use the comment directive `//ftl:enum`:\n\n```go\n//ftl:enum\ntype Animal interface { animal() }\n\ntype Cat struct {}\nfunc (Cat) animal() {}\n\ntype Dog struct {}\nfunc (Dog) animal() {}\n```\n## Value enums\n\nA value enum is an enumerated set of string or integer values.\n\n```go\n//ftl:enum\ntype Colour string\n\nconst (\n Red Colour = \"red\"\n Green Colour = \"green\"\n Blue Colour = \"blue\"\n)\n```\n", "//ftl:ingress": "## HTTP Ingress\n\nVerbs annotated with `ftl:ingress` will be exposed via HTTP (`http` is the default ingress type). These endpoints will then be available on one of our default `ingress` ports (local development defaults to `http://localhost:8891`).\n\nThe following will be available at `http://localhost:8891/http/users/123/posts?postId=456`.\n\n\n```go\ntype GetRequestPathParams struct {\n\tUserID string `json:\"userId\"`\n}\n\ntype GetRequestQueryParams struct {\n\tPostID string `json:\"postId\"`\n}\n\ntype GetResponse struct {\n\tMessage string `json:\"msg\"`\n}\n\n//ftl:ingress GET /http/users/{userId}/posts\nfunc Get(ctx context.Context, req builtin.HttpRequest[ftl.Unit, GetRequestPathParams, GetRequestQueryParams]) (builtin.HttpResponse[GetResponse, ErrorResponse], error) {\n // ...\n}\n```\n\nBecause the example above only has a single path parameter it can be simplified by just using a scalar such as `string` or `int64` as the path parameter type:\n\n```go\n\n//ftl:ingress GET /http/users/{userId}/posts\nfunc Get(ctx context.Context, req builtin.HttpRequest[ftl.Unit, int64, GetRequestQueryParams]) (builtin.HttpResponse[GetResponse, ErrorResponse], error) {\n // ...\n}\n```\n\n> **NOTE!**\n> The `req` and `resp` types of HTTP `ingress` [verbs](../verbs) must be `builtin.HttpRequest` and `builtin.HttpResponse` respectively. These types provide the necessary fields for HTTP `ingress` (`headers`, `statusCode`, etc.)\n>\n> You will need to import `ftl/builtin`.\n\nKey points:\n\n- `ingress` verbs will be automatically exported by default.\n\n## Field mapping\n\nThe `HttpRequest` request object takes 3 type parameters, the body, the path parameters and the query parameters.\n\nGiven the following request verb:\n\n```go\n\ntype PostBody struct{\n\tTitle string `json:\"title\"`\n\tContent string `json:\"content\"`\n\tTag ftl.Option[string] `json:\"tag\"`\n}\ntype PostPathParams struct {\n\tUserID string `json:\"userId\"`\n\tPostID string `json:\"postId\"`\n}\n\ntype PostQueryParams struct {\n\tPublish boolean `json:\"publish\"`\n}\n\n//ftl:ingress http PUT /users/{userId}/posts/{postId}\nfunc Get(ctx context.Context, req builtin.HttpRequest[PostBody, PostPathParams, PostQueryParams]) (builtin.HttpResponse[GetResponse, string], error) {\n\treturn builtin.HttpResponse[GetResponse, string]{\n\t\tHeaders: map[string][]string{\"Get\": {\"Header from FTL\"}},\n\t\tBody: ftl.Some(GetResponse{\n\t\t\tMessage: fmt.Sprintf(\"UserID: %s, PostID: %s, Tag: %s\", req.pathParameters.UserID, req.pathParameters.PostID, req.Body.Tag.Default(\"none\")),\n\t\t}),\n\t}, nil\n}\n```\n\nThe rules for how each element is mapped are slightly different, as they have a different structure:\n\n- The body is mapped directly to the body of the request, generally as a JSON object. Scalars are also supported, as well as []byte to get the raw body. If they type is `any` then it will be assumed to be JSON and mapped to the appropriate types based on the JSON structure.\n- The path parameters can be mapped directly to an object with field names corresponding to the name of the path parameter. If there is only a single path parameter it can be injected directly as a scalar. They can also be injected as a `map[string]string`.\n- The path parameters can also be mapped directly to an object with field names corresponding to the name of the path parameter. They can also be injected directly as a `map[string]string`, or `map[string][]string` for multiple values.\n\n#### Optional fields\n\nOptional fields are represented by the `ftl.Option` type. The `Option` type is a wrapper around the actual type and can be `Some` or `None`. In the example above, the `Tag` field is optional.\n\n```sh\ncurl -i http://localhost:8891/users/123/posts/456\n```\n\nBecause the `tag` query parameter is not provided, the response will be:\n\n```json\n{\n \"msg\": \"UserID: 123, PostID: 456, Tag: none\"\n}\n```\n\n#### Casing\n\nField names use lowerCamelCase by default. You can override this by using the `json` tag.\n\n## SumTypes\n\nGiven the following request verb:\n\n```go\n//ftl:enum export\ntype SumType interface {\n\ttag()\n}\n\ntype A string\n\nfunc (A) tag() {}\n\ntype B []string\n\nfunc (B) tag() {}\n\n//ftl:ingress http POST /typeenum\nfunc TypeEnum(ctx context.Context, req builtin.HttpRequest[SumType, ftl.Unit, ftl.Unit]) (builtin.HttpResponse[SumType, string], error) {\n\treturn builtin.HttpResponse[SumType, string]{Body: ftl.Some(req.Body)}, nil\n}\n```\n\nThe following curl request will map the `SumType` name and value to the `req.Body`:\n\n```sh\ncurl -X POST \"http://localhost:8891/typeenum\" \\\n -H \"Content-Type: application/json\" \\\n --data '{\"name\": \"A\", \"value\": \"sample\"}'\n```\n\nThe response will be:\n\n```json\n{\n \"name\": \"A\",\n \"value\": \"sample\"\n}\n```\n\n## Encoding query params as JSON\n\nComplex query params can also be encoded as JSON using the `@json` query parameter. For example:\n\n> `{\"tag\":\"ftl\"}` url-encoded is `%7B%22tag%22%3A%22ftl%22%7D`\n\n```bash\ncurl -i http://localhost:8891/users/123/posts/456?@json=%7B%22tag%22%3A%22ftl%22%7D\n```\n\n\n\n", "//ftl:retry": "## Retries\n\nSome FTL features allow specifying a retry policy via a Go comment directive. Retries back off exponentially until the maximum is reached.\n\nThe directive has the following syntax:\n\n\n```go\n//ftl:retry [] [] [catch ]\n```\n\n\nFor example, the following function will retry up to 10 times, with a delay of 5s, 10s, 20s, 40s, 60s, 60s, etc.\n\n\n```go\n//ftl:retry 10 5s 1m\nfunc Process(ctx context.Context, in Invoice) error {\n // ...\n}\n```\n\n### PubSub\n\nSubscribers can have a retry policy. For example:\n\n\n```go\n//ftl:retry 5 1s catch recoverPaymentProcessing\nfunc ProcessPayment(ctx context.Context, payment Payment) error {\n...\n}\n```\n\n\n## Catching\nAfter all retries have failed, a catch verb can be used to safely recover.\n\nThese catch verbs have a request type of `builtin.CatchRequest` and no response type. If a catch verb returns an error, it will be retried until it succeeds so it is important to handle errors carefully.\n\n\n\n```go\n//ftl:retry 5 1s catch recoverPaymentProcessing\nfunc ProcessPayment(ctx context.Context, payment Payment) error {\n...\n}\n\n//ftl:verb\nfunc RecoverPaymentProcessing(ctx context.Context, request builtin.CatchRequest[Payment]) error {\n// safely handle final failure of the payment\n}\n```\n", - "//ftl:subscribe": "## PubSub\n\nFTL has first-class support for PubSub, modelled on the concepts of topics (where events are sent) and subscribers (a verb which consumes events). Subscribers are, as you would expect, sinks. Each subscriber is a cursor over the topic it is associated with. Each topic may have multiple subscriptions. Each published event has an at least once delivery guarantee for each subscription.\n\n\nFirst, declare a new topic:\n\n```go\npackage payments\n\nimport (\n \"github.com/block/ftl/go-runtime/ftl\"\n)\ntype Invoice struct {\n InvoiceNo string\n}\n\n//ftl:export\ntype Invoices = ftl.TopicHandle[Invoice, ftl.SinglePartitionMap[Invoice]]\n```\n\nNote that the name of the topic as represented in the FTL schema is the lower camel case version of the type name.\n\nThe `Invoices` type is a handle to the topic. It is a generic type that takes two arguments: the event type and the partition map type. The partition map type is used to map events to partitions. In this case, we are using a single partition map, which means that all events are sent to the same partition.\n\nThen define a Sink to consume from the topic:\n\n```go\n//ftl:subscribe payments.invoices from=beginning\nfunc SendInvoiceEmail(ctx context.Context, in Invoice) error {\n // ...\n}\n```\n\nEvents can be published to a topic by injecting the topic type into a verb:\n\n```go\n//ftl:verb\nfunc PublishInvoice(ctx context.Context, topic Invoices) error {\n topic.Publish(ctx, Invoice{...})\n // ...\n}\n```\n\n> **NOTE!**\n> PubSub topics cannot be published to from outside the module that declared them, they can only be subscribed to. That is, if a topic is declared in module `A`, module `B` cannot publish to it.\n", + "//ftl:subscribe": "## PubSub\n\nFTL has first-class support for PubSub, modelled on the concepts of topics (where events are sent) and subscribers (a verb which consumes events). Subscribers are, as you would expect, sinks. Each subscriber is a cursor over the topic it is associated with. Each topic may have multiple subscriptions. Each published event has an at least once delivery guarantee for each subscription.\n\nA topic can be exported to allow other modules to subscribe to it. Subscriptions are always private to their module.\n\nWhen a subscription is first created in an environment, it can start consuming from the beginning of the topic or only consume events published afterwards.\n\nTopics allow configuring the number of partitions and how each event should be mapped to a partition, allowing for greater throughput. Subscriptions will consume in order within each partition. There are cases where a small amount of progress on a subscription will be lost, so subscriptions should be able to handle receiving some events that have already been consumed.\n\n\nFirst, declare a new topic:\n\n```go\npackage payments\n\nimport (\n \"github.com/block/ftl/go-runtime/ftl\"\n)\n\n// Define an event type\ntype Invoice struct {\n InvoiceNo string\n}\n\n// ftl.TopicPartitionMap is an interface for mapping each event to a partition in the topic.\n// \n// If creating a topic with multiple partitions, you'll need to define a partition mapper for your event type.\n// Otherwise you can use ftl.SinglePartitionMap[Event]\ntype PartitionMapper struct{}\n\nvar _ ftl.TopicPartitionMap[PubSubEvent] = PartitionMapper{}\n\nfunc (PartitionMapper) PartitionKey(event PubSubEvent) string {\n\treturn event.Time.String()\n}\n\n//ftl:topic export partitions=10\ntype Invoices = ftl.TopicHandle[Invoice, PartitionMapper]\n```\n\nNote that the name of the topic as represented in the FTL schema is the lower camel case version of the type name.\n\nThe `Invoices` type is a handle to the topic. It is a generic type that takes two arguments: the event type and the partition map type. The partition map type is used to map events to partitions.\n\nThen define a Sink to consume from the topic:\n\n```go\n// Configure initial event consumption with either from=beginning or from=latest\n//\n//ftl:subscribe payments.invoices from=beginning\nfunc SendInvoiceEmail(ctx context.Context, in Invoice) error {\n // ...\n}\n```\n\nEvents can be published to a topic by injecting the topic type into a verb:\n\n```go\n//ftl:verb\nfunc PublishInvoice(ctx context.Context, topic Invoices) error {\n topic.Publish(ctx, Invoice{...})\n // ...\n}\n```\n\n> **NOTE!**\n> PubSub topics cannot be published to from outside the module that declared them, they can only be subscribed to. That is, if a topic is declared in module `A`, module `B` cannot publish to it.\n", "//ftl:typealias": "## Type aliases\n\nA type alias is an alternate name for an existing type. It can be declared like so:\n\n```go\n//ftl:typealias\ntype Alias Target\n```\nor\n```go\n//ftl:typealias\ntype Alias = Target\n```\n\neg.\n\n```go\n//ftl:typealias\ntype UserID string\n\n//ftl:typealias\ntype UserToken = string\n```\n", "//ftl:verb": "## Verbs\n\n## Defining Verbs\n\n\nTo declare a Verb, write a normal Go function with the following signature, annotated with the Go [comment directive](https://tip.golang.org/doc/comment#syntax) `//ftl:verb`:\n\n```go\n//ftl:verb\nfunc F(context.Context, In) (Out, error) { }\n```\n\neg.\n\n```go\ntype EchoRequest struct {}\n\ntype EchoResponse struct {}\n\n//ftl:verb\nfunc Echo(ctx context.Context, in EchoRequest) (EchoResponse, error) {\n // ...\n}\n```\n\n\nBy default verbs are only [visible](../visibility) to other verbs in the same module.\n\n## Calling Verbs\n\n\nTo call a verb, import the module's verb client (`{ModuleName}.{VerbName}Client`), add it to your verb's signature, then invoke it as a function. eg.\n\n```go\n//ftl:verb\nfunc Echo(ctx context.Context, in EchoRequest, tc time.TimeClient) (EchoResponse, error) {\n\tout, err := tc(ctx, TimeRequest{...})\n}\n```\n\nVerb clients are generated by FTL. If the callee verb belongs to the same module as the caller, you must build the \nmodule first (with callee verb defined) in order to generate its client for use by the caller. Local verb clients are \navailable in the generated `types.ftl.go` file as `{VerbName}Client`.\n\n", } diff --git a/internal/lsp/markdown/completion/configDeclare.md b/internal/lsp/markdown/completion/config.md similarity index 59% rename from internal/lsp/markdown/completion/configDeclare.md rename to internal/lsp/markdown/completion/config.md index f76570cfb9..a7bc26ca96 100644 --- a/internal/lsp/markdown/completion/configDeclare.md +++ b/internal/lsp/markdown/completion/config.md @@ -3,10 +3,11 @@ Declare a config variable. Configuration values are named, typed values. They are managed by the `ftl config` command-line. ```go -var defaultUser = ftl.Config[string]("defaultUser") +// Will create a config value called "myConfig" in the FTL schema +type MyConfig = ftl.Config[string] ``` See https://block.github.io/ftl/docs/reference/secretsconfig/ --- -var ${1:configVar} = ftl.Config[${2:Type}]("${1:configVar}") +type ${1:Name} = ftl.Config[${2:Type}] diff --git a/internal/lsp/markdown/completion/cron.md b/internal/lsp/markdown/completion/cron.md index a1b517a130..8cd5d452e1 100644 --- a/internal/lsp/markdown/completion/cron.md +++ b/internal/lsp/markdown/completion/cron.md @@ -1,6 +1,6 @@ Declare a cron job. -A cron job is an Empty verb that will be called on a schedule. +A cron job is an Empty verb that will be called on a schedule. Supports both cron expressions and duration format. ```go //ftl:cron 0 * * * * diff --git a/internal/lsp/markdown/completion/cronExpression.md b/internal/lsp/markdown/completion/cronExpression.md index 883978846f..7e7a26fe78 100644 --- a/internal/lsp/markdown/completion/cronExpression.md +++ b/internal/lsp/markdown/completion/cronExpression.md @@ -1,6 +1,6 @@ Declare a cron job, autocompleting on a cron expression. -A cron job is an Empty verb that will be called on a schedule. +A cron job is an Empty verb that will be called on a schedule. Supports both cron expressions and duration format. ```go //ftl:cron 0 * * * * @@ -8,6 +8,9 @@ func Hourly(ctx context.Context) error {} //ftl:cron 6h func EverySixHours(ctx context.Context) error {} + +//ftl:cron Mon +func Mondays(ctx context.Context) error {} ``` See https://block.github.io/ftl/docs/reference/cron/ diff --git a/internal/lsp/markdown/completion/enumType.md b/internal/lsp/markdown/completion/enumType.md index 5179443343..988db1ecc6 100644 --- a/internal/lsp/markdown/completion/enumType.md +++ b/internal/lsp/markdown/completion/enumType.md @@ -1,6 +1,6 @@ -Declare a type enum (sum types). +Declare a type enum (sum type). -A type enum is a set of types that can be used as a single type, which `go` does not directly support. +Type enums allow you to define a set of related types that implement a common interface. This provides sum type functionality that Go doesn't natively support. ```go //ftl:enum @@ -20,4 +20,5 @@ See https://block.github.io/ftl/docs/reference/types/ type ${1:Type} interface { ${2:interface}() } type ${3:Value} struct {} + func (${3:Value}) ${2:interface}() {} diff --git a/internal/lsp/markdown/completion/enumValue.md b/internal/lsp/markdown/completion/enumValue.md index b541cd699f..b266c6c557 100644 --- a/internal/lsp/markdown/completion/enumValue.md +++ b/internal/lsp/markdown/completion/enumValue.md @@ -4,12 +4,12 @@ A value enum is an enumerated set of string or integer values. ```go //ftl:enum -type Colour string +type Color string const ( - Red Colour = "red" - Green Colour = "green" - Blue Colour = "blue" + Red Color = "red" + Green Color = "green" + Blue Color = "blue" ) ``` diff --git a/internal/lsp/markdown/completion/ingress.md b/internal/lsp/markdown/completion/ingress.md index 422a09181b..d347869a0f 100644 --- a/internal/lsp/markdown/completion/ingress.md +++ b/internal/lsp/markdown/completion/ingress.md @@ -1,6 +1,6 @@ Declare an ingress function. -Verbs annotated with `ftl:ingress` will be exposed via HTTP (http is the default ingress type). These endpoints will then be available on one of our default ingress ports (local development defaults to http://localhost:8891). +Verbs annotated with `ftl:ingress` will be exposed via HTTP. These endpoints will be available on default ingress ports (local development defaults to http://localhost:8891). ```go type GetPathParams struct { @@ -34,7 +34,7 @@ type ${1:Func}Response struct { } //ftl:ingress ${2:GET} ${3:/url/path} -func ${1:Func}(ctx context.Context, req builtin.HttpRequest[ftl.Unit, flt.Unit, ${1:Func}Request]) (builtin.HttpResponse[${1:Func}Response, string], error) { +func ${1:Func}(ctx context.Context, req builtin.HttpRequest[ftl.Unit, ftl.Unit, ${1:Func}Request]) (builtin.HttpResponse[${1:Func}Response, string], error) { ${4:// TODO: Implement} return builtin.HttpResponse[${1:Func}Response, string]{ Status: 200, diff --git a/internal/lsp/markdown/completion/pubSubSink.md b/internal/lsp/markdown/completion/pubSubSink.md deleted file mode 100644 index 79e224bf05..0000000000 --- a/internal/lsp/markdown/completion/pubSubSink.md +++ /dev/null @@ -1,17 +0,0 @@ -Declare a sink function that consumes events from a PubSub subscription. - -```go -//ftl:subscribe emailInvoices -func SendInvoiceEmail(ctx context.Context, in Invoice) error { - // ... -} -``` - -See https://block.github.io/ftl/docs/reference/pubsub/ ---- - -//ftl:subscribe ${1:subscriptionName} -func ${2:FunctionName}(ctx context.Context, in ${3:Type}) error { - ${4:// TODO: Implement} - return nil -} diff --git a/internal/lsp/markdown/completion/pubSubSubscription.md b/internal/lsp/markdown/completion/pubSubSubscription.md index 964a97d827..6adb5e6062 100644 --- a/internal/lsp/markdown/completion/pubSubSubscription.md +++ b/internal/lsp/markdown/completion/pubSubSubscription.md @@ -1,10 +1,21 @@ Declare a subscription to a topic. +A subscription consumes events from a topic, starting either from the beginning of the topic or only new events. + ```go -var _ = ftl.Subscription(invoicesTopic, "emailInvoices") +//ftl:subscribe payments.invoices from=beginning +func SendInvoiceEmail(ctx context.Context, in Invoice) error { + // Process the invoice event + return nil +} ``` See https://block.github.io/ftl/docs/reference/pubsub/ --- -var _ = ftl.Subscription(${1:topicVar}, "${2:subscriptionName}") +//ftl:subscribe ${1:topicName} from=${2:beginning} +func ${3:OnEvent}(ctx context.Context, in ${4:EventType}) error { + ${5:// TODO: Implement} + return nil +} + diff --git a/internal/lsp/markdown/completion/pubSubTopic.md b/internal/lsp/markdown/completion/pubSubTopic.md index 1bf9d3177f..ea1375ca5d 100644 --- a/internal/lsp/markdown/completion/pubSubTopic.md +++ b/internal/lsp/markdown/completion/pubSubTopic.md @@ -1,10 +1,35 @@ -Declare a PubSub topic. +Declare a topic for publishing events. + +Topics are where events are sent in the PubSub system. A topic can be exported to allow other modules to subscribe to it. Each topic can have multiple subscriptions. ```go -var Invoices = ftl.Topic[Invoice]("invoices") +type PubSubEvent struct { + Time time.Time +} + +//ftl:export +type TestTopic = ftl.TopicHandle[PubSubEvent, ftl.SinglePartitionMap[PubSubEvent]] + +//ftl:verb +func Publish(ctx context.Context, topic TestTopic) error { + logger := ftl.LoggerFromContext(ctx) + t := time.Now() + logger.Infof("Publishing %v", t) + return topic.Publish(ctx, PubSubEvent{Time: t}) +} ``` See https://block.github.io/ftl/docs/reference/pubsub/ --- -var ${1:topicVar} = ftl.Topic[${2:Type}]("${1:topicName}") +type ${1:Event} struct { + ${2:// Event fields} +} + +//ftl:export +type ${3:TopicName} = ftl.TopicHandle[${1:Event}, ftl.SinglePartitionMap[${1:Event}]] + +//ftl:verb +func ${4:Publish}(ctx context.Context, topic ${3:TopicName}) error { + return topic.Publish(ctx, ${1:Event}{}) +} diff --git a/internal/lsp/markdown/completion/retry.md b/internal/lsp/markdown/completion/retry.md index 1fd543a358..325ab5020e 100644 --- a/internal/lsp/markdown/completion/retry.md +++ b/internal/lsp/markdown/completion/retry.md @@ -1,12 +1,20 @@ Directive for retrying an async operation. -Any verb called asynchronously (specifically, PubSub subscribers and cron jobs), may optionally specify a basic exponential backoff retry policy. +Any verb called asynchronously (specifically, PubSub subscribers and cron jobs) may specify a basic exponential backoff retry policy. ```go -//ftl:retry [] [] [catch ] +//ftl:retry 10 5s 1m +func Process(ctx context.Context, in Invoice) error { + // Process with retries + return nil +} ``` See https://block.github.io/ftl/docs/reference/retries/ --- //ftl:retry ${1:attempts} ${2:minBackoff} ${3:maxBackoff} +func ${4:Process}(ctx context.Context, in ${5:Type}) error { + ${6:// TODO: Implement} + return nil +} diff --git a/internal/lsp/markdown/completion/retryWithCatch.md b/internal/lsp/markdown/completion/retryWithCatch.md new file mode 100644 index 0000000000..ac916f4c33 --- /dev/null +++ b/internal/lsp/markdown/completion/retryWithCatch.md @@ -0,0 +1,32 @@ +Directive for retrying an async operation with a catch handler. + +Specify a catch handler verb to safely handle final failures after all retries are exhausted. + +```go +//ftl:retry 5 1s catch recoverPaymentProcessing +func ProcessPayment(ctx context.Context, payment Payment) error { + // Process payment with retries + return nil +} + +//ftl:verb +func RecoverPaymentProcessing(ctx context.Context, request builtin.CatchRequest[Payment]) error { + // Safely handle final failure of the payment + return nil +} +``` + +See https://block.github.io/ftl/docs/reference/retries/ +--- + +//ftl:retry ${1:5} ${2:1s} catch ${3:RecoverProcessing} +func ${4:Process}(ctx context.Context, in ${5:Type}) error { + ${6:// TODO: Implement} + return nil +} + +//ftl:verb +func ${3:RecoverProcessing}(ctx context.Context, request builtin.CatchRequest[${5:Type}]) error { + ${7:// Handle final failure} + return nil +} diff --git a/internal/lsp/markdown/completion/secretDeclare.md b/internal/lsp/markdown/completion/secret.md similarity index 57% rename from internal/lsp/markdown/completion/secretDeclare.md rename to internal/lsp/markdown/completion/secret.md index fec9b0431c..dac85e3016 100644 --- a/internal/lsp/markdown/completion/secretDeclare.md +++ b/internal/lsp/markdown/completion/secret.md @@ -3,10 +3,11 @@ Declare a secret. Secrets are encrypted, named, typed values. They are managed by the `ftl secret` command-line. ```go -var apiKey = ftl.Secret[string]("apiKey") +// Will create a secret value called "mySecret" in the FTL schema +type MySecret = ftl.Secret[string] ``` See https://block.github.io/ftl/docs/reference/secretsconfig/ --- -var ${1:secretVar} = ftl.Secret[${2:Type}]("${1:secretVar}") +type ${1:Name} = ftl.Secret[${2:Type}] diff --git a/internal/lsp/markdown/completion/typeAlias.md b/internal/lsp/markdown/completion/typeAlias.md index 59ca535c90..4273cd6e52 100644 --- a/internal/lsp/markdown/completion/typeAlias.md +++ b/internal/lsp/markdown/completion/typeAlias.md @@ -1,10 +1,13 @@ Declare a type alias. -A type alias is an alternate name for an existing type. +A type alias is an alternate name for an existing type. You can declare it with or without the equals sign. ```go //ftl:typealias type UserID string + +//ftl:typealias +type UserToken = string ``` See https://block.github.io/ftl/docs/reference/types/ diff --git a/internal/lsp/markdown/completion/verb.md b/internal/lsp/markdown/completion/verb.md index 0bb59b7dc5..54b52bff3b 100644 --- a/internal/lsp/markdown/completion/verb.md +++ b/internal/lsp/markdown/completion/verb.md @@ -1,20 +1,35 @@ Declare a Verb function. -A Verb is a remotely callable function that takes an input and returns an output. +A Verb is a remotely callable function that takes an input and returns an output. Verbs are the primary way to expose functionality in FTL. They are strongly typed and support both synchronous and asynchronous execution. +The structure of a verb is: ```go //ftl:verb -func Name(ctx context.Context, req Request) (Response, error) {} +func F(context.Context, In) (Out, error) { } +``` + +Here's an example of a verb: +```go +type EchoRequest struct {} + +type EchoResponse struct {} + +//ftl:verb +func Echo(ctx context.Context, in EchoRequest) (EchoResponse, error) { + // Process the request + return EchoResponse{}, nil +} ``` See https://block.github.io/ftl/docs/reference/verbs/ --- type ${1:Request} struct {} + type ${2:Response} struct {} //ftl:verb -func ${3:Name}(ctx context.Context, req ${1:Request}) (${2:Response}, error) { +func ${3:Name}(ctx context.Context, in ${1:Request}) (${2:Response}, error) { ${4:// TODO: Implement} return ${2:Response}{}, nil }